aboutsummaryrefslogtreecommitdiff
path: root/pkg/api
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/api')
-rw-r--r--pkg/api/handlers/compat/containers_logs.go21
-rw-r--r--pkg/api/handlers/compat/events.go22
-rw-r--r--pkg/api/handlers/compat/images.go2
-rw-r--r--pkg/api/handlers/compat/images_build.go258
-rw-r--r--pkg/api/handlers/compat/ping.go4
-rw-r--r--pkg/api/handlers/compat/version.go8
-rw-r--r--pkg/api/handlers/libpod/images.go122
-rw-r--r--pkg/api/handlers/libpod/images_pull.go193
-rw-r--r--pkg/api/handlers/utils/handler.go4
-rw-r--r--pkg/api/server/handler_api.go4
-rw-r--r--pkg/api/server/register_archive.go6
11 files changed, 384 insertions, 260 deletions
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go
index f6d4a518e..d24b7d959 100644
--- a/pkg/api/handlers/compat/containers_logs.go
+++ b/pkg/api/handlers/compat/containers_logs.go
@@ -105,6 +105,18 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var frame strings.Builder
header := make([]byte, 8)
+
+ writeHeader := true
+ // Docker does not write stream headers iff the container has a tty.
+ if !utils.IsLibpodRequest(r) {
+ inspectData, err := ctnr.Inspect(false)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrapf(err, "Failed to obtain logs for Container '%s'", name))
+ return
+ }
+ writeHeader = !inspectData.Config.Tty
+ }
+
for line := range logChannel {
if _, found := r.URL.Query()["until"]; found {
if line.Time.After(until) {
@@ -138,10 +150,13 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
}
frame.WriteString(line.Msg)
- binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
- if _, err := w.Write(header[0:8]); err != nil {
- log.Errorf("unable to write log output header: %q", err)
+ if writeHeader {
+ binary.BigEndian.PutUint32(header[4:], uint32(frame.Len()))
+ if _, err := w.Write(header[0:8]); err != nil {
+ log.Errorf("unable to write log output header: %q", err)
+ }
}
+
if _, err := io.WriteString(w, frame.String()); err != nil {
log.Errorf("unable to write frame string: %q", err)
}
diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go
index 289bf4a2d..fbb33410f 100644
--- a/pkg/api/handlers/compat/events.go
+++ b/pkg/api/handlers/compat/events.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "sync"
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/libpod/events"
@@ -113,8 +112,13 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
errorChannel <- runtime.Events(r.Context(), readOpts)
}()
- var coder *jsoniter.Encoder
- var writeHeader sync.Once
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(http.StatusOK)
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ coder := json.NewEncoder(w)
+ coder.SetEscapeHTML(true)
for stream := true; stream; stream = query.Stream {
select {
@@ -124,18 +128,6 @@ func GetEvents(w http.ResponseWriter, r *http.Request) {
return
}
case evt := <-eventChannel:
- writeHeader.Do(func() {
- // Use a sync.Once so that we write the header
- // only once.
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(http.StatusOK)
- if flusher, ok := w.(http.Flusher); ok {
- flusher.Flush()
- }
- coder = json.NewEncoder(w)
- coder.SetEscapeHTML(true)
- })
-
if evt == nil {
continue
}
diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go
index 8765e20ca..c1ba9ca66 100644
--- a/pkg/api/handlers/compat/images.go
+++ b/pkg/api/handlers/compat/images.go
@@ -205,7 +205,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file"))
}
}
- iid, err := runtime.Import(r.Context(), source, "", query.Changes, "", false)
+ iid, err := runtime.Import(r.Context(), source, "", "", query.Changes, "", false)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball"))
return
diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go
index 9601f5e18..fbaf8d10a 100644
--- a/pkg/api/handlers/compat/images_build.go
+++ b/pkg/api/handlers/compat/images_build.go
@@ -1,7 +1,7 @@
package compat
import (
- "bytes"
+ "context"
"encoding/base64"
"encoding/json"
"fmt"
@@ -18,8 +18,10 @@ import (
"github.com/containers/podman/v2/libpod"
"github.com/containers/podman/v2/pkg/api/handlers"
"github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/channel"
"github.com/containers/storage/pkg/archive"
"github.com/gorilla/schema"
+ "github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
@@ -47,12 +49,25 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
}
- anchorDir, err := extractTarFile(r)
+ contextDirectory, err := extractTarFile(r)
if err != nil {
utils.InternalServerError(w, err)
return
}
- defer os.RemoveAll(anchorDir)
+
+ defer func() {
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found {
+ if keep, _ := strconv.ParseBool(v); keep {
+ return
+ }
+ }
+ }
+ err := os.RemoveAll(filepath.Dir(contextDirectory))
+ if err != nil {
+ logrus.Warn(errors.Wrapf(err, "failed to remove build scratch directory %q", filepath.Dir(contextDirectory)))
+ }
+ }()
query := struct {
Dockerfile string `schema:"dockerfile"`
@@ -67,10 +82,10 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
ForceRm bool `schema:"forcerm"`
Memory int64 `schema:"memory"`
MemSwap int64 `schema:"memswap"`
- CpuShares uint64 `schema:"cpushares"` //nolint
- CpuSetCpus string `schema:"cpusetcpus"` //nolint
- CpuPeriod uint64 `schema:"cpuperiod"` //nolint
- CpuQuota int64 `schema:"cpuquota"` //nolint
+ CpuShares uint64 `schema:"cpushares"` // nolint
+ CpuSetCpus string `schema:"cpusetcpus"` // nolint
+ CpuPeriod uint64 `schema:"cpuperiod"` // nolint
+ CpuQuota int64 `schema:"cpuquota"` // nolint
BuildArgs string `schema:"buildargs"`
ShmSize int `schema:"shmsize"`
Squash bool `schema:"squash"`
@@ -81,52 +96,32 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
Outputs string `schema:"outputs"`
Registry string `schema:"registry"`
}{
- Dockerfile: "Dockerfile",
- Tag: []string{},
- ExtraHosts: "",
- Remote: "",
- Quiet: false,
- NoCache: false,
- CacheFrom: "",
- Pull: false,
- Rm: true,
- ForceRm: false,
- Memory: 0,
- MemSwap: 0,
- CpuShares: 0,
- CpuSetCpus: "",
- CpuPeriod: 0,
- CpuQuota: 0,
- BuildArgs: "",
- ShmSize: 64 * 1024 * 1024,
- Squash: false,
- Labels: "",
- NetworkMode: "",
- Platform: "",
- Target: "",
- Outputs: "",
- Registry: "docker.io",
+ Dockerfile: "Dockerfile",
+ Tag: []string{},
+ Rm: true,
+ ShmSize: 64 * 1024 * 1024,
+ Registry: "docker.io",
}
+
decoder := r.Context().Value("decoder").(*schema.Decoder)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err)
return
}
- var (
- output string
- additionalNames []string
- )
+ var output string
if len(query.Tag) > 0 {
output = query.Tag[0]
}
+ if _, found := r.URL.Query()["target"]; found {
+ output = query.Target
+ }
+
+ var additionalNames []string
if len(query.Tag) > 1 {
additionalNames = query.Tag[1:]
}
- if _, found := r.URL.Query()["target"]; found {
- output = query.Target
- }
var buildArgs = map[string]string{}
if _, found := r.URL.Query()["buildargs"]; found {
if err := json.Unmarshal([]byte(query.BuildArgs), &buildArgs); err != nil {
@@ -156,95 +151,136 @@ func BuildImage(w http.ResponseWriter, r *http.Request) {
}
}
- // build events will be recorded here
- var (
- buildEvents = []string{}
- progress = bytes.Buffer{}
- )
+ // Channels all mux'ed in select{} below to follow API build protocol
+ stdout := channel.NewWriter(make(chan []byte, 1))
+ defer stdout.Close()
+
+ auxout := channel.NewWriter(make(chan []byte, 1))
+ defer auxout.Close()
+
+ stderr := channel.NewWriter(make(chan []byte, 1))
+ defer stderr.Close()
+
+ reporter := channel.NewWriter(make(chan []byte, 1))
+ defer reporter.Close()
buildOptions := imagebuildah.BuildOptions{
- ContextDirectory: filepath.Join(anchorDir, "build"),
+ ContextDirectory: contextDirectory,
PullPolicy: pullPolicy,
Registry: query.Registry,
IgnoreUnrecognizedInstructions: true,
Quiet: query.Quiet,
Isolation: buildah.IsolationChroot,
- Runtime: "",
- RuntimeArgs: nil,
- TransientMounts: nil,
Compression: archive.Gzip,
Args: buildArgs,
Output: output,
AdditionalTags: additionalNames,
- Log: func(format string, args ...interface{}) {
- buildEvents = append(buildEvents, fmt.Sprintf(format, args...))
- },
- In: nil,
- Out: &progress,
- Err: &progress,
- SignaturePolicyPath: "",
- ReportWriter: &progress,
- OutputFormat: buildah.Dockerv2ImageManifest,
- SystemContext: nil,
- NamespaceOptions: nil,
- ConfigureNetwork: 0,
- CNIPluginPath: "",
- CNIConfigDir: "",
- IDMappingOptions: nil,
- AddCapabilities: nil,
- DropCapabilities: nil,
+ Out: stdout,
+ Err: auxout,
+ ReportWriter: reporter,
+ OutputFormat: buildah.Dockerv2ImageManifest,
CommonBuildOpts: &buildah.CommonBuildOptions{
- AddHost: nil,
- CgroupParent: "",
- CPUPeriod: query.CpuPeriod,
- CPUQuota: query.CpuQuota,
- CPUShares: query.CpuShares,
- CPUSetCPUs: query.CpuSetCpus,
- CPUSetMems: "",
- HTTPProxy: false,
- Memory: query.Memory,
- DNSSearch: nil,
- DNSServers: nil,
- DNSOptions: nil,
- MemorySwap: query.MemSwap,
- LabelOpts: nil,
- SeccompProfilePath: "",
- ApparmorProfile: "",
- ShmSize: strconv.Itoa(query.ShmSize),
- Ulimit: nil,
- Volumes: nil,
+ CPUPeriod: query.CpuPeriod,
+ CPUQuota: query.CpuQuota,
+ CPUShares: query.CpuShares,
+ CPUSetCPUs: query.CpuSetCpus,
+ Memory: query.Memory,
+ MemorySwap: query.MemSwap,
+ ShmSize: strconv.Itoa(query.ShmSize),
},
- DefaultMountsFilePath: "",
- IIDFile: "",
Squash: query.Squash,
Labels: labels,
- Annotations: nil,
- OnBuild: nil,
- Layers: false,
NoCache: query.NoCache,
RemoveIntermediateCtrs: query.Rm,
ForceRmIntermediateCtrs: query.ForceRm,
- BlobDirectory: "",
Target: query.Target,
- Devices: nil,
}
runtime := r.Context().Value("runtime").(*libpod.Runtime)
- id, _, err := runtime.Build(r.Context(), buildOptions, query.Dockerfile)
- if err != nil {
- utils.InternalServerError(w, err)
- return
+ runCtx, cancel := context.WithCancel(context.Background())
+ var imageID string
+ go func() {
+ defer cancel()
+ imageID, _, err = runtime.Build(r.Context(), buildOptions, query.Dockerfile)
+ if err != nil {
+ stderr.Write([]byte(err.Error() + "\n"))
+ }
+ }()
+
+ flush := func() {
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
}
- // Find image ID that was built...
- utils.WriteResponse(w, http.StatusOK,
- struct {
- Stream string `json:"stream"`
- }{
- Stream: progress.String() + "\n" +
- strings.Join(buildEvents, "\n") +
- fmt.Sprintf("\nSuccessfully built %s\n", id),
- })
+ // Send headers and prime client for stream to come
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ flush()
+
+ var failed bool
+
+ body := w.(io.Writer)
+ if logrus.IsLevelEnabled(logrus.DebugLevel) {
+ if v, found := os.LookupEnv("PODMAN_RETAIN_BUILD_ARTIFACT"); found {
+ if keep, _ := strconv.ParseBool(v); keep {
+ t, _ := ioutil.TempFile("", "build_*_server")
+ defer t.Close()
+ body = io.MultiWriter(t, w)
+ }
+ }
+ }
+
+ enc := json.NewEncoder(body)
+ enc.SetEscapeHTML(true)
+loop:
+ for {
+ m := struct {
+ Stream string `json:"stream,omitempty"`
+ Error string `json:"error,omitempty"`
+ }{}
+
+ select {
+ case e := <-stdout.Chan():
+ m.Stream = string(e)
+ if err := enc.Encode(m); err != nil {
+ stderr.Write([]byte(err.Error()))
+ }
+ flush()
+ case e := <-auxout.Chan():
+ m.Stream = string(e)
+ if err := enc.Encode(m); err != nil {
+ stderr.Write([]byte(err.Error()))
+ }
+ flush()
+ case e := <-reporter.Chan():
+ m.Stream = string(e)
+ if err := enc.Encode(m); err != nil {
+ stderr.Write([]byte(err.Error()))
+ }
+ flush()
+ case e := <-stderr.Chan():
+ failed = true
+ m.Error = string(e)
+ if err := enc.Encode(m); err != nil {
+ logrus.Warnf("Failed to json encode error %q", err.Error())
+ }
+ flush()
+ case <-runCtx.Done():
+ if !failed {
+ if utils.IsLibpodRequest(r) {
+ m.Stream = imageID
+ } else {
+ m.Stream = fmt.Sprintf("Successfully built %12.12s\n", imageID)
+ }
+ if err := enc.Encode(m); err != nil {
+ logrus.Warnf("Failed to json encode error %q", err.Error())
+ }
+ flush()
+ }
+ break loop
+ }
+ }
}
func extractTarFile(r *http.Request) (string, error) {
@@ -253,10 +289,9 @@ func extractTarFile(r *http.Request) (string, error) {
if err != nil {
return "", err
}
- buildDir := filepath.Join(anchorDir, "build")
path := filepath.Join(anchorDir, "tarBall")
- tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ tarBall, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return "", err
}
@@ -265,14 +300,17 @@ func extractTarFile(r *http.Request) (string, error) {
// Content-Length not used as too many existing API clients didn't honor it
_, err = io.Copy(tarBall, r.Body)
r.Body.Close()
-
if err != nil {
return "", fmt.Errorf("failed Request: Unable to copy tar file from request body %s", r.RequestURI)
}
- _, _ = tarBall.Seek(0, 0)
- if err := archive.Untar(tarBall, buildDir, &archive.TarOptions{}); err != nil {
+ buildDir := filepath.Join(anchorDir, "build")
+ err = os.Mkdir(buildDir, 0700)
+ if err != nil {
return "", err
}
- return anchorDir, nil
+
+ _, _ = tarBall.Seek(0, 0)
+ err = archive.Untar(tarBall, buildDir, nil)
+ return buildDir, err
}
diff --git a/pkg/api/handlers/compat/ping.go b/pkg/api/handlers/compat/ping.go
index eb7eed5b6..06150bb63 100644
--- a/pkg/api/handlers/compat/ping.go
+++ b/pkg/api/handlers/compat/ping.go
@@ -5,7 +5,6 @@ import (
"net/http"
"github.com/containers/buildah"
- "github.com/containers/podman/v2/pkg/api/handlers/utils"
)
// Ping returns headers to client about the service
@@ -14,13 +13,12 @@ import (
// Clients will use the Header availability to test which backend engine is in use.
// Note: Additionally handler supports GET and HEAD methods
func Ping(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("API-Version", utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion].String())
+ // Note API-Version and Libpod-API-Version are set in handler_api.go
w.Header().Set("BuildKit-Version", "")
w.Header().Set("Docker-Experimental", "true")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Pragma", "no-cache")
- w.Header().Set("Libpod-API-Version", utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String())
w.Header().Set("Libpod-Buildha-Version", buildah.Version)
w.WriteHeader(http.StatusOK)
diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go
index e12c7cefa..92900b75d 100644
--- a/pkg/api/handlers/compat/version.go
+++ b/pkg/api/handlers/compat/version.go
@@ -30,6 +30,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "Failed to obtain system memory info"))
return
}
+
components := []docker.ComponentVersion{{
Name: "Podman Engine",
Version: versionInfo.Version,
@@ -46,6 +47,9 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
},
}}
+ apiVersion := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion]
+ minVersion := utils.APIVersion[utils.CompatTree][utils.MinimalAPIVersion]
+
utils.WriteResponse(w, http.StatusOK, entities.ComponentVersion{
Version: docker.Version{
Platform: struct {
@@ -53,7 +57,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
}{
Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version),
},
- APIVersion: components[0].Details["APIVersion"],
+ APIVersion: fmt.Sprintf("%d.%d", apiVersion.Major, apiVersion.Minor),
Arch: components[0].Details["Arch"],
BuildTime: components[0].Details["BuildTime"],
Components: components,
@@ -61,7 +65,7 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) {
GitCommit: components[0].Details["GitCommit"],
GoVersion: components[0].Details["GoVersion"],
KernelVersion: components[0].Details["KernelVersion"],
- MinAPIVersion: components[0].Details["MinAPIVersion"],
+ MinAPIVersion: fmt.Sprintf("%d.%d", minVersion.Major, minVersion.Minor),
Os: components[0].Details["Os"],
Version: components[0].Version,
}})
diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go
index 85f7903dc..bc1bdc287 100644
--- a/pkg/api/handlers/libpod/images.go
+++ b/pkg/api/handlers/libpod/images.go
@@ -11,8 +11,6 @@ import (
"strings"
"github.com/containers/buildah"
- "github.com/containers/image/v5/docker"
- "github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/containers/podman/v2/libpod"
@@ -25,7 +23,6 @@ import (
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/containers/podman/v2/pkg/errorhandling"
- "github.com/containers/podman/v2/pkg/util"
utils2 "github.com/containers/podman/v2/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@@ -391,7 +388,7 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) {
tmpfile.Close()
source = tmpfile.Name()
}
- importedImage, err := runtime.Import(context.Background(), source, query.Reference, query.Changes, query.Message, true)
+ importedImage, err := runtime.Import(context.Background(), source, query.Reference, "", query.Changes, query.Message, true)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to import image"))
return
@@ -400,123 +397,6 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, entities.ImageImportReport{Id: importedImage})
}
-// ImagesPull is the v2 libpod endpoint for pulling images. Note that the
-// mandatory `reference` must be a reference to a registry (i.e., of docker
-// transport or be normalized to one). Other transports are rejected as they
-// do not make sense in a remote context.
-func ImagesPull(w http.ResponseWriter, r *http.Request) {
- runtime := r.Context().Value("runtime").(*libpod.Runtime)
- decoder := r.Context().Value("decoder").(*schema.Decoder)
- query := struct {
- Reference string `schema:"reference"`
- OverrideOS string `schema:"overrideOS"`
- OverrideArch string `schema:"overrideArch"`
- OverrideVariant string `schema:"overrideVariant"`
- TLSVerify bool `schema:"tlsVerify"`
- AllTags bool `schema:"allTags"`
- }{
- TLSVerify: true,
- }
-
- if err := decoder.Decode(&query, r.URL.Query()); err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
- return
- }
-
- if len(query.Reference) == 0 {
- utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
- return
- }
-
- imageRef, err := utils.ParseDockerReference(query.Reference)
- if err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference))
- return
- }
-
- // Trim the docker-transport prefix.
- rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name()))
-
- // all-tags doesn't work with a tagged reference, so let's check early
- namedRef, err := reference.Parse(rawImage)
- if err != nil {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Wrapf(err, "error parsing reference %q", rawImage))
- return
- }
- if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags {
- utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
- errors.Errorf("reference %q must not have a tag for all-tags", rawImage))
- return
- }
-
- authConf, authfile, err := auth.GetCredentials(r)
- if err != nil {
- utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
- return
- }
- defer auth.RemoveAuthfile(authfile)
-
- // Setup the registry options
- dockerRegistryOptions := image.DockerRegistryOptions{
- DockerRegistryCreds: authConf,
- OSChoice: query.OverrideOS,
- ArchitectureChoice: query.OverrideArch,
- VariantChoice: query.OverrideVariant,
- }
- if _, found := r.URL.Query()["tlsVerify"]; found {
- dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
- }
-
- sys := runtime.SystemContext()
- if sys == nil {
- sys = image.GetSystemContext("", authfile, false)
- }
- dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
- sys.DockerAuthConfig = authConf
-
- // Prepare the images we want to pull
- imagesToPull := []string{}
- res := []handlers.LibpodImagesPullReport{}
- imageName := namedRef.String()
-
- if !query.AllTags {
- imagesToPull = append(imagesToPull, imageName)
- } else {
- tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef)
- if err != nil {
- utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
- return
- }
- for _, tag := range tags {
- imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag))
- }
- }
-
- // Finally pull the images
- for _, img := range imagesToPull {
- newImage, err := runtime.ImageRuntime().New(
- context.Background(),
- img,
- "",
- authfile,
- os.Stderr,
- &dockerRegistryOptions,
- image.SigningOptions{},
- nil,
- util.PullImageAlways)
- if err != nil {
- utils.InternalServerError(w, err)
- return
- }
- res = append(res, handlers.LibpodImagesPullReport{ID: newImage.ID()})
- }
-
- utils.WriteResponse(w, http.StatusOK, res)
-}
-
// PushImage is the handler for the compat http endpoint for pushing images.
func PushImage(w http.ResponseWriter, r *http.Request) {
decoder := r.Context().Value("decoder").(*schema.Decoder)
diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go
new file mode 100644
index 000000000..8a2f4f4cf
--- /dev/null
+++ b/pkg/api/handlers/libpod/images_pull.go
@@ -0,0 +1,193 @@
+package libpod
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "strings"
+
+ "github.com/containers/image/v5/docker"
+ "github.com/containers/image/v5/docker/reference"
+ "github.com/containers/image/v5/types"
+ "github.com/containers/podman/v2/libpod"
+ "github.com/containers/podman/v2/libpod/image"
+ "github.com/containers/podman/v2/pkg/api/handlers/utils"
+ "github.com/containers/podman/v2/pkg/auth"
+ "github.com/containers/podman/v2/pkg/channel"
+ "github.com/containers/podman/v2/pkg/domain/entities"
+ "github.com/containers/podman/v2/pkg/util"
+ "github.com/gorilla/schema"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// ImagesPull is the v2 libpod endpoint for pulling images. Note that the
+// mandatory `reference` must be a reference to a registry (i.e., of docker
+// transport or be normalized to one). Other transports are rejected as they
+// do not make sense in a remote context.
+func ImagesPull(w http.ResponseWriter, r *http.Request) {
+ runtime := r.Context().Value("runtime").(*libpod.Runtime)
+ decoder := r.Context().Value("decoder").(*schema.Decoder)
+ query := struct {
+ Reference string `schema:"reference"`
+ OverrideOS string `schema:"overrideOS"`
+ OverrideArch string `schema:"overrideArch"`
+ OverrideVariant string `schema:"overrideVariant"`
+ TLSVerify bool `schema:"tlsVerify"`
+ AllTags bool `schema:"allTags"`
+ }{
+ TLSVerify: true,
+ }
+
+ if err := decoder.Decode(&query, r.URL.Query()); err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
+ return
+ }
+
+ if len(query.Reference) == 0 {
+ utils.InternalServerError(w, errors.New("reference parameter cannot be empty"))
+ return
+ }
+
+ imageRef, err := utils.ParseDockerReference(query.Reference)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "image destination %q is not a docker-transport reference", query.Reference))
+ return
+ }
+
+ // Trim the docker-transport prefix.
+ rawImage := strings.TrimPrefix(query.Reference, fmt.Sprintf("%s://", docker.Transport.Name()))
+
+ // all-tags doesn't work with a tagged reference, so let's check early
+ namedRef, err := reference.Parse(rawImage)
+ if err != nil {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Wrapf(err, "error parsing reference %q", rawImage))
+ return
+ }
+ if _, isTagged := namedRef.(reference.Tagged); isTagged && query.AllTags {
+ utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
+ errors.Errorf("reference %q must not have a tag for all-tags", rawImage))
+ return
+ }
+
+ authConf, authfile, err := auth.GetCredentials(r)
+ if err != nil {
+ utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", auth.XRegistryAuthHeader, r.URL.String()))
+ return
+ }
+ defer auth.RemoveAuthfile(authfile)
+
+ // Setup the registry options
+ dockerRegistryOptions := image.DockerRegistryOptions{
+ DockerRegistryCreds: authConf,
+ OSChoice: query.OverrideOS,
+ ArchitectureChoice: query.OverrideArch,
+ VariantChoice: query.OverrideVariant,
+ }
+ if _, found := r.URL.Query()["tlsVerify"]; found {
+ dockerRegistryOptions.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify)
+ }
+
+ sys := runtime.SystemContext()
+ if sys == nil {
+ sys = image.GetSystemContext("", authfile, false)
+ }
+ dockerRegistryOptions.DockerCertPath = sys.DockerCertPath
+ sys.DockerAuthConfig = authConf
+
+ // Prepare the images we want to pull
+ imagesToPull := []string{}
+ imageName := namedRef.String()
+
+ if !query.AllTags {
+ imagesToPull = append(imagesToPull, imageName)
+ } else {
+ tags, err := docker.GetRepositoryTags(context.Background(), sys, imageRef)
+ if err != nil {
+ utils.InternalServerError(w, errors.Wrap(err, "error getting repository tags"))
+ return
+ }
+ for _, tag := range tags {
+ imagesToPull = append(imagesToPull, fmt.Sprintf("%s:%s", imageName, tag))
+ }
+ }
+
+ writer := channel.NewWriter(make(chan []byte, 1))
+ defer writer.Close()
+
+ stderr := channel.NewWriter(make(chan []byte, 1))
+ defer stderr.Close()
+
+ images := make([]string, 0, len(imagesToPull))
+ runCtx, cancel := context.WithCancel(context.Background())
+ go func(imgs []string) {
+ defer cancel()
+ // Finally pull the images
+ for _, img := range imgs {
+ newImage, err := runtime.ImageRuntime().New(
+ runCtx,
+ img,
+ "",
+ authfile,
+ writer,
+ &dockerRegistryOptions,
+ image.SigningOptions{},
+ nil,
+ util.PullImageAlways)
+ if err != nil {
+ stderr.Write([]byte(err.Error() + "\n"))
+ } else {
+ images = append(images, newImage.ID())
+ }
+ }
+ }(imagesToPull)
+
+ flush := func() {
+ if flusher, ok := w.(http.Flusher); ok {
+ flusher.Flush()
+ }
+ }
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-Type", "application/json")
+ flush()
+
+ enc := json.NewEncoder(w)
+ enc.SetEscapeHTML(true)
+ var failed bool
+loop: // break out of for/select infinite loop
+ for {
+ var report entities.ImagePullReport
+ select {
+ case e := <-writer.Chan():
+ report.Stream = string(e)
+ if err := enc.Encode(report); err != nil {
+ stderr.Write([]byte(err.Error()))
+ }
+ flush()
+ case e := <-stderr.Chan():
+ failed = true
+ report.Error = string(e)
+ if err := enc.Encode(report); err != nil {
+ logrus.Warnf("Failed to json encode error %q", err.Error())
+ }
+ flush()
+ case <-runCtx.Done():
+ if !failed {
+ report.Images = images
+ if err := enc.Encode(report); err != nil {
+ logrus.Warnf("Failed to json encode error %q", err.Error())
+ }
+ flush()
+ }
+ break loop // break out of for/select infinite loop
+ case <-r.Context().Done():
+ // Client has closed connection
+ break loop // break out of for/select infinite loop
+ }
+ }
+}
diff --git a/pkg/api/handlers/utils/handler.go b/pkg/api/handlers/utils/handler.go
index 62fdc05dd..517dccad0 100644
--- a/pkg/api/handlers/utils/handler.go
+++ b/pkg/api/handlers/utils/handler.go
@@ -43,8 +43,8 @@ var (
// clients to shop for the Version they wish to support
APIVersion = map[VersionTree]map[VersionLevel]semver.Version{
LibpodTree: {
- CurrentAPIVersion: semver.MustParse("1.0.0"),
- MinimalAPIVersion: semver.MustParse("1.0.0"),
+ CurrentAPIVersion: semver.MustParse("2.0.0"),
+ MinimalAPIVersion: semver.MustParse("2.0.0"),
},
CompatTree: {
CurrentAPIVersion: semver.MustParse("1.40.0"),
diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go
index e47b66bb4..f2ce0301b 100644
--- a/pkg/api/server/handler_api.go
+++ b/pkg/api/server/handler_api.go
@@ -40,6 +40,10 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc {
c = context.WithValue(c, "idletracker", s.idleTracker) //nolint
r = r.WithContext(c)
+ v := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion]
+ w.Header().Set("API-Version", fmt.Sprintf("%d.%d", v.Major, v.Minor))
+ w.Header().Set("Libpod-API-Version", utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String())
+
h(w, r)
}
fn(w, r)
diff --git a/pkg/api/server/register_archive.go b/pkg/api/server/register_archive.go
index 5931c2fc9..b2d2543c4 100644
--- a/pkg/api/server/register_archive.go
+++ b/pkg/api/server/register_archive.go
@@ -9,7 +9,7 @@ import (
)
func (s *APIServer) registerAchiveHandlers(r *mux.Router) error {
- // swagger:operation POST /containers/{name}/archive compat putArchive
+ // swagger:operation PUT /containers/{name}/archive compat putArchive
// ---
// summary: Put files into a container
// description: Put a tar archive of files into a container
@@ -84,9 +84,9 @@ func (s *APIServer) registerAchiveHandlers(r *mux.Router) error {
// $ref: "#/responses/NoSuchContainer"
// 500:
// $ref: "#/responses/InternalError"
- r.HandleFunc(VersionedPath("/containers/{name}/archive"), s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost)
+ r.HandleFunc(VersionedPath("/containers/{name}/archive"), s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPut, http.MethodHead)
// Added non version path to URI to support docker non versioned paths
- r.HandleFunc("/containers/{name}/archive", s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPost)
+ r.HandleFunc("/containers/{name}/archive", s.APIHandler(compat.Archive)).Methods(http.MethodGet, http.MethodPut, http.MethodHead)
/*
Libpod