diff options
Diffstat (limited to 'pkg')
45 files changed, 545 insertions, 362 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index cae8f88fd..00be8e845 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -410,25 +410,14 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, return nil, err } - networkSettingsDefault := types.DefaultNetworkSettings{ - EndpointID: "", - Gateway: "", - GlobalIPv6Address: "", - GlobalIPv6PrefixLen: 0, - IPAddress: "", - IPPrefixLen: 0, - IPv6Gateway: "", - MacAddress: l.Config().StaticMAC.String(), - } - - networkSettingsBase := types.NetworkSettingsBase{ - Ports: ports, + n, err := json.Marshal(inspect.NetworkSettings) + if err != nil { + return nil, err } - networkSettings := types.NetworkSettings{ - NetworkSettingsBase: networkSettingsBase, - DefaultNetworkSettings: networkSettingsDefault, - Networks: nil, + networkSettings := types.NetworkSettings{} + if err := json.Unmarshal(n, &networkSettings); err != nil { + return nil, err } c := types.ContainerJSON{ diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 4ce31cc83..87c95a24c 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -1,27 +1,19 @@ package compat import ( - "context" "encoding/json" - "fmt" "net/http" - "strings" - "github.com/containers/common/pkg/config" + "github.com/containers/podman/v2/cmd/podman/common" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" - image2 "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/handlers/utils" - "github.com/containers/podman/v2/pkg/namespaces" - "github.com/containers/podman/v2/pkg/rootless" - "github.com/containers/podman/v2/pkg/signal" - createconfig "github.com/containers/podman/v2/pkg/spec" + "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/containers/podman/v2/pkg/specgen" - "github.com/containers/storage" "github.com/gorilla/schema" "github.com/pkg/errors" - "golang.org/x/sys/unix" ) func CreateContainer(w http.ResponseWriter, r *http.Request) { @@ -56,220 +48,27 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "NewFromLocal()")) return } - containerConfig, err := runtime.GetConfig() + + // Take input structure and convert to cliopts + cliOpts, args, err := common.ContainerCreateToContainerCLIOpts(input) if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "GetConfig()")) + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) return } - cc, err := makeCreateConfig(r.Context(), containerConfig, input, newImage) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "makeCreatConfig()")) + sg := specgen.NewSpecGenerator(newImage.ID(), cliOpts.RootFS) + if err := common.FillOutSpecGen(sg, cliOpts, args); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "fill out specgen")) return } - cc.Name = query.Name - utils.CreateContainer(r.Context(), w, runtime, &cc) -} - -func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input handlers.CreateContainerConfig, newImage *image2.Image) (createconfig.CreateConfig, error) { - var ( - err error - init bool - ) - env := make(map[string]string) - stopSignal := unix.SIGTERM - if len(input.StopSignal) > 0 { - stopSignal, err = signal.ParseSignal(input.StopSignal) - if err != nil { - return createconfig.CreateConfig{}, err - } - } - - workDir, err := newImage.WorkingDir(ctx) + ic := abi.ContainerEngine{Libpod: runtime} + report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { - return createconfig.CreateConfig{}, err - } - if workDir == "" { - workDir = "/" - } - if len(input.WorkingDir) > 0 { - workDir = input.WorkingDir - } - - // Only use image's Cmd when the user does not set the entrypoint - if input.Entrypoint == nil && len(input.Cmd) == 0 { - cmdSlice, err := newImage.Cmd(ctx) - if err != nil { - return createconfig.CreateConfig{}, err - } - input.Cmd = cmdSlice - } - - if input.Entrypoint == nil { - entrypointSlice, err := newImage.Entrypoint(ctx) - if err != nil { - return createconfig.CreateConfig{}, err - } - input.Entrypoint = entrypointSlice - } - - stopTimeout := containerConfig.Engine.StopTimeout - if input.StopTimeout != nil { - stopTimeout = uint(*input.StopTimeout) - } - c := createconfig.CgroupConfig{ - Cgroups: "", // podman - Cgroupns: "", // podman - CgroupParent: "", // podman - CgroupMode: "", // podman - } - security := createconfig.SecurityConfig{ - CapAdd: input.HostConfig.CapAdd, - CapDrop: input.HostConfig.CapDrop, - LabelOpts: nil, // podman - NoNewPrivs: false, // podman - ApparmorProfile: "", // podman - SeccompProfilePath: "", - SecurityOpts: input.HostConfig.SecurityOpt, - Privileged: input.HostConfig.Privileged, - ReadOnlyRootfs: input.HostConfig.ReadonlyRootfs, - ReadOnlyTmpfs: false, // podman-only - Sysctl: input.HostConfig.Sysctls, - } - - var netmode namespaces.NetworkMode - if rootless.IsRootless() { - netmode = namespaces.NetworkMode(specgen.Slirp) - } - - network := createconfig.NetworkConfig{ - DNSOpt: input.HostConfig.DNSOptions, - DNSSearch: input.HostConfig.DNSSearch, - DNSServers: input.HostConfig.DNS, - ExposedPorts: input.ExposedPorts, - HTTPProxy: false, // podman - IP6Address: "", - IPAddress: "", - LinkLocalIP: nil, // docker-only - MacAddress: input.MacAddress, - NetMode: netmode, - Network: input.HostConfig.NetworkMode.NetworkName(), - NetworkAlias: nil, // docker-only now - PortBindings: input.HostConfig.PortBindings, - Publish: nil, // podmanseccompPath - PublishAll: input.HostConfig.PublishAllPorts, - } - - uts := createconfig.UtsConfig{ - UtsMode: namespaces.UTSMode(input.HostConfig.UTSMode), - NoHosts: false, //podman - HostAdd: input.HostConfig.ExtraHosts, - Hostname: input.Hostname, - } - - z := createconfig.UserConfig{ - GroupAdd: input.HostConfig.GroupAdd, - IDMappings: &storage.IDMappingOptions{}, // podman //TODO <--- fix this, - UsernsMode: namespaces.UsernsMode(input.HostConfig.UsernsMode), - User: input.User, - } - pidConfig := createconfig.PidConfig{PidMode: namespaces.PidMode(input.HostConfig.PidMode)} - // TODO: We should check that these binds are all listed in the `Volumes` - // key since it doesn't make sense to define a `Binds` element for a - // container path which isn't defined as a volume - volumes := input.HostConfig.Binds - - // Docker is more flexible about its input where podman throws - // 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) { - case 0: - continue - case 1: - env[splitEnv[0]] = "" - default: - env[splitEnv[0]] = strings.Join(splitEnv[1:], "=") - } - } - - // format the tmpfs mounts into a []string from map - tmpfs := make([]string, 0, len(input.HostConfig.Tmpfs)) - for k, v := range input.HostConfig.Tmpfs { - tmpfs = append(tmpfs, fmt.Sprintf("%s:%s", k, v)) - } - - if input.HostConfig.Init != nil && *input.HostConfig.Init { - init = true - } - - m := createconfig.CreateConfig{ - Annotations: nil, // podman - Args: nil, - Cgroup: c, - CidFile: "", - ConmonPidFile: "", // podman - Command: input.Cmd, - UserCommand: input.Cmd, // podman - Detach: false, // - // Devices: input.HostConfig.Devices, - Entrypoint: input.Entrypoint, - Env: env, - HealthCheck: nil, // - Init: init, - InitPath: "", // tbd - Image: input.Image, - ImageID: newImage.ID(), - BuiltinImgVolumes: nil, // podman - ImageVolumeType: "", // podman - Interactive: input.OpenStdin, - // IpcMode: input.HostConfig.IpcMode, - Labels: input.Labels, - LogDriver: input.HostConfig.LogConfig.Type, // is this correct - // LogDriverOpt: input.HostConfig.LogConfig.Config, - Name: input.Name, - Network: network, - Pod: "", // podman - PodmanPath: "", // podman - Quiet: false, // front-end only - Resources: createconfig.CreateResourceConfig{}, - RestartPolicy: input.HostConfig.RestartPolicy.Name, - Rm: input.HostConfig.AutoRemove, - StopSignal: stopSignal, - StopTimeout: stopTimeout, - Systemd: false, // podman - Tmpfs: tmpfs, - User: z, - Uts: uts, - Tty: input.Tty, - Mounts: nil, // we populate - // MountsFlag: input.HostConfig.Mounts, - NamedVolumes: nil, // we populate - Volumes: volumes, - VolumesFrom: input.HostConfig.VolumesFrom, - WorkDir: workDir, - Rootfs: "", // podman - Security: security, - Syslog: false, // podman - - Pid: pidConfig, + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "container create")) + return } - - fullCmd := append(input.Entrypoint, input.Cmd...) - if len(fullCmd) > 0 { - m.PodmanPath = fullCmd[0] - if len(fullCmd) == 1 { - m.Args = fullCmd - } else { - m.Args = fullCmd[1:] - } + createResponse := entities.ContainerCreateResponse{ + ID: report.Id, + Warnings: []string{}, } - - return m, nil + utils.WriteResponse(w, http.StatusCreated, createResponse) } diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go index faab66fe7..38a6329b9 100644 --- a/pkg/api/handlers/compat/containers_logs.go +++ b/pkg/api/handlers/compat/containers_logs.go @@ -148,7 +148,13 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { frame.WriteString(line.Time.Format(time.RFC3339)) frame.WriteString(" ") } + frame.WriteString(line.Msg) + // Log lines in the compat layer require adding EOL + // https://github.com/containers/podman/issues/8058 + if !utils.IsLibpodRequest(r) { + frame.WriteString("\n") + } if writeHeader { binary.BigEndian.PutUint32(header[4:], uint32(frame.Len())) diff --git a/pkg/api/handlers/compat/events.go b/pkg/api/handlers/compat/events.go index a729b84d4..f74491a8f 100644 --- a/pkg/api/handlers/compat/events.go +++ b/pkg/api/handlers/compat/events.go @@ -112,11 +112,15 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { errorChannel <- runtime.Events(r.Context(), readOpts) }() - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) + var flush = func() {} if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() + flush = flusher.Flush } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + flush() + coder := json.NewEncoder(w) coder.SetEscapeHTML(true) @@ -124,6 +128,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { select { case err := <-errorChannel: if err != nil { + // FIXME StatusOK already sent above cannot send 500 here utils.InternalServerError(w, err) return } @@ -136,9 +141,7 @@ func GetEvents(w http.ResponseWriter, r *http.Request) { if err := coder.Encode(e); err != nil { logrus.Errorf("unable to write json: %q", err) } - if flusher, ok := w.(http.Flusher); ok { - flusher.Flush() - } + flush() case <-r.Context().Done(): return } diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 3431823bd..d177b2335 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -60,7 +60,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } - if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false); err != nil { + if err := newImage.Save(r.Context(), name, "docker-archive", tmpfile.Name(), []string{}, false, false, true); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to save image")) return } @@ -429,7 +429,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) return } - if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false); err != nil { + if err := runtime.ImageRuntime().SaveImages(r.Context(), images, "docker-archive", tmpfile.Name(), false, true); err != nil { utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/compat/images_search.go b/pkg/api/handlers/compat/images_search.go index 131fab69f..b3ceae3ee 100644 --- a/pkg/api/handlers/compat/images_search.go +++ b/pkg/api/handlers/compat/images_search.go @@ -7,6 +7,7 @@ import ( "github.com/containers/image/v5/types" "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/gorilla/schema" "github.com/pkg/errors" ) @@ -14,9 +15,10 @@ import ( func SearchImages(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value("decoder").(*schema.Decoder) query := struct { - Term string `json:"term"` - Limit int `json:"limit"` - Filters map[string][]string `json:"filters"` + Term string `json:"term"` + Limit int `json:"limit"` + Filters map[string][]string `json:"filters"` + TLSVerify bool `json:"tlsVerify"` }{ // This is where you can override the golang default value for one of fields } @@ -58,6 +60,18 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { Limit: query.Limit, } + if _, found := r.URL.Query()["tlsVerify"]; found { + options.InsecureSkipTLSVerify = types.NewOptionalBool(!query.TLSVerify) + } + + _, authfile, key, err := auth.GetCredentials(r) + if err != nil { + 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) + options.Authfile = authfile + results, err := image.SearchImages(query.Term, options) if err != nil { utils.BadRequest(w, "term", query.Term, err) diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index a46784a6c..8011c0a04 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -20,6 +20,7 @@ import ( dockerNetwork "github.com/docker/docker/api/types/network" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func InspectNetwork(w http.ResponseWriter, r *http.Request) { @@ -231,6 +232,9 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { if len(networkCreate.Name) > 0 { name = networkCreate.Name } + if len(networkCreate.Driver) < 1 { + networkCreate.Driver = network.DefaultNetworkDriver + } // At present I think we should just support the bridge driver // and allow demand to make us consider more if networkCreate.Driver != network.DefaultNetworkDriver { @@ -312,3 +316,81 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { } utils.WriteResponse(w, http.StatusNoContent, "") } + +// Connect adds a container to a network +// TODO: For now this func is a no-op that checks the container name, network name, and +// responds with a 200. This allows the call to remain intact. We need to decide how +// we make this work with CNI networking and setup/teardown. +func Connect(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + var netConnect types.NetworkConnect + if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + config, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + name := utils.GetName(r) + exists, err := network.Exists(config, name) + if err != nil { + utils.InternalServerError(w, err) + return + } + if !exists { + utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork) + return + } + if _, err = runtime.LookupContainer(netConnect.Container); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + utils.ContainerNotFound(w, netConnect.Container, err) + return + } + utils.Error(w, "unable to lookup container", http.StatusInternalServerError, err) + return + } + logrus.Warnf("network connect endpoint is not fully implemented - tried to connect container %s to network %s", netConnect.Container, name) + utils.WriteResponse(w, http.StatusOK, "OK") +} + +// Disconnect removes a container from a network +// TODO: For now this func is a no-op that checks the container name, network name, and +// responds with a 200. This allows the call to remain intact. We need to decide how +// we make this work with CNI networking and setup/teardown. +func Disconnect(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + + var netDisconnect types.NetworkDisconnect + if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + return + } + config, err := runtime.GetConfig() + if err != nil { + utils.InternalServerError(w, err) + return + } + name := utils.GetName(r) + exists, err := network.Exists(config, name) + if err != nil { + utils.InternalServerError(w, err) + return + } + if !exists { + utils.Error(w, "network not found", http.StatusNotFound, define.ErrNoSuchNetwork) + return + } + if _, err = runtime.LookupContainer(netDisconnect.Container); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + utils.ContainerNotFound(w, netDisconnect.Container, err) + return + } + utils.Error(w, "unable to lookup container", http.StatusInternalServerError, err) + return + } + logrus.Warnf("network disconnect endpoint is not fully implemented - tried to connect container %s to network %s", netDisconnect.Container, name) + utils.WriteResponse(w, http.StatusOK, "OK") +} diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go index 371d02388..0a514822b 100644 --- a/pkg/api/handlers/compat/swagger.go +++ b/pkg/api/handlers/compat/swagger.go @@ -63,3 +63,17 @@ type swagCompatNetworkCreateResponse struct { // in:body Body struct{ types.NetworkCreate } } + +// Network disconnect +// swagger:model NetworkConnectRequest +type swagCompatNetworkConnectRequest struct { + // in:body + Body struct{ types.NetworkConnect } +} + +// Network disconnect +// swagger:model NetworkDisconnectRequest +type swagCompatNetworkDisconnectRequest struct { + // in:body + Body struct{ types.NetworkDisconnect } +} diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 7dde51102..7e6481321 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -11,6 +11,7 @@ import ( "github.com/containers/podman/v2/pkg/api/handlers/compat" "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/containers/podman/v2/pkg/ps" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -18,9 +19,30 @@ import ( ) func ContainerExists(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value("decoder").(*schema.Decoder) runtime := r.Context().Value("runtime").(*libpod.Runtime) + // Now use the ABI implementation to prevent us from having duplicate + // code. + containerEngine := abi.ContainerEngine{Libpod: runtime} + name := utils.GetName(r) - _, err := runtime.LookupContainer(name) + query := struct { + External bool `schema:"external"` + }{ + // override any golang type defaults + } + + 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 + } + + options := entities.ContainerExistsOptions{ + External: query.External, + } + + report, err := containerEngine.ContainerExists(r.Context(), name, options) if err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { utils.ContainerNotFound(w, name, err) @@ -30,7 +52,11 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { return } - utils.WriteResponse(w, http.StatusNoContent, "") + if report.Value { + utils.WriteResponse(w, http.StatusNoContent, "") + } else { + utils.ContainerNotFound(w, name, define.ErrNoSuchCtr) + } } func ListContainers(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 1292090fb..598a46abe 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -206,7 +206,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.Error(w, "unknown format", http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) return } - if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress); err != nil { + if err := newImage.Save(r.Context(), name, query.Format, output, []string{}, false, query.Compress, true); err != nil { utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) return } @@ -284,6 +284,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { Format: query.Format, MultiImageArchive: true, Output: output, + RemoveSignatures: true, } imageEngine := abi.ImageEngine{Libpod: runtime} @@ -636,6 +637,14 @@ func SearchImages(w http.ResponseWriter, r *http.Request) { options.Filter = *filter } + _, authfile, key, err := auth.GetCredentials(r) + if err != nil { + 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) + options.Authfile = authfile + searchResults, err := image.SearchImages(query.Term, options) if err != nil { utils.BadRequest(w, "term", query.Term, err) diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index b7e2b3988..9f6103c45 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -113,15 +113,15 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { return } name := utils.GetName(r) - options := entities.NetworkInspectOptions{} + options := entities.InspectOptions{} ic := abi.ContainerEngine{Libpod: runtime} - reports, err := ic.NetworkInspect(r.Context(), []string{name}, options) + reports, errs, err := ic.NetworkInspect(r.Context(), []string{name}, options) + // If the network cannot be found, we return a 404. + if len(errs) > 0 { + utils.Error(w, "Something went wrong", http.StatusNotFound, define.ErrNoSuchNetwork) + return + } if err != nil { - // If the network cannot be found, we return a 404. - if errors.Cause(err) == define.ErrNoSuchNetwork { - utils.Error(w, "Something went wrong", http.StatusNotFound, err) - return - } utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index 70466f01b..6bb5f5101 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -271,6 +271,15 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI if err != nil { return nil, err } + + rootfs := docker.RootFS{} + if info.RootFS != nil { + rootfs.Type = info.RootFS.Type + rootfs.Layers = make([]string, 0, len(info.RootFS.Layers)) + for _, layer := range info.RootFS.Layers { + rootfs.Layers = append(rootfs.Layers, string(layer)) + } + } dockerImageInspect := docker.ImageInspect{ Architecture: l.Architecture, Author: l.Author, @@ -286,7 +295,7 @@ func ImageDataToImageInspect(ctx context.Context, l *libpodImage.Image) (*ImageI Parent: l.Parent, RepoDigests: info.RepoDigests, RepoTags: info.RepoTags, - RootFS: docker.RootFS{}, + RootFS: rootfs, Size: info.Size, Variant: "", VirtualSize: info.VirtualSize, diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index 61916eedf..6222006e5 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -98,6 +98,63 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // $ref: "#/responses/InternalError" r.HandleFunc(VersionedPath("/networks/create"), s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost) r.HandleFunc("/networks/create", s.APIHandler(compat.CreateNetwork)).Methods(http.MethodPost) + // swagger:operation POST /networks/{name}/connect compat compatConnectNetwork + // --- + // tags: + // - networks (compat) + // summary: Connect container to network + // description: Connect a container to a network. This endpoint is current a no-op + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name of the network + // - in: body + // name: create + // description: attributes for connecting a container to a network + // schema: + // $ref: "#/definitions/NetworkConnectRequest" + // responses: + // 200: + // description: OK + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/{name}/connect"), s.APIHandler(compat.Connect)).Methods(http.MethodPost) + r.HandleFunc("/networks/{name}/connect", s.APIHandler(compat.Connect)).Methods(http.MethodPost) + // swagger:operation POST /networks/{name}/disconnect compat compatDisconnectNetwork + // --- + // tags: + // - networks (compat) + // summary: Disconnect container from network + // description: Disconnect a container from a network. This endpoint is current a no-op + // produces: + // - application/json + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: the name of the network + // - in: body + // name: create + // description: attributes for disconnecting a container from a network + // schema: + // $ref: "#/definitions/NetworkDisconnectRequest" + // responses: + // 200: + // description: OK + // 400: + // $ref: "#/responses/BadParamError" + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost) + r.HandleFunc("/networks/{name}/disconnect", s.APIHandler(compat.Disconnect)).Methods(http.MethodPost) + // swagger:operation DELETE /libpod/networks/{name} libpod libpodRemoveNetwork // --- // tags: diff --git a/pkg/api/server/server.go b/pkg/api/server/server.go index 355a46fb7..64008767b 100644 --- a/pkg/api/server/server.go +++ b/pkg/api/server/server.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "os" - "os/signal" goRuntime "runtime" "strings" "sync" @@ -15,6 +14,7 @@ import ( "time" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/api/server/idle" "github.com/coreos/go-systemd/v22/activation" @@ -180,8 +180,17 @@ func setupSystemd() { // Serve starts responding to HTTP requests. func (s *APIServer) Serve() error { setupSystemd() - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Start the shutdown signal handler. + if err := shutdown.Start(); err != nil { + return err + } + if err := shutdown.Register("server", func(sig os.Signal) error { + return s.Shutdown() + }); err != nil { + return err + } + errChan := make(chan error, 1) go func() { @@ -217,14 +226,7 @@ func (s *APIServer) Serve() error { errChan <- nil }() - select { - case err := <-errChan: - return err - case sig := <-sigChan: - logrus.Infof("APIServer terminated by signal %v", sig) - } - - return nil + return <-errChan } // Shutdown is a clean shutdown waiting on existing clients diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 3a7662c41..31435ae91 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -214,19 +214,23 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) ( authMethods = append(authMethods, ssh.Password(string(pass))) } + port := _url.Port() + if port == "" { + port = "22" + } + callback := ssh.InsecureIgnoreHostKey() if secure { - key := terminal.HostKey(_url.Hostname()) + host := _url.Hostname() + if port != "22" { + host = fmt.Sprintf("[%s]:%s", host, port) + } + key := terminal.HostKey(host) if key != nil { callback = ssh.FixedHostKey(key) } } - port := _url.Port() - if port == "" { - port = "22" - } - bastion, err := ssh.Dial("tcp", net.JoinHostPort(_url.Hostname(), port), &ssh.ClientConfig{ diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 3bd85fbae..7b321af93 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -19,6 +19,7 @@ import ( "github.com/containers/podman/v2/pkg/bindings" sig "github.com/containers/podman/v2/pkg/signal" "github.com/containers/podman/v2/utils" + "github.com/moby/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -60,8 +61,14 @@ func Attach(ctx context.Context, nameOrID string, detachKeys *string, logs, stre } params := url.Values{} + detachKeysInBytes := []byte{} if detachKeys != nil { params.Add("detachKeys", *detachKeys) + + detachKeysInBytes, err = term.ToBytes(*detachKeys) + if err != nil { + return errors.Wrapf(err, "invalid detach keys") + } } if logs != nil { params.Add("logs", fmt.Sprintf("%t", *logs)) @@ -141,27 +148,51 @@ func Attach(ctx context.Context, nameOrID string, detachKeys *string, logs, stre attachReady <- true } + stdoutChan := make(chan error) + stdinChan := make(chan error) + if isSet.stdin { go func() { logrus.Debugf("Copying STDIN to socket") - _, err := utils.CopyDetachable(socket, stdin, []byte{}) - if err != nil { + + _, err := utils.CopyDetachable(socket, stdin, detachKeysInBytes) + + if err != nil && err != define.ErrDetach { logrus.Error("failed to write input to service: " + err.Error()) } + stdinChan <- err }() } buffer := make([]byte, 1024) if ctnr.Config.Tty { - logrus.Debugf("Copying STDOUT of container in terminal mode") + go func() { + logrus.Debugf("Copying STDOUT of container in terminal mode") - if !isSet.stdout { - return fmt.Errorf("container %q requires stdout to be set", ctnr.ID) - } - // If not multiplex'ed, read from server and write to stdout - _, err := io.Copy(stdout, socket) - if err != nil { - return err + if !isSet.stdout { + stdoutChan <- fmt.Errorf("container %q requires stdout to be set", ctnr.ID) + } + // If not multiplex'ed, read from server and write to stdout + _, err := io.Copy(stdout, socket) + + stdoutChan <- err + }() + + for { + select { + case err := <-stdoutChan: + if err != nil { + return err + } + + return nil + case err := <-stdinChan: + if err != nil { + return err + } + + return nil + } } } else { logrus.Debugf("Copying standard streams of container in non-terminal mode") @@ -205,7 +236,6 @@ func Attach(ctx context.Context, nameOrID string, detachKeys *string, logs, stre } } } - return nil } // DemuxHeader reads header for stream from server multiplexed stdin/stdout/stderr/2nd error channel diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 708ad06cb..b5cd2128b 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -322,12 +322,14 @@ func Wait(ctx context.Context, nameOrID string, condition *define.ContainerStatu // Exists is a quick, light-weight way to determine if a given container // exists in local storage. The nameOrID can be a container name // or a partial/full ID. -func Exists(ctx context.Context, nameOrID string) (bool, error) { +func Exists(ctx context.Context, nameOrID string, external bool) (bool, error) { conn, err := bindings.GetClient(ctx) if err != nil { return false, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", nil, nil, nameOrID) + params := url.Values{} + params.Set("external", strconv.FormatBool(external)) + response, err := conn.DoRequest(nil, http.MethodGet, "/containers/%s/exists", params, nil, nameOrID) if err != nil { return false, err } diff --git a/pkg/bindings/test/containers_test.go b/pkg/bindings/test/containers_test.go index 408b4769d..0fb677768 100644 --- a/pkg/bindings/test/containers_test.go +++ b/pkg/bindings/test/containers_test.go @@ -405,7 +405,7 @@ var _ = Describe("Podman containers ", func() { It("podman bogus container does not exist in local storage", func() { // Bogus container existence check should fail - containerExists, err := containers.Exists(bt.conn, "foobar") + containerExists, err := containers.Exists(bt.conn, "foobar", false) Expect(err).To(BeNil()) Expect(containerExists).To(BeFalse()) }) @@ -415,7 +415,7 @@ var _ = Describe("Podman containers ", func() { var name = "top" _, err := bt.RunTopContainer(&name, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerExists, err := containers.Exists(bt.conn, name) + containerExists, err := containers.Exists(bt.conn, name, false) Expect(err).To(BeNil()) Expect(containerExists).To(BeTrue()) }) @@ -425,7 +425,7 @@ var _ = Describe("Podman containers ", func() { var name = "top" cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerExists, err := containers.Exists(bt.conn, cid) + containerExists, err := containers.Exists(bt.conn, cid, false) Expect(err).To(BeNil()) Expect(containerExists).To(BeTrue()) }) @@ -435,7 +435,7 @@ var _ = Describe("Podman containers ", func() { var name = "top" cid, err := bt.RunTopContainer(&name, bindings.PFalse, nil) Expect(err).To(BeNil()) - containerExists, err := containers.Exists(bt.conn, cid[0:12]) + containerExists, err := containers.Exists(bt.conn, cid[0:12], false) Expect(err).To(BeNil()) Expect(containerExists).To(BeTrue()) }) @@ -455,7 +455,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) err = containers.Kill(bt.conn, name, "SIGINT") Expect(err).To(BeNil()) - _, err = containers.Exists(bt.conn, name) + _, err = containers.Exists(bt.conn, name, false) Expect(err).To(BeNil()) }) @@ -466,7 +466,7 @@ var _ = Describe("Podman containers ", func() { Expect(err).To(BeNil()) err = containers.Kill(bt.conn, cid, "SIGTERM") Expect(err).To(BeNil()) - _, err = containers.Exists(bt.conn, cid) + _, err = containers.Exists(bt.conn, cid, false) Expect(err).To(BeNil()) }) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 3b6dd106f..3fd7c79f4 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -246,6 +246,11 @@ type ExecOptions struct { WorkDir string } +// ContainerExistsOptions describes the cli values to check if a container exists +type ContainerExistsOptions struct { + External bool +} + // ContainerStartOptions describes the val from the // CLI needed to start a container type ContainerStartOptions struct { @@ -289,6 +294,7 @@ type ContainerListOptions struct { // ContainerRunOptions describes the options needed // to run a container from the CLI type ContainerRunOptions struct { + CIDFile string Detach bool DetachKeys string ErrorStream *os.File diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 803a59932..8ab72dbd8 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -21,7 +21,7 @@ type ContainerEngine interface { ContainerDiff(ctx context.Context, nameOrID string, options DiffOptions) (*DiffReport, error) ContainerExec(ctx context.Context, nameOrID string, options ExecOptions, streams define.AttachStreams) (int, error) ContainerExecDetached(ctx context.Context, nameOrID string, options ExecOptions) (string, error) - ContainerExists(ctx context.Context, nameOrID string) (*BoolReport, error) + ContainerExists(ctx context.Context, nameOrID string, options ContainerExistsOptions) (*BoolReport, error) ContainerExport(ctx context.Context, nameOrID string, options ContainerExportOptions) error ContainerInit(ctx context.Context, namesOrIds []string, options ContainerInitOptions) ([]*ContainerInitReport, error) ContainerInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]*ContainerInspectReport, []error, error) @@ -51,7 +51,7 @@ type ContainerEngine interface { HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error) Info(ctx context.Context) (*define.Info, error) NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (*NetworkCreateReport, error) - NetworkInspect(ctx context.Context, namesOrIds []string, options NetworkInspectOptions) ([]NetworkInspectReport, error) + NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error) NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error) NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error) PlayKube(ctx context.Context, path string, opts PlayKubeOptions) (*PlayKubeReport, error) @@ -76,7 +76,7 @@ type ContainerEngine interface { VarlinkService(ctx context.Context, opts ServiceOptions) error Version(ctx context.Context) (*SystemVersionReport, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) - VolumeInspect(ctx context.Context, namesOrIds []string, opts VolumeInspectOptions) ([]*VolumeInspectReport, error) + VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) VolumePrune(ctx context.Context) ([]*VolumePruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 982fa0cc0..101542a98 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -300,6 +300,8 @@ type ImageSaveOptions struct { MultiImageArchive bool // Output - write image to the specified path. Output string + // Do not save the signature from the source image + RemoveSignatures bool // Quiet - suppress output when copying images Quiet bool } diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index 9beeeb042..0bab672a7 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -18,11 +18,6 @@ type NetworkListReport struct { *libcni.NetworkConfigList } -// NetworkInspectOptions describes options for inspect networks -type NetworkInspectOptions struct { - Format string -} - // NetworkInspectReport describes the results from inspect networks type NetworkInspectReport map[string]interface{} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 5fdd8afcb..d8ad2d891 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -56,6 +56,8 @@ type InspectOptions struct { Size bool `json:",omitempty"` // Type -- return JSON for specified type. Type string `json:",omitempty"` + // All -- inspect all + All bool `json:",omitempty"` } // All API and CLI diff commands and diff sub-commands use the same options diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index fb8466d04..1bc1e4301 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -105,10 +105,6 @@ type VolumeRmReport struct { Id string //nolint } -type VolumeInspectOptions struct { - All bool -} - type VolumeInspectReport struct { *VolumeConfigResponse } diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 614fd5fe0..98b886845 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -29,6 +29,7 @@ import ( "github.com/containers/podman/v2/pkg/signal" "github.com/containers/podman/v2/pkg/specgen" "github.com/containers/podman/v2/pkg/specgen/generate" + "github.com/containers/podman/v2/pkg/util" "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -74,11 +75,19 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru return } -// TODO: Should return *entities.ContainerExistsReport, error -func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { +// ContainerExists returns whether the container exists in container storage +func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, options entities.ContainerExistsOptions) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupContainer(nameOrID) - if err != nil && errors.Cause(err) != define.ErrNoSuchCtr { - return nil, err + if err != nil { + if errors.Cause(err) != define.ErrNoSuchCtr { + return nil, err + } + if options.External { + // Check if container exists in storage + if _, storageErr := ic.Libpod.StorageContainer(nameOrID); storageErr == nil { + err = nil + } + } } return &entities.BoolReport{Value: err == nil}, nil } @@ -838,6 +847,12 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta return nil, err } + if opts.CIDFile != "" { + if err := util.CreateCidFile(opts.CIDFile, ctr.ID()); err != nil { + return nil, err + } + } + var joinPod bool if len(ctr.PodID()) > 0 { joinPod = true diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index 30a5a55b8..41fdf8f34 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -28,6 +28,9 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, if err != nil { return err } + if runlabel == "" { + return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) + } cmd, env, err := generateRunlabelCommand(runlabel, img, args, options) if err != nil { diff --git a/pkg/domain/infra/abi/cp.go b/pkg/domain/infra/abi/cp.go index a0bfcc90c..ab90c8183 100644 --- a/pkg/domain/infra/abi/cp.go +++ b/pkg/domain/infra/abi/cp.go @@ -26,7 +26,7 @@ import ( ) func (ic *ContainerEngine) ContainerCp(ctx context.Context, source, dest string, options entities.ContainerCpOptions) (*entities.ContainerCpReport, error) { - var extract bool + extract := options.Extract srcCtr, srcPath := parsePath(ic.Libpod, source) destCtr, destPath := parsePath(ic.Libpod, dest) diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index f9d733c63..25335cf11 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -482,13 +482,13 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { if options.MultiImageArchive { nameOrIDs := append([]string{nameOrID}, tags...) - return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet) + return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet, true) } newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) if err != nil { return err } - return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress) + return newImage.Save(ctx, nameOrID, options.Format, options.Output, tags, options.Quiet, options.Compress, true) } func (ir *ImageEngine) Diff(_ context.Context, nameOrID string, _ entities.DiffOptions) (*entities.DiffReport, error) { diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index 3e47dc67a..281b04294 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -5,6 +5,7 @@ import ( libpodImage "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/pkg/errors" ) func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { @@ -43,12 +44,21 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) VirtualSize: img.VirtualSize, RepoTags: img.Names(), // may include tags and digests } - e.Labels, _ = img.Labels(context.TODO()) + e.Labels, err = img.Labels(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving label for image %q: you may need to remove the image to resolve the error", img.ID()) + } - ctnrs, _ := img.Containers() + ctnrs, err := img.Containers() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving containers for image %q: you may need to remove the image to resolve the error", img.ID()) + } e.Containers = len(ctnrs) - sz, _ := img.Size(context.TODO()) + sz, err := img.Size(ctx) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving size of image %q: you may need to remove the image to resolve the error", img.ID()) + } e.Size = int64(*sz) summaries = append(summaries, &e) diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index f40df828a..4f572fb88 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -43,21 +43,26 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net return reports, nil } -func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) { +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]entities.NetworkInspectReport, []error, error) { config, err := ic.Libpod.GetConfig() if err != nil { - return nil, err + return nil, nil, err } - + var errs []error rawCNINetworks := make([]entities.NetworkInspectReport, 0, len(namesOrIds)) for _, name := range namesOrIds { rawList, err := network.InspectNetwork(config, name) if err != nil { - return nil, err + if errors.Cause(err) == define.ErrNoSuchNetwork { + errs = append(errs, errors.Errorf("no such network %s", name)) + continue + } else { + return nil, nil, errors.Wrapf(err, "error inspecting network %s", name) + } } rawCNINetworks = append(rawCNINetworks, rawList) } - return rawCNINetworks, nil + return rawCNINetworks, errs, nil } func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) { diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index a7c66bae6..348570a20 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -341,7 +341,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } named, err := reference.ParseNormalizedNamed(container.Image) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "Failed to parse image %q", container.Image) } // In kube, if the image is tagged with latest, it should always pull if tagged, isTagged := named.(reference.NamedTagged); isTagged { diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index f484e926c..0c586cf5c 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -5,12 +5,17 @@ import ( "syscall" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/shutdown" "github.com/containers/podman/v2/pkg/signal" "github.com/sirupsen/logrus" ) // ProxySignals ... func ProxySignals(ctr *libpod.Container) { + // Stop catching the shutdown signals (SIGINT, SIGTERM) - they're going + // to the container now. + shutdown.Stop() + sigBuffer := make(chan os.Signal, 128) signal.CatchAll(sigBuffer) diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index 946f258af..a7262f61b 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -4,6 +4,7 @@ import ( "context" "github.com/containers/podman/v2/libpod" + "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/filters" "github.com/containers/podman/v2/pkg/domain/infra/abi/parse" @@ -71,9 +72,10 @@ func (ic *ContainerEngine) VolumeRm(ctx context.Context, namesOrIds []string, op return reports, nil } -func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.VolumeInspectOptions) ([]*entities.VolumeInspectReport, error) { +func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]*entities.VolumeInspectReport, []error, error) { var ( err error + errs []error vols []*libpod.Volume ) @@ -82,13 +84,18 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin if opts.All { vols, err = ic.Libpod.GetAllVolumes() if err != nil { - return nil, err + return nil, nil, err } } else { for _, v := range namesOrIds { vol, err := ic.Libpod.LookupVolume(v) if err != nil { - return nil, errors.Wrapf(err, "error inspecting volume %s", v) + if errors.Cause(err) == define.ErrNoSuchVolume { + errs = append(errs, errors.Errorf("no such volume %s", v)) + continue + } else { + return nil, nil, errors.Wrapf(err, "error inspecting volume %s", v) + } } vols = append(vols, vol) } @@ -98,11 +105,11 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin var uid, gid int uid, err = v.UID() if err != nil { - return nil, err + return nil, nil, err } gid, err = v.GID() if err != nil { - return nil, err + return nil, nil, err } config := entities.VolumeConfigResponse{ Name: v.Name(), @@ -117,7 +124,7 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin } reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: &config}) } - return reports, nil + return reports, errs, nil } func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index 1bb4e68ac..8066e1c00 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -21,6 +21,7 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/specgen" + "github.com/containers/podman/v2/pkg/util" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -29,8 +30,8 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, return errors.New("not implemented") } -func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { - exists, err := containers.Exists(ic.ClientCxt, nameOrID) +func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, options entities.ContainerExistsOptions) (*entities.BoolReport, error) { + exists, err := containers.Exists(ic.ClientCxt, nameOrID, options.External) return &entities.BoolReport{Value: exists}, err } @@ -500,7 +501,6 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if err == define.ErrDetach { // User manually detached // Exit cleanly immediately - report.Err = err reports = append(reports, &report) return reports, nil } @@ -559,6 +559,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta for _, w := range con.Warnings { fmt.Fprintf(os.Stderr, "%s\n", w) } + if opts.CIDFile != "" { + if err := util.CreateCidFile(opts.CIDFile, con.ID); err != nil { + return nil, err + } + } report := entities.ContainerRunReport{Id: con.ID} @@ -573,6 +578,10 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta // Attach if err := startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream); err != nil { + if err == define.ErrDetach { + return &report, nil + } + report.ExitCode = define.ExitCode(err) if opts.Rm { if rmErr := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); rmErr != nil { diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index d155fdd9e..15527e02c 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -5,22 +5,34 @@ import ( "github.com/containers/podman/v2/pkg/bindings/network" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/pkg/errors" ) func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { return network.List(ic.ClientCxt, options) } -func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) { - reports := make([]entities.NetworkInspectReport, 0, len(namesOrIds)) +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]entities.NetworkInspectReport, []error, error) { + var ( + reports = make([]entities.NetworkInspectReport, 0, len(namesOrIds)) + errs = []error{} + ) for _, name := range namesOrIds { report, err := network.Inspect(ic.ClientCxt, name) if err != nil { - return nil, err + errModel, ok := err.(entities.ErrorModel) + if !ok { + return nil, nil, err + } + if errModel.ResponseCode == 404 { + errs = append(errs, errors.Errorf("no such network %q", name)) + continue + } + return nil, nil, err } reports = append(reports, report...) } - return reports, nil + return reports, errs, nil } func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, options entities.NetworkRmOptions) ([]*entities.NetworkRmReport, error) { diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index e432d3292..c0df2bb7b 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -5,6 +5,7 @@ import ( "github.com/containers/podman/v2/pkg/bindings/volumes" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/pkg/errors" ) func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IDOrNameResponse, error) { @@ -35,25 +36,36 @@ func (ic *ContainerEngine) VolumeRm(ctx context.Context, namesOrIds []string, op return reports, nil } -func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.VolumeInspectOptions) ([]*entities.VolumeInspectReport, error) { +func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]*entities.VolumeInspectReport, []error, error) { + var ( + reports = make([]*entities.VolumeInspectReport, 0, len(namesOrIds)) + errs = []error{} + ) if opts.All { vols, err := volumes.List(ic.ClientCxt, nil) if err != nil { - return nil, err + return nil, nil, err } for _, v := range vols { namesOrIds = append(namesOrIds, v.Name) } } - reports := make([]*entities.VolumeInspectReport, 0, len(namesOrIds)) for _, id := range namesOrIds { data, err := volumes.Inspect(ic.ClientCxt, id) if err != nil { - return nil, err + errModel, ok := err.(entities.ErrorModel) + if !ok { + return nil, nil, err + } + if errModel.ResponseCode == 404 { + errs = append(errs, errors.Errorf("no such volume %q", id)) + continue + } + return nil, nil, err } reports = append(reports, &entities.VolumeInspectReport{VolumeConfigResponse: data}) } - return reports, nil + return reports, errs, nil } func (ic *ContainerEngine) VolumePrune(ctx context.Context) ([]*entities.VolumePruneReport, error) { diff --git a/pkg/spec/security.go b/pkg/spec/security.go index e152e3495..5f7db7edb 100644 --- a/pkg/spec/security.go +++ b/pkg/spec/security.go @@ -178,7 +178,7 @@ func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserCon for _, opt := range c.SecurityOpts { // Split on both : and = - splitOpt := strings.Split(opt, "=") + splitOpt := strings.SplitN(opt, "=", 2) if len(splitOpt) == 1 { splitOpt = strings.Split(opt, ":") } diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go index 5e2f04e50..ebf5ec196 100644 --- a/pkg/spec/storage.go +++ b/pkg/spec/storage.go @@ -394,7 +394,7 @@ func getBindMount(args []string) (spec.Mount, error) { var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool for _, val := range args { - kv := strings.Split(val, "=") + kv := strings.SplitN(val, "=", 2) switch kv[0] { case "bind-nonrecursive": newMount.Options = append(newMount.Options, "bind") @@ -517,7 +517,7 @@ func getTmpfsMount(args []string) (spec.Mount, error) { var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool for _, val := range args { - kv := strings.Split(val, "=") + kv := strings.SplitN(val, "=", 2) switch kv[0] { case "tmpcopyup", "notmpcopyup": if setTmpcopyup { @@ -591,7 +591,7 @@ func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { var setSource, setDest, setRORW, setSuid, setDev, setExec bool for _, val := range args { - kv := strings.Split(val, "=") + kv := strings.SplitN(val, "=", 2) switch kv[0] { case "ro", "rw": if setRORW { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 105e36bc6..f051537de 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "strings" "github.com/containers/common/pkg/config" "github.com/containers/podman/v2/libpod" @@ -91,11 +92,19 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, err } - imgName := s.Image - names := newImage.Names() - if len(names) > 0 { - imgName = names[0] + // If the input name changed, we could properly resolve the + // image. Otherwise, it must have been an ID where we're + // defaulting to the first name or an empty one if no names are + // present. + imgName := newImage.InputName + if s.Image == newImage.InputName && strings.HasPrefix(newImage.ID(), s.Image) { + imgName = "" + names := newImage.Names() + if len(names) > 0 { + imgName = names[0] + } } + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), imgName, s.RawImageName)) } if err := s.Validate(); err != nil { diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 7adb8be6a..7e4f09dc4 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -342,7 +342,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt return errors.Wrapf(err, "error looking up container to share uts namespace with") } hostname = utsCtr.Hostname() - case s.NetNS.NSMode == specgen.Host || s.UtsNS.NSMode == specgen.Host: + case (s.NetNS.NSMode == specgen.Host && hostname == "") || s.UtsNS.NSMode == specgen.Host: tmpHostname, err := os.Hostname() if err != nil { return errors.Wrap(err, "unable to retrieve hostname of the host") diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index f02432f5b..8454458a8 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -110,7 +110,7 @@ func makeCommand(ctx context.Context, s *specgen.SpecGenerator, img *image.Image // Only use image command if the user did not manually set an // entrypoint. command := s.Command - if command == nil && img != nil && s.Entrypoint == nil { + if (command == nil || len(command) == 0) && img != nil && (s.Entrypoint == nil || len(s.Entrypoint) == 0) { newCmd, err := img.Cmd(ctx) if err != nil { return nil, err diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index 7dd50ac0d..5c13c95b2 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -25,7 +25,12 @@ const ( func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { // First, we need to validate the ports passed in the specgen, and then // convert them into CNI port mappings. - finalMappings := []ocicni.PortMapping{} + type tempMapping struct { + mapping ocicni.PortMapping + startOfRange bool + isInRange bool + } + tempMappings := []tempMapping{} // To validate, we need two maps: one for host ports, one for container // ports. @@ -153,18 +158,32 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, Protocol: p, HostIP: port.HostIP, } - finalMappings = append(finalMappings, cniPort) + tempMappings = append( + tempMappings, + tempMapping{ + mapping: cniPort, + startOfRange: port.Range > 0 && index == 0, + isInRange: port.Range > 0, + }, + ) } } } // Handle any 0 host ports now by setting random container ports. if postAssignHostPort { - remadeMappings := make([]ocicni.PortMapping, 0, len(finalMappings)) + remadeMappings := make([]ocicni.PortMapping, 0, len(tempMappings)) + + var ( + candidate int + err error + ) // Iterate over all - for _, p := range finalMappings { - if p.HostPort != 0 { + for _, tmp := range tempMappings { + p := tmp.mapping + + if p.HostPort != 0 && !tmp.isInRange { remadeMappings = append(remadeMappings, p) continue } @@ -192,9 +211,15 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, // Max retries to ensure we don't loop forever. for i := 0; i < 15; i++ { - candidate, err := getRandomPort() - if err != nil { - return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort) + // Only get a random candidate for single entries or the start + // of a range. Otherwise we just increment the candidate. + if !tmp.isInRange || tmp.startOfRange { + candidate, err = getRandomPort() + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort) + } + } else { + candidate++ } if hostPortMap[uint16(candidate)] == 0 { @@ -213,6 +238,11 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, return remadeMappings, containerPortValidate, hostPortValidate, nil } + finalMappings := []ocicni.PortMapping{} + for _, m := range tempMappings { + finalMappings = append(finalMappings, m.mapping) + } + return finalMappings, containerPortValidate, hostPortValidate, nil } diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go index ab3dc54e4..169bec2af 100644 --- a/pkg/terminal/util.go +++ b/pkg/terminal/util.go @@ -12,6 +12,7 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/knownhosts" "golang.org/x/crypto/ssh/terminal" "k8s.io/client-go/util/homedir" ) @@ -114,6 +115,9 @@ func HostKey(host string) ssh.PublicKey { return nil } + // support -H parameter for ssh-keyscan + hashhost := knownhosts.HashHostname(host) + scanner := bufio.NewScanner(fd) for scanner.Scan() { _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes()) @@ -123,7 +127,7 @@ func HostKey(host string) ssh.PublicKey { } for _, h := range hosts { - if h == host { + if h == host || h == hashhost { return key } } diff --git a/pkg/util/utils.go b/pkg/util/utils.go index c3a70e2fb..a9aad657d 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -474,8 +474,8 @@ func getTomlStorage(storeOptions *storage.StoreOptions) *tomlConfig { config.Storage.RunRoot = storeOptions.RunRoot config.Storage.GraphRoot = storeOptions.GraphRoot for _, i := range storeOptions.GraphDriverOptions { - s := strings.Split(i, "=") - if s[0] == "overlay.mount_program" { + s := strings.SplitN(i, "=", 2) + if s[0] == "overlay.mount_program" && len(s) == 2 { config.Storage.Options.MountProgram = s[1] } } @@ -638,3 +638,18 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) { func DefaultContainerConfig() *config.Config { return containerConfig } + +func CreateCidFile(cidfile string, id string) error { + cidFile, err := OpenExclusiveFile(cidfile) + if err != nil { + if os.IsExist(err) { + return errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", cidfile) + } + return errors.Errorf("error opening cidfile %s", cidfile) + } + if _, err = cidFile.WriteString(id); err != nil { + logrus.Error(err) + } + cidFile.Close() + return nil +} diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index ef310d590..af6c43fec 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -843,7 +843,7 @@ func (i *VarlinkAPI) ImageSave(call iopodman.VarlinkCall, options iopodman.Image saveOutput := bytes.NewBuffer([]byte{}) c := make(chan error) go func() { - err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress) + err := newImage.Save(getContext(), options.Name, options.Format, output, options.MoreTags, options.Quiet, options.Compress, true) c <- err close(c) }() |