diff options
Diffstat (limited to 'pkg/api/handlers/compat')
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 25 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_attach.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_create.go | 15 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_logs.go | 21 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers_stats.go | 43 | ||||
-rw-r--r-- | pkg/api/handlers/compat/events.go | 22 | ||||
-rw-r--r-- | pkg/api/handlers/compat/exec.go | 6 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images.go | 28 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_build.go | 310 | ||||
-rw-r--r-- | pkg/api/handlers/compat/images_push.go | 4 | ||||
-rw-r--r-- | pkg/api/handlers/compat/ping.go | 4 | ||||
-rw-r--r-- | pkg/api/handlers/compat/version.go | 8 | ||||
-rw-r--r-- | pkg/api/handlers/compat/volumes.go | 23 |
13 files changed, 325 insertions, 190 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index b1ef08cda..3a904ba87 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -17,6 +17,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func RemoveContainer(w http.ResponseWriter, r *http.Request) { @@ -44,8 +45,25 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) + if err != nil && errors.Cause(err) == define.ErrNoSuchCtr { + // Failed to get container. If force is specified, get the container's ID + // and evict it + if !query.Force { + utils.ContainerNotFound(w, name, err) + return + } + + if _, err := runtime.EvictContainer(r.Context(), name, query.Vols); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Debugf("Ignoring error (--allow-missing): %q", err) + w.WriteHeader(http.StatusNoContent) + return + } + logrus.Warn(errors.Wrapf(err, "Failed to evict container: %q", name)) + utils.InternalServerError(w, err) + return + } + w.WriteHeader(http.StatusNoContent) return } @@ -85,7 +103,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 { + if _, found := r.URL.Query()["limit"]; found && query.Limit > 0 { last := query.Limit if len(containers) > last { containers = containers[len(containers)-last:] @@ -175,6 +193,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { err = con.Kill(signal) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) + return } // Docker waits for the container to stop if the signal is 0 or diff --git a/pkg/api/handlers/compat/containers_attach.go b/pkg/api/handlers/compat/containers_attach.go index e20d48d86..4a1196c89 100644 --- a/pkg/api/handlers/compat/containers_attach.go +++ b/pkg/api/handlers/compat/containers_attach.go @@ -6,7 +6,7 @@ import ( "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/gorilla/schema" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -92,7 +92,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) // Perform HTTP attach. @@ -109,7 +109,7 @@ func AttachContainer(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 93e4fe540..0579da8de 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -82,7 +82,13 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input } } - workDir := "/" + workDir, err := newImage.WorkingDir(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + if workDir == "" { + workDir = "/" + } if len(input.WorkingDir) > 0 { workDir = input.WorkingDir } @@ -169,6 +175,11 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input // away incorrectly formatted variables so we cannot reuse the // parsing of the env input // [Foo Other=one Blank=] + imgEnv, err := newImage.Env(ctx) + if err != nil { + return createconfig.CreateConfig{}, err + } + input.Env = append(imgEnv, input.Env...) for _, e := range input.Env { splitEnv := strings.Split(e, "=") switch len(splitEnv) { @@ -210,7 +221,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input ImageID: newImage.ID(), BuiltinImgVolumes: nil, // podman ImageVolumeType: "", // podman - Interactive: false, + Interactive: input.OpenStdin, // IpcMode: input.HostConfig.IpcMode, Labels: input.Labels, LogDriver: input.HostConfig.LogConfig.Type, // is this correct 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/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go index 3d7d49ad3..16bd0518a 100644 --- a/pkg/api/handlers/compat/containers_stats.go +++ b/pkg/api/handlers/compat/containers_stats.go @@ -75,32 +75,48 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { } } - for ok := true; ok; ok = query.Stream { + // Write header and content type. + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + if flusher, ok := w.(http.Flusher); ok { + flusher.Flush() + } + + // Setup JSON encoder for streaming. + coder := json.NewEncoder(w) + coder.SetEscapeHTML(true) + +streamLabel: // A label to flatten the scope + select { + case <-r.Context().Done(): + logrus.Debugf("Client connection (container stats) cancelled") + + default: // Container stats stats, err := ctnr.GetContainerStats(stats) if err != nil { - utils.InternalServerError(w, err) + logrus.Errorf("Unable to get container stats: %v", err) return } inspect, err := ctnr.Inspect(false) if err != nil { - utils.InternalServerError(w, err) + logrus.Errorf("Unable to inspect container: %v", err) return } // Cgroup stats cgroupPath, err := ctnr.CGroupPath() if err != nil { - utils.InternalServerError(w, err) + logrus.Errorf("Unable to get cgroup path of container: %v", err) return } cgroup, err := cgroups.Load(cgroupPath) if err != nil { - utils.InternalServerError(w, err) + logrus.Errorf("Unable to load cgroup: %v", err) return } cgroupStat, err := cgroup.Stat() if err != nil { - utils.InternalServerError(w, err) + logrus.Errorf("Unable to get cgroup stats: %v", err) return } @@ -175,11 +191,18 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { Networks: net, } - utils.WriteJSON(w, http.StatusOK, s) + if err := coder.Encode(s); err != nil { + logrus.Errorf("Unable to encode stats: %v", err) + return + } if flusher, ok := w.(http.Flusher); ok { flusher.Flush() } + if !query.Stream { + return + } + preRead = s.Read bits, err := json.Marshal(s.CPUStats) if err != nil { @@ -189,10 +212,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { logrus.Errorf("Unable to unmarshal previous stats: %q", err) } - // Only sleep when we're streaming. - if query.Stream { - time.Sleep(DefaultStatsPeriod) - } + time.Sleep(DefaultStatsPeriod) + goto streamLabel } } 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/exec.go b/pkg/api/handlers/compat/exec.go index 1db950f85..df51293c2 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -10,7 +10,7 @@ import ( "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/api/server/idletracker" + "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/containers/podman/v2/pkg/specgen/generate" "github.com/gorilla/mux" "github.com/pkg/errors" @@ -174,7 +174,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } - idleTracker := r.Context().Value("idletracker").(*idletracker.IdleTracker) + idleTracker := r.Context().Value("idletracker").(*idle.Tracker) hijackChan := make(chan bool, 1) if err := sessionCtr.ExecHTTPStartAndAttach(sessionID, r, w, nil, nil, nil, hijackChan); err != nil { @@ -186,7 +186,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { // We do need to tell the idle tracker that the // connection has been closed, though. We can guarantee // that is true after HTTPAttach exits. - idleTracker.TrackHijackedClosed() + idleTracker.Close() } else { // A hijack was not successfully completed. We need to // report the error normally. diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 8765e20ca..940b57343 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/containers/buildah" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/podman/v2/libpod" image2 "github.com/containers/podman/v2/libpod/image" @@ -17,7 +18,6 @@ import ( "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/auth" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/containers/podman/v2/pkg/util" "github.com/docker/docker/api/types" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -93,7 +93,7 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { }) } - //FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden + // FIXME/TODO to do this exactly correct, pruneimages needs to return idrs and space-reclaimed, then we are golden ipr := types.ImagesPruneReport{ ImagesDeleted: idr, SpaceReclaimed: 1, // TODO we cannot supply this right now @@ -113,7 +113,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { Changes string `schema:"changes"` Comment string `schema:"comment"` Container string `schema:"container"` - //fromSrc string # fromSrc is currently unused + // fromSrc string # fromSrc is currently unused Pause bool `schema:"pause"` Repo string `schema:"repo"` Tag string `schema:"tag"` @@ -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 @@ -224,7 +224,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { Status string `json:"status"` Progress string `json:"progress"` ProgressDetail map[string]string `json:"progressDetail"` - Id string `json:"id"` //nolint + Id string `json:"id"` // nolint }{ Status: iid, ProgressDetail: map[string]string{}, @@ -257,9 +257,9 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { fromImage = fmt.Sprintf("%s:%s", fromImage, query.Tag) } - authConf, authfile, err := auth.GetCredentials(r) + authConf, authfile, key, 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())) + utils.Error(w, "Failed to retrieve repository credentials", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", key, r.URL.String())) return } defer auth.RemoveAuthfile(authfile) @@ -268,6 +268,16 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { if sys := runtime.SystemContext(); sys != nil { registryOpts.DockerCertPath = sys.DockerCertPath } + rtc, err := runtime.GetConfig() + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + pullPolicy, err := config.ValidatePullPolicy(rtc.Engine.PullPolicy) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } img, err := runtime.ImageRuntime().New(r.Context(), fromImage, "", // signature policy @@ -276,7 +286,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { ®istryOpts, image2.SigningOptions{}, nil, // label - util.PullImageMissing, + pullPolicy, ) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) @@ -289,7 +299,7 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { Error string `json:"error"` Progress string `json:"progress"` ProgressDetail map[string]string `json:"progressDetail"` - Id string `json:"id"` //nolint + Id string `json:"id"` // nolint }{ Status: fmt.Sprintf("pulling image (%s) from %s", img.Tag, strings.Join(img.Names(), ", ")), ProgressDetail: map[string]string{}, diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 9601f5e18..d5ccf56fe 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -1,8 +1,7 @@ package compat import ( - "bytes" - "encoding/base64" + "context" "encoding/json" "fmt" "io" @@ -11,28 +10,21 @@ import ( "os" "path/filepath" "strconv" - "strings" "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" + "github.com/containers/image/v5/types" "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/auth" + "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" ) func BuildImage(w http.ResponseWriter, r *http.Request) { - authConfigs := map[string]handlers.AuthConfig{} - if hdr, found := r.Header["X-Registry-Config"]; found && len(hdr) > 0 { - authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(hdr[0])) - if json.NewDecoder(authConfigsJSON).Decode(&authConfigs) != nil { - utils.BadRequest(w, "X-Registry-Config", hdr[0], json.NewDecoder(authConfigsJSON).Decode(&authConfigs)) - return - } - } - if hdr, found := r.Header["Content-Type"]; found && len(hdr) > 0 { contentType := hdr[0] switch contentType { @@ -47,86 +39,80 @@ 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 { + BuildArgs string `schema:"buildargs"` + CacheFrom string `schema:"cachefrom"` + CpuPeriod uint64 `schema:"cpuperiod"` // nolint + CpuQuota int64 `schema:"cpuquota"` // nolint + CpuSetCpus string `schema:"cpusetcpus"` // nolint + CpuShares uint64 `schema:"cpushares"` // nolint Dockerfile string `schema:"dockerfile"` - Tag []string `schema:"t"` ExtraHosts string `schema:"extrahosts"` - Remote string `schema:"remote"` - Quiet bool `schema:"q"` + ForceRm bool `schema:"forcerm"` + HTTPProxy bool `schema:"httpproxy"` + Labels string `schema:"labels"` + MemSwap int64 `schema:"memswap"` + Memory int64 `schema:"memory"` + NetworkMode string `schema:"networkmode"` NoCache bool `schema:"nocache"` - CacheFrom string `schema:"cachefrom"` + Outputs string `schema:"outputs"` + Platform string `schema:"platform"` Pull bool `schema:"pull"` + Quiet bool `schema:"q"` + Registry string `schema:"registry"` + Remote string `schema:"remote"` Rm bool `schema:"rm"` - 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 - BuildArgs string `schema:"buildargs"` ShmSize int `schema:"shmsize"` Squash bool `schema:"squash"` - Labels string `schema:"labels"` - NetworkMode string `schema:"networkmode"` - Platform string `schema:"platform"` + Tag []string `schema:"t"` Target string `schema:"target"` - 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", + Registry: "docker.io", + Rm: true, + ShmSize: 64 * 1024 * 1024, + Tag: []string{}, } + 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 +142,149 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { } } - // build events will be recorded here - var ( - buildEvents = []string{} - progress = bytes.Buffer{} - ) + creds, authfile, key, err := auth.GetCredentials(r) + if err != nil { + // Credential value(s) not returned as their value is not human readable + utils.BadRequest(w, key.String(), "n/a", err) + return + } + defer auth.RemoveAuthfile(authfile) + + // 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...)) + Out: stdout, + Err: auxout, + ReportWriter: reporter, + OutputFormat: buildah.Dockerv2ImageManifest, + SystemContext: &types.SystemContext{ + AuthFilePath: authfile, + DockerAuthConfig: creds, }, - 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, 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, + HTTPProxy: query.HTTPProxy, + 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 +293,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 +304,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/images_push.go b/pkg/api/handlers/compat/images_push.go index e69a2212a..dd706a156 100644 --- a/pkg/api/handlers/compat/images_push.go +++ b/pkg/api/handlers/compat/images_push.go @@ -49,9 +49,9 @@ func PushImage(w http.ResponseWriter, r *http.Request) { return } - authConf, authfile, err := auth.GetCredentials(r) + authConf, authfile, key, 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())) + utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse %q header for %s", key, r.URL.String())) return } defer auth.RemoveAuthfile(authfile) 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/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index 976c52acb..a45509fdb 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -93,6 +93,29 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { return } + // See if the volume exists already + existingVolume, err := runtime.GetVolume(input.Name) + if err != nil && errors.Cause(err) != define.ErrNoSuchVolume { + utils.InternalServerError(w, err) + return + } + + // if using the compat layer and the volume already exists, we + // must return a 201 with the same information as create + if existingVolume != nil && !utils.IsLibpodRequest(r) { + response := docker_api_types.Volume{ + CreatedAt: existingVolume.CreatedTime().Format(time.RFC3339), + Driver: existingVolume.Driver(), + Labels: existingVolume.Labels(), + Mountpoint: existingVolume.MountPoint(), + Name: existingVolume.Name(), + Options: existingVolume.Options(), + Scope: existingVolume.Scope(), + } + utils.WriteResponse(w, http.StatusCreated, response) + return + } + if len(input.Name) > 0 { volumeOptions = append(volumeOptions, libpod.WithVolumeName(input.Name)) } |