diff options
Diffstat (limited to 'pkg')
111 files changed, 2371 insertions, 1242 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index efc4b1a4d..ae063dc9f 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -2,6 +2,7 @@ package compat import ( "encoding/json" + "errors" "fmt" "net/http" "sort" @@ -27,7 +28,6 @@ import ( "github.com/docker/go-connections/nat" "github.com/docker/go-units" "github.com/gorilla/schema" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -46,7 +46,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -73,7 +73,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) reports, err := containerEngine.ContainerRm(r.Context(), []string{name}, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } @@ -83,7 +83,7 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { } if len(reports) > 0 && reports[0].Err != nil { err = reports[0].Err - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } @@ -110,12 +110,12 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to decode filter parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode filter parameters for %s: %w", r.URL.String(), err)) return } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -164,7 +164,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { for _, ctnr := range containers { api, err := LibpodToContainer(ctnr, query.Size) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { // container was removed between the initial fetch of the list and conversion logrus.Debugf("Container %s removed between initial fetch and conversion, ignoring in output", ctnr.ID()) continue @@ -187,7 +187,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -215,7 +215,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { Signal: "KILL", } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -228,12 +228,12 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { } report, err := containerEngine.ContainerKill(r.Context(), []string{name}, options) if err != nil { - if errors.Cause(err) == define.ErrCtrStateInvalid || - errors.Cause(err) == define.ErrCtrStopped { + if errors.Is(err, define.ErrCtrStateInvalid) || + errors.Is(err, define.ErrCtrStopped) { utils.Error(w, http.StatusConflict, err) return } - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } @@ -289,8 +289,10 @@ func LibpodToContainer(l *libpod.Container, sz bool) (*handlers.Container, error return nil, err } stateStr := state.String() - if stateStr == "configured" { - stateStr = "created" + + // Some docker states are not the same as ours. This makes sure the state string stays true to the Docker API + if state == define.ContainerStateCreated { + stateStr = define.ContainerStateConfigured.String() } switch state { @@ -429,9 +431,9 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, state.Running = true } - // docker calls the configured state "created" - if state.Status == define.ContainerStateConfigured.String() { - state.Status = define.ContainerStateCreated.String() + // Dockers created state is our configured state + if state.Status == define.ContainerStateCreated.String() { + state.Status = define.ContainerStateConfigured.String() } if l.HasHealthCheck() && state.Status != "created" { @@ -519,7 +521,7 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON, for ep := range inspect.HostConfig.PortBindings { splitp := strings.SplitN(ep, "/", 2) if len(splitp) != 2 { - return nil, errors.Errorf("PORT/PROTOCOL Format required for %q", ep) + return nil, fmt.Errorf("PORT/PROTOCOL Format required for %q", ep) } exposedPort, err := nat.NewPort(splitp[1], splitp[0]) if err != nil { @@ -626,7 +628,7 @@ func RenameContainer(w http.ResponseWriter, r *http.Request) { Name string `schema:"name"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -637,7 +639,7 @@ func RenameContainer(w http.ResponseWriter, r *http.Request) { } if _, err := runtime.RenameContainer(r.Context(), ctr, query.Name); err != nil { - if errors.Cause(err) == define.ErrPodExists || errors.Cause(err) == define.ErrCtrExists { + if errors.Is(err, define.ErrPodExists) || errors.Is(err, define.ErrCtrExists) { utils.Error(w, http.StatusConflict, err) return } diff --git a/pkg/api/handlers/compat/containers_archive.go b/pkg/api/handlers/compat/containers_archive.go index 77fbbe38a..cadfb7bd5 100644 --- a/pkg/api/handlers/compat/containers_archive.go +++ b/pkg/api/handlers/compat/containers_archive.go @@ -2,10 +2,13 @@ package compat import ( "encoding/json" + "fmt" "net/http" "os" "strings" + "errors" + "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers/utils" @@ -14,7 +17,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/gorilla/schema" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -28,7 +30,7 @@ func Archive(w http.ResponseWriter, r *http.Request) { case http.MethodHead, http.MethodGet: handleHeadAndGet(w, r, decoder, runtime) default: - utils.Error(w, http.StatusNotImplemented, errors.Errorf("unsupported method: %v", r.Method)) + utils.Error(w, http.StatusNotImplemented, fmt.Errorf("unsupported method: %v", r.Method)) } } @@ -39,7 +41,7 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De err := decoder.Decode(&query, r.URL.Query()) if err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query")) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("couldn't decode the query: %w", err)) return } @@ -65,7 +67,7 @@ func handleHeadAndGet(w http.ResponseWriter, r *http.Request, decoder *schema.De w.Header().Add(copy.XDockerContainerPathStatHeader, statHeader) } - if errors.Cause(err) == define.ErrNoSuchCtr || errors.Cause(err) == copy.ErrENOENT { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, copy.ErrENOENT) { // 404 is returned for an absent container and path. The // clients must deal with it accordingly. utils.Error(w, http.StatusNotFound, err) @@ -105,14 +107,14 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, err := decoder.Decode(&query, r.URL.Query()) if err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query")) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("couldn't decode the query: %w", err)) return } var rename map[string]string if query.Rename != "" { if err := json.Unmarshal([]byte(query.Rename), &rename); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrap(err, "couldn't decode the query field 'rename'")) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("couldn't decode the query field 'rename': %w", err)) return } } @@ -128,10 +130,10 @@ func handlePut(w http.ResponseWriter, r *http.Request, decoder *schema.Decoder, }) if err != nil { switch { - case errors.Cause(err) == define.ErrNoSuchCtr || os.IsNotExist(err): + case errors.Is(err, define.ErrNoSuchCtr) || os.IsNotExist(err): // 404 is returned for an absent container and path. The // clients must deal with it accordingly. - utils.Error(w, http.StatusNotFound, errors.Wrap(err, "the container doesn't exists")) + utils.Error(w, http.StatusNotFound, fmt.Errorf("the container doesn't exists: %w", err)) case strings.Contains(err.Error(), "copier: put: error creating file"): // Not the best test but need to break this out for compatibility // See vendor/github.com/containers/buildah/copier/copier.go:1585 diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 67ec52047..9fff8b4c8 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -2,6 +2,7 @@ package compat import ( "encoding/json" + "errors" "fmt" "net" "net/http" @@ -26,7 +27,6 @@ import ( "github.com/containers/storage" "github.com/docker/docker/api/types/mount" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func CreateContainer(w http.ResponseWriter, r *http.Request) { @@ -38,14 +38,14 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } // compatible configuration body := handlers.CreateContainerConfig{} if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err)) return } @@ -53,37 +53,37 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { body.Name = query.Name if len(body.HostConfig.Links) > 0 { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(utils.ErrLinkNotSupport, "bad parameter")) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("bad parameter: %w", utils.ErrLinkNotSupport)) return } rtc, err := runtime.GetConfig() if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to get runtime config")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to get runtime config: %w", err)) return } imageName, err := utils.NormalizeToDockerHub(r, body.Config.Image) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } body.Config.Image = imageName newImage, resolvedName, err := runtime.LibimageRuntime().LookupImage(body.Config.Image, nil) if err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { - utils.Error(w, http.StatusNotFound, errors.Wrap(err, "No such image")) + if errors.Is(err, storage.ErrImageUnknown) { + utils.Error(w, http.StatusNotFound, fmt.Errorf("no such image: %w", err)) return } - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error looking up image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error looking up image: %w", err)) return } // Take body structure and convert to cliopts cliOpts, args, err := cliOpts(body, rtc) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "make cli opts()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("make cli opts(): %w", err)) return } @@ -100,7 +100,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { sg := specgen.NewSpecGenerator(imgNameOrID, cliOpts.RootFS) if err := specgenutil.FillOutSpecGen(sg, cliOpts, args); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "fill out specgen")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("fill out specgen: %w", err)) return } // moby always create the working directory @@ -109,7 +109,7 @@ func CreateContainer(w http.ResponseWriter, r *http.Request) { ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.ContainerCreate(r.Context(), sg) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "container create")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("container create: %w", err)) return } createResponse := entities.ContainerCreateResponse{ @@ -300,7 +300,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C if len(endpoint.IPAddress) > 0 { staticIP := net.ParseIP(endpoint.IPAddress) if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ip address %q", endpoint.IPAddress) + return nil, nil, fmt.Errorf("failed to parse the ip address %q", endpoint.IPAddress) } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) } @@ -310,7 +310,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C if len(endpoint.IPAMConfig.IPv4Address) > 0 { staticIP := net.ParseIP(endpoint.IPAMConfig.IPv4Address) if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv4 address %q", endpoint.IPAMConfig.IPv4Address) + return nil, nil, fmt.Errorf("failed to parse the ipv4 address %q", endpoint.IPAMConfig.IPv4Address) } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) } @@ -318,7 +318,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C if len(endpoint.IPAMConfig.IPv6Address) > 0 { staticIP := net.ParseIP(endpoint.IPAMConfig.IPv6Address) if staticIP == nil { - return nil, nil, errors.Errorf("failed to parse the ipv6 address %q", endpoint.IPAMConfig.IPv6Address) + return nil, nil, fmt.Errorf("failed to parse the ipv6 address %q", endpoint.IPAMConfig.IPv6Address) } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) } @@ -327,7 +327,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C if len(endpoint.MacAddress) > 0 { staticMac, err := net.ParseMAC(endpoint.MacAddress) if err != nil { - return nil, nil, errors.Errorf("failed to parse the mac address %q", endpoint.MacAddress) + return nil, nil, fmt.Errorf("failed to parse the mac address %q", endpoint.MacAddress) } netOpts.StaticMAC = types.HardwareAddr(staticMac) } @@ -433,7 +433,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C } if cc.HostConfig.Resources.NanoCPUs > 0 { if cliOpts.CPUPeriod != 0 || cliOpts.CPUQuota != 0 { - return nil, nil, errors.Errorf("NanoCpus conflicts with CpuPeriod and CpuQuota") + return nil, nil, fmt.Errorf("NanoCpus conflicts with CpuPeriod and CpuQuota") } cliOpts.CPUPeriod = 100000 cliOpts.CPUQuota = cc.HostConfig.Resources.NanoCPUs / 10000 @@ -479,7 +479,7 @@ func cliOpts(cc handlers.CreateContainerConfig, rtc *config.Config) (*entities.C } if err := os.MkdirAll(vol, 0o755); err != nil { if !os.IsExist(err) { - return nil, nil, errors.Wrapf(err, "error making volume mountpoint for volume %s", vol) + return nil, nil, fmt.Errorf("error making volume mountpoint for volume %s: %w", vol, err) } } } diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go index ded6480bc..d805b95c2 100644 --- a/pkg/api/handlers/compat/containers_restart.go +++ b/pkg/api/handlers/compat/containers_restart.go @@ -1,6 +1,8 @@ package compat import ( + "errors" + "fmt" "net/http" "github.com/containers/podman/v4/libpod" @@ -10,7 +12,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func RestartContainer(w http.ResponseWriter, r *http.Request) { @@ -29,7 +30,7 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) { // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -44,7 +45,7 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) { } report, err := containerEngine.ContainerRestart(r.Context(), []string{name}, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } diff --git a/pkg/api/handlers/compat/containers_stats.go b/pkg/api/handlers/compat/containers_stats.go index 6855e369b..12c5283fc 100644 --- a/pkg/api/handlers/compat/containers_stats.go +++ b/pkg/api/handlers/compat/containers_stats.go @@ -12,6 +12,7 @@ import ( api "github.com/containers/podman/v4/pkg/api/types" docker "github.com/docker/docker/api/types" "github.com/gorilla/schema" + runccgroups "github.com/opencontainers/runc/libcontainer/cgroups" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -58,7 +59,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { flusher.Flush() } - // Setup JSON encoder for streaming. + // Set up JSON encoder for streaming. coder.SetEscapeHTML(true) var preRead time.Time var preCPUStats CPUStats @@ -132,17 +133,23 @@ streamLabel: // A label to flatten the scope InstanceID: "", } + cfg := ctnr.Config() + memoryLimit := cgroupStat.MemoryStats.Usage.Limit + if cfg.Spec.Linux != nil && cfg.Spec.Linux.Resources != nil && cfg.Spec.Linux.Resources.Memory != nil && *cfg.Spec.Linux.Resources.Memory.Limit > 0 { + memoryLimit = uint64(*cfg.Spec.Linux.Resources.Memory.Limit) + } + systemUsage, _ := cgroups.GetSystemCPUUsage() s := StatsJSON{ Stats: Stats{ Read: time.Now(), PreRead: preRead, PidsStats: docker.PidsStats{ - Current: cgroupStat.Pids.Current, + Current: cgroupStat.PidsStats.Current, Limit: 0, }, BlkioStats: docker.BlkioStats{ - IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.Blkio.IoServiceBytesRecursive), + IoServiceBytesRecursive: toBlkioStatEntry(cgroupStat.BlkioStats.IoServiceBytesRecursive), IoServicedRecursive: nil, IoQueuedRecursive: nil, IoServiceTimeRecursive: nil, @@ -153,14 +160,14 @@ streamLabel: // A label to flatten the scope }, CPUStats: CPUStats{ CPUUsage: docker.CPUUsage{ - TotalUsage: cgroupStat.CPU.Usage.Total, - PercpuUsage: cgroupStat.CPU.Usage.PerCPU, - UsageInKernelmode: cgroupStat.CPU.Usage.Kernel, - UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel, + TotalUsage: cgroupStat.CpuStats.CpuUsage.TotalUsage, + PercpuUsage: cgroupStat.CpuStats.CpuUsage.PercpuUsage, + UsageInKernelmode: cgroupStat.CpuStats.CpuUsage.UsageInKernelmode, + UsageInUsermode: cgroupStat.CpuStats.CpuUsage.TotalUsage - cgroupStat.CpuStats.CpuUsage.UsageInKernelmode, }, CPU: stats.CPU, SystemUsage: systemUsage, - OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)), + OnlineCPUs: uint32(len(cgroupStat.CpuStats.CpuUsage.PercpuUsage)), ThrottlingData: docker.ThrottlingData{ Periods: 0, ThrottledPeriods: 0, @@ -169,11 +176,11 @@ streamLabel: // A label to flatten the scope }, PreCPUStats: preCPUStats, MemoryStats: docker.MemoryStats{ - Usage: cgroupStat.Memory.Usage.Usage, - MaxUsage: cgroupStat.Memory.Usage.Limit, + Usage: cgroupStat.MemoryStats.Usage.Usage, + MaxUsage: cgroupStat.MemoryStats.Usage.Limit, Stats: nil, Failcnt: 0, - Limit: cgroupStat.Memory.Usage.Limit, + Limit: memoryLimit, Commit: 0, CommitPeak: 0, PrivateWorkingSet: 0, @@ -210,7 +217,7 @@ streamLabel: // A label to flatten the scope } } -func toBlkioStatEntry(entries []cgroups.BlkIOEntry) []docker.BlkioStatEntry { +func toBlkioStatEntry(entries []runccgroups.BlkioStatEntry) []docker.BlkioStatEntry { results := make([]docker.BlkioStatEntry, len(entries)) for i, e := range entries { bits, err := json.Marshal(e) diff --git a/pkg/api/handlers/compat/containers_stop.go b/pkg/api/handlers/compat/containers_stop.go index 1c1fb310c..33bb3a679 100644 --- a/pkg/api/handlers/compat/containers_stop.go +++ b/pkg/api/handlers/compat/containers_stop.go @@ -1,6 +1,8 @@ package compat import ( + "errors" + "fmt" "net/http" "github.com/containers/podman/v4/libpod" @@ -10,7 +12,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func StopContainer(w http.ResponseWriter, r *http.Request) { @@ -29,7 +30,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) { // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -63,7 +64,7 @@ func StopContainer(w http.ResponseWriter, r *http.Request) { } report, err := containerEngine.ContainerStop(r.Context(), []string{name}, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } diff --git a/pkg/api/handlers/compat/exec.go b/pkg/api/handlers/compat/exec.go index a8b45c685..1b4dead8b 100644 --- a/pkg/api/handlers/compat/exec.go +++ b/pkg/api/handlers/compat/exec.go @@ -2,9 +2,12 @@ package compat import ( "encoding/json" + "errors" + "fmt" "net/http" "strings" + "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers" @@ -14,7 +17,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/specgenutil" "github.com/gorilla/mux" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -24,7 +26,7 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { input := new(handlers.ExecCreateConfig) if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error decoding request body as JSON")) + utils.InternalServerError(w, fmt.Errorf("error decoding request body as JSON: %w", err)) return } @@ -48,7 +50,7 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { for _, envStr := range input.Env { split := strings.SplitN(envStr, "=", 2) if len(split) != 2 { - utils.Error(w, http.StatusBadRequest, errors.Errorf("environment variable %q badly formed, must be key=value", envStr)) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("environment variable %q badly formed, must be key=value", envStr)) return } libpodConfig.Environment[split[0]] = split[1] @@ -78,14 +80,14 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) { sessID, err := ctr.ExecCreate(libpodConfig) if err != nil { - if errors.Cause(err) == define.ErrCtrStateInvalid { + if errors.Is(err, define.ErrCtrStateInvalid) { // Check if the container is paused. If so, return a 409 state, err := ctr.State() if err == nil { // Ignore the error != nil case. We're already // throwing an InternalServerError below. if state == define.ContainerStatePaused { - utils.Error(w, http.StatusConflict, errors.Errorf("cannot create exec session as container %s is paused", ctr.ID())) + utils.Error(w, http.StatusConflict, fmt.Errorf("cannot create exec session as container %s is paused", ctr.ID())) return } } @@ -112,7 +114,7 @@ func ExecInspectHandler(w http.ResponseWriter, r *http.Request) { session, err := sessionCtr.ExecSession(sessionID) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "error retrieving exec session %s from container %s", sessionID, sessionCtr.ID())) + utils.InternalServerError(w, fmt.Errorf("error retrieving exec session %s from container %s: %w", sessionID, sessionCtr.ID(), err)) return } @@ -135,7 +137,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { bodyParams := new(handlers.ExecStartConfig) if err := json.NewDecoder(r.Body).Decode(&bodyParams); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to decode parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to decode parameters for %s: %w", r.URL.String(), err)) return } // TODO: Verify TTY setting against what inspect session was made with @@ -154,7 +156,7 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { return } if state != define.ContainerStateRunning { - utils.Error(w, http.StatusConflict, errors.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String())) + utils.Error(w, http.StatusConflict, fmt.Errorf("cannot exec in a container that is not running; container %s is %s", sessionCtr.ID(), state.String())) return } @@ -172,12 +174,12 @@ func ExecStartHandler(w http.ResponseWriter, r *http.Request) { } logErr := func(e error) { - logrus.Error(errors.Wrapf(e, "error attaching to container %s exec session %s", sessionCtr.ID(), sessionID)) + logrus.Error(fmt.Errorf("error attaching to container %s exec session %s: %w", sessionCtr.ID(), sessionID, e)) } - var size *define.TerminalSize + var size *resize.TerminalSize if bodyParams.Tty && (bodyParams.Height > 0 || bodyParams.Width > 0) { - size = &define.TerminalSize{ + size = &resize.TerminalSize{ Height: bodyParams.Height, Width: bodyParams.Width, } diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 981a38c35..2f8d151d8 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -2,6 +2,7 @@ package compat import ( "encoding/json" + "errors" "fmt" "io/ioutil" "net/http" @@ -24,7 +25,6 @@ import ( "github.com/containers/storage" "github.com/gorilla/schema" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -50,7 +50,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } defer os.Remove(tmpfile.Name()) @@ -58,7 +58,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } @@ -70,22 +70,22 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } if err := imageEngine.Save(r.Context(), possiblyNormalizedName, nil, saveOptions); err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { - utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) + if errors.Is(err, storage.ErrImageUnknown) { + utils.ImageNotFound(w, name, fmt.Errorf("failed to find image %s: %w", name, err)) return } - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } if err := tmpfile.Close(); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to close tempfile: %w", err)) return } rdr, err := os.Open(tmpfile.Name()) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to read the exported tarfile: %w", err)) return } defer rdr.Close() @@ -111,12 +111,12 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } rtc, err := runtime.GetConfig() if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } sc := runtime.SystemContext() @@ -132,7 +132,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { input := handlers.CreateContainerConfig{} if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } @@ -154,7 +154,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { destImage = fmt.Sprintf("%s:%s", query.Repo, query.Tag) possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, destImage) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } destImage = possiblyNormalizedName @@ -162,7 +162,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { commitImage, err := ctr.Commit(r.Context(), destImage, options) if err != nil && !strings.Contains(err.Error(), "is not running") { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("CommitFailure: %w", err)) return } utils.WriteResponse(w, http.StatusCreated, entities.IDResponse{ID: commitImage.ID()}) @@ -186,7 +186,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } // fromSrc – Source to import. The value may be a URL from which the image can be retrieved or - to read the image from the request body. This parameter may only be used when importing an image. @@ -194,13 +194,13 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { if source == "-" { f, err := ioutil.TempFile("", "api_load.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to create tempfile: %w", err)) return } source = f.Name() if err := SaveFromBody(f, r); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to write temporary file: %w", err)) } } @@ -208,7 +208,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { if query.Repo != "" { possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, reference) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } reference = possiblyNormalizedName @@ -229,7 +229,7 @@ func CreateImageFromSrc(w http.ResponseWriter, r *http.Request) { imageEngine := abi.ImageEngine{Libpod: runtime} report, err := imageEngine.Import(r.Context(), opts) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to import tarball: %w", err)) return } // Success @@ -265,13 +265,13 @@ func CreateImageFromImage(w http.ResponseWriter, r *http.Request) { // This is where you can override the golang default value for one of fields } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, mergeNameAndTagOrDigest(query.FromImage, query.Tag)) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } @@ -388,7 +388,7 @@ func GetImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } @@ -397,12 +397,12 @@ func GetImage(w http.ResponseWriter, r *http.Request) { // Here we need to fiddle with the error message because docker-py is looking for "No // such image" to determine on how to raise the correct exception. errMsg := strings.ReplaceAll(err.Error(), "image not known", "No such image") - utils.Error(w, http.StatusNotFound, errors.Errorf("failed to find image %s: %s", name, errMsg)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s: %s", name, errMsg)) return } inspect, err := handlers.ImageDataToImageInspect(r.Context(), newImage) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to convert ImageData to ImageInspect '%s'", inspect.ID)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to convert ImageData to ImageInspect '%s': %w", inspect.ID, err)) return } utils.WriteResponse(w, http.StatusOK, inspect) @@ -421,7 +421,7 @@ func GetImages(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if _, found := r.URL.Query()["digests"]; found && query.Digests { @@ -472,7 +472,7 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -480,7 +480,7 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { // to load. f, err := ioutil.TempFile("", "api_load.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to create tempfile: %w", err)) return } defer func() { @@ -490,7 +490,7 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { } }() if err := SaveFromBody(f, r); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to write temporary file")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to write temporary file: %w", err)) return } @@ -499,12 +499,12 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { loadOptions := entities.ImageLoadOptions{Input: f.Name()} loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to load image: %w", err)) return } if len(loadReport.Names) < 1 { - utils.Error(w, http.StatusInternalServerError, errors.Errorf("one or more images are required")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("one or more images are required")) return } @@ -527,7 +527,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { // This is where you can override the golang default value for one of fields } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if len(query.Names) == 0 { @@ -539,7 +539,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { for i, img := range query.Names { possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, img) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } images[i] = possiblyNormalizedName @@ -547,12 +547,12 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } defer os.Remove(tmpfile.Name()) if err := tmpfile.Close(); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to close tempfile: %w", err)) return } @@ -566,7 +566,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { rdr, err := os.Open(tmpfile.Name()) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to read the exported tarfile: %w", err)) return } defer rdr.Close() diff --git a/pkg/api/handlers/compat/images_remove.go b/pkg/api/handlers/compat/images_remove.go index 35bcb36aa..b59bfd0b1 100644 --- a/pkg/api/handlers/compat/images_remove.go +++ b/pkg/api/handlers/compat/images_remove.go @@ -1,6 +1,8 @@ package compat import ( + "errors" + "fmt" "net/http" "github.com/containers/podman/v4/libpod" @@ -10,7 +12,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/storage" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func RemoveImage(w http.ResponseWriter, r *http.Request) { @@ -25,7 +26,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if _, found := r.URL.Query()["noprune"]; found { @@ -36,7 +37,7 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) possiblyNormalizedName, err := utils.NormalizeToDockerHub(r, name) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error normalizing image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error normalizing image: %w", err)) return } @@ -48,12 +49,12 @@ func RemoveImage(w http.ResponseWriter, r *http.Request) { report, rmerrors := imageEngine.Remove(r.Context(), []string{possiblyNormalizedName}, options) if len(rmerrors) > 0 && rmerrors[0] != nil { err := rmerrors[0] - if errors.Cause(err) == storage.ErrImageUnknown { - utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) + if errors.Is(err, storage.ErrImageUnknown) { + utils.ImageNotFound(w, name, fmt.Errorf("failed to find image %s: %w", name, err)) return } - if errors.Cause(err) == storage.ErrImageUsedByContainer { - utils.Error(w, http.StatusConflict, errors.Wrapf(err, "image %s is in use", name)) + if errors.Is(err, storage.ErrImageUsedByContainer) { + utils.Error(w, http.StatusConflict, fmt.Errorf("image %s is in use: %w", name, err)) return } utils.Error(w, http.StatusInternalServerError, err) diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 6fdd5c6a7..65177218a 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -2,6 +2,7 @@ package compat import ( "encoding/json" + "errors" "fmt" "net" "net/http" @@ -19,7 +20,6 @@ import ( dockerNetwork "github.com/docker/docker/api/types/network" "github.com/gorilla/schema" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -36,7 +36,7 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { } decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -133,7 +133,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -161,12 +161,13 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { func CreateNetwork(w http.ResponseWriter, r *http.Request) { var ( - networkCreate types.NetworkCreateRequest - network nettypes.Network + networkCreate types.NetworkCreateRequest + network nettypes.Network + responseWarning string ) runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } @@ -179,8 +180,40 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { network.Internal = networkCreate.Internal network.IPv6Enabled = networkCreate.EnableIPv6 - // FIXME use docker options and convert them to valid libpod options - // network.Options = networkCreate.Options + network.Options = make(map[string]string) + + // TODO: we should consider making this constants in c/common/libnetwork/types + for opt, optVal := range networkCreate.Options { + switch opt { + case "mtu": + fallthrough + case "com.docker.network.driver.mtu": + if network.Driver == nettypes.BridgeNetworkDriver { + network.Options["mtu"] = optVal + } + case "icc": + fallthrough + case "com.docker.network.bridge.enable_icc": + // TODO: needs to be implemented + if network.Driver == nettypes.BridgeNetworkDriver { + responseWarning = "com.docker.network.bridge.enable_icc is not currently implemented" + } + case "com.docker.network.bridge.name": + if network.Driver == nettypes.BridgeNetworkDriver { + network.NetworkInterface = optVal + } + case "mode": + if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver { + network.Options[opt] = optVal + } + case "parent": + if network.Driver == nettypes.MacVLANNetworkDriver || network.Driver == nettypes.IPVLANNetworkDriver { + network.NetworkInterface = optVal + } + default: + responseWarning = "\"" + opt + ": " + optVal + "\" is not a recognized option" + } + } // dns is only enabled for the bridge driver if network.Driver == nettypes.BridgeNetworkDriver { @@ -194,7 +227,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { var err error subnet, err := nettypes.ParseCIDR(conf.Subnet) if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "failed to parse subnet")) + utils.InternalServerError(w, fmt.Errorf("failed to parse subnet: %w", err)) return } s.Subnet = subnet @@ -202,7 +235,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { if len(conf.Gateway) > 0 { gw := net.ParseIP(conf.Gateway) if gw == nil { - utils.InternalServerError(w, errors.Errorf("failed to parse gateway ip %s", conf.Gateway)) + utils.InternalServerError(w, fmt.Errorf("failed to parse gateway ip %s", conf.Gateway)) return } s.Gateway = gw @@ -210,17 +243,17 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { if len(conf.IPRange) > 0 { _, net, err := net.ParseCIDR(conf.IPRange) if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "failed to parse ip range")) + utils.InternalServerError(w, fmt.Errorf("failed to parse ip range: %w", err)) return } startIP, err := netutil.FirstIPInSubnet(net) if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "failed to get first ip in range")) + utils.InternalServerError(w, fmt.Errorf("failed to get first ip in range: %w", err)) return } lastIP, err := netutil.LastIPInSubnet(net) if err != nil { - utils.InternalServerError(w, errors.Wrap(err, "failed to get last ip in range")) + utils.InternalServerError(w, fmt.Errorf("failed to get last ip in range: %w", err)) return } s.LeaseRange = &nettypes.LeaseRange{ @@ -242,9 +275,10 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { body := struct { ID string `json:"Id"` - Warning string + Warning string `json:"Warning"` }{ - ID: newNetwork.ID, + ID: newNetwork.ID, + Warning: responseWarning, } utils.WriteResponse(w, http.StatusCreated, body) } @@ -262,7 +296,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -278,12 +312,12 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { return } if len(reports) == 0 { - utils.Error(w, http.StatusInternalServerError, errors.Errorf("internal error")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("internal error")) return } report := reports[0] if report.Err != nil { - if errors.Cause(report.Err) == define.ErrNoSuchNetwork { + if errors.Is(report.Err, define.ErrNoSuchNetwork) { utils.Error(w, http.StatusNotFound, define.ErrNoSuchNetwork) return } @@ -300,7 +334,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { var netConnect types.NetworkConnect if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } @@ -317,7 +351,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { staticIP := net.ParseIP(netConnect.EndpointConfig.IPAddress) if staticIP == nil { utils.Error(w, http.StatusInternalServerError, - errors.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress)) + fmt.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress)) return } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) @@ -329,7 +363,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv4Address) if staticIP == nil { utils.Error(w, http.StatusInternalServerError, - errors.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address)) + fmt.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address)) return } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) @@ -339,7 +373,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv6Address) if staticIP == nil { utils.Error(w, http.StatusInternalServerError, - errors.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) + fmt.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) return } netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) @@ -350,7 +384,7 @@ func Connect(w http.ResponseWriter, r *http.Request) { staticMac, err := net.ParseMAC(netConnect.EndpointConfig.MacAddress) if err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) + fmt.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) return } netOpts.StaticMAC = nettypes.HardwareAddr(staticMac) @@ -358,11 +392,11 @@ func Connect(w http.ResponseWriter, r *http.Request) { } err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netOpts) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, netConnect.Container, err) return } - if errors.Cause(err) == define.ErrNoSuchNetwork { + if errors.Is(err, define.ErrNoSuchNetwork) { utils.Error(w, http.StatusNotFound, err) return } @@ -378,18 +412,18 @@ func Disconnect(w http.ResponseWriter, r *http.Request) { var netDisconnect types.NetworkDisconnect if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } name := utils.GetName(r) err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.Error(w, http.StatusNotFound, err) return } - if errors.Cause(err) == define.ErrNoSuchNetwork { + if errors.Is(err, define.ErrNoSuchNetwork) { utils.Error(w, http.StatusNotFound, err) return } @@ -404,7 +438,7 @@ func Prune(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } diff --git a/pkg/api/handlers/compat/resize.go b/pkg/api/handlers/compat/resize.go index ce7340f62..a2caf6e35 100644 --- a/pkg/api/handlers/compat/resize.go +++ b/pkg/api/handlers/compat/resize.go @@ -1,17 +1,18 @@ package compat import ( + "errors" "fmt" "net/http" "strings" + "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers/utils" api "github.com/containers/podman/v4/pkg/api/types" "github.com/gorilla/mux" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func ResizeTTY(w http.ResponseWriter, r *http.Request) { @@ -28,11 +29,11 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } - sz := define.TerminalSize{ + sz := resize.TerminalSize{ Width: query.Width, Height: query.Height, } @@ -47,8 +48,8 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { return } if err := ctnr.AttachResize(sz); err != nil { - if errors.Cause(err) != define.ErrCtrStateInvalid { - utils.InternalServerError(w, errors.Wrapf(err, "cannot resize container")) + if !errors.Is(err, define.ErrCtrStateInvalid) { + utils.InternalServerError(w, fmt.Errorf("cannot resize container: %w", err)) } else { utils.Error(w, http.StatusConflict, err) } @@ -65,15 +66,15 @@ func ResizeTTY(w http.ResponseWriter, r *http.Request) { return } if state, err := ctnr.State(); err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "cannot obtain session container state")) + utils.InternalServerError(w, fmt.Errorf("cannot obtain session container state: %w", err)) return } else if state != define.ContainerStateRunning && !query.IgnoreNotRunning { utils.Error(w, http.StatusConflict, fmt.Errorf("container %q in wrong state %q", name, state.String())) return } if err := ctnr.ExecResize(name, sz); err != nil { - if errors.Cause(err) != define.ErrExecSessionStateInvalid || !query.IgnoreNotRunning { - utils.InternalServerError(w, errors.Wrapf(err, "cannot resize session")) + if !errors.Is(err, define.ErrExecSessionStateInvalid) || !query.IgnoreNotRunning { + utils.InternalServerError(w, fmt.Errorf("cannot resize session: %w", err)) return } } diff --git a/pkg/api/handlers/compat/secrets.go b/pkg/api/handlers/compat/secrets.go index 5031bf76b..13b3c4e24 100644 --- a/pkg/api/handlers/compat/secrets.go +++ b/pkg/api/handlers/compat/secrets.go @@ -4,7 +4,10 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" + "fmt" "net/http" + "strings" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/pkg/api/handlers/utils" @@ -12,14 +15,13 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" ) func ListSecrets(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filtersMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } ic := abi.ContainerEngine{Libpod: runtime} @@ -106,11 +108,11 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { }{} if err := json.NewDecoder(r.Body).Decode(&createParams); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } if len(createParams.Labels) > 0 { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(errors.New("bad parameter"), "labels not supported")) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("labels not supported: %w", errors.New("bad parameter"))) return } @@ -121,7 +123,7 @@ func CreateSecret(w http.ResponseWriter, r *http.Request) { ic := abi.ContainerEngine{Libpod: runtime} report, err := ic.SecretCreate(r.Context(), createParams.Name, reader, opts) if err != nil { - if errors.Cause(err).Error() == "secret name in use" { + if strings.Contains(err.Error(), "secret name in use") { utils.Error(w, http.StatusConflict, err) return } diff --git a/pkg/api/handlers/compat/volumes.go b/pkg/api/handlers/compat/volumes.go index ff0a7af02..fe6ae36aa 100644 --- a/pkg/api/handlers/compat/volumes.go +++ b/pkg/api/handlers/compat/volumes.go @@ -3,6 +3,8 @@ package compat import ( "bytes" "encoding/json" + "errors" + "fmt" "net/http" "net/url" "time" @@ -18,7 +20,6 @@ import ( docker_api_types "github.com/docker/docker/api/types" docker_api_types_volume "github.com/docker/docker/api/types/volume" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func ListVolumes(w http.ResponseWriter, r *http.Request) { @@ -27,7 +28,7 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { filtersMap, err := util.PrepareFilters(r) if err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -36,7 +37,7 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { for filter := range *filtersMap { if filter == "opts" { utils.Error(w, http.StatusInternalServerError, - errors.Errorf("unsupported libpod filters passed to docker endpoint")) + fmt.Errorf("unsupported libpod filters passed to docker endpoint")) return } } @@ -86,13 +87,13 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { query := struct{}{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } // decode params from body input := docker_api_types_volume.VolumeCreateBody{} if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } @@ -103,7 +104,7 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { if len(input.Name) != 0 { // See if the volume exists already existingVolume, err = runtime.GetVolume(input.Name) - if err != nil && errors.Cause(err) != define.ErrNoSuchVolume { + if err != nil && !errors.Is(err, define.ErrNoSuchVolume) { utils.InternalServerError(w, err) return } @@ -219,7 +220,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -239,7 +240,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) { if err == nil { // As above, we do not pass `force` from the query parameters here if err := runtime.RemoveVolume(r.Context(), vol, false, query.Timeout); err != nil { - if errors.Cause(err) == define.ErrVolumeBeingUsed { + if errors.Is(err, define.ErrVolumeBeingUsed) { utils.Error(w, http.StatusConflict, err) } else { utils.InternalServerError(w, err) @@ -264,14 +265,14 @@ func PruneVolumes(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } f := (url.Values)(*filterMap) filterFuncs, err := filters.GeneratePruneVolumeFilters(f) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse filters for %s", f.Encode())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse filters for %s: %w", f.Encode(), err)) return } diff --git a/pkg/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 6b5bee403..5d85d4009 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,6 +1,7 @@ package libpod import ( + "errors" "fmt" "io/ioutil" "net/http" @@ -16,7 +17,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/podman/v4/pkg/util" "github.com/gorilla/schema" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -35,7 +35,7 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -45,7 +45,7 @@ func ContainerExists(w http.ResponseWriter, r *http.Request) { report, err := containerEngine.ContainerExists(r.Context(), name, options) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } @@ -75,12 +75,12 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to decode filter parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode filter parameters for %s: %w", r.URL.String(), err)) return } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -115,10 +115,6 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if len(pss) == 0 { - utils.WriteResponse(w, http.StatusOK, "[]") - return - } utils.WriteResponse(w, http.StatusOK, pss) } @@ -131,7 +127,7 @@ func GetContainer(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) @@ -225,7 +221,7 @@ func Checkpoint(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -311,7 +307,7 @@ func Restore(w http.ResponseWriter, r *http.Request) { // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -348,11 +344,11 @@ func Restore(w http.ResponseWriter, r *http.Request) { ir := abi.ImageEngine{Libpod: runtime} report, err := ir.Exists(r.Context(), name) if err != nil { - utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find container or checkpoint image %s", name)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find container or checkpoint image %s: %w", name, err)) return } if !report.Value { - utils.Error(w, http.StatusNotFound, errors.Errorf("failed to find container or checkpoint image %s", name)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find container or checkpoint image %s", name)) return } } @@ -384,7 +380,7 @@ func InitContainer(w http.ResponseWriter, r *http.Request) { return } err = ctr.Init(r.Context(), ctr.PodID() != "") - if errors.Cause(err) == define.ErrCtrStateInvalid { + if errors.Is(err, define.ErrCtrStateInvalid) { utils.Error(w, http.StatusNotModified, err) return } @@ -404,7 +400,7 @@ func ShouldRestart(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) report, err := containerEngine.ShouldRestart(r.Context(), name) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, name, err) return } diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go index d34254fd7..46d722a3d 100644 --- a/pkg/api/handlers/libpod/containers_stats.go +++ b/pkg/api/handlers/libpod/containers_stats.go @@ -66,7 +66,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) { flusher.Flush() } - // Setup JSON encoder for streaming. + // Set up JSON encoder for streaming. coder := json.NewEncoder(w) coder.SetEscapeHTML(true) diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index a8a50ae58..ed1c65f8e 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -2,6 +2,7 @@ package libpod import ( "context" + "errors" "fmt" "io" "io/ioutil" @@ -21,13 +22,14 @@ import ( api "github.com/containers/podman/v4/pkg/api/types" "github.com/containers/podman/v4/pkg/auth" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/domain/entities/reports" "github.com/containers/podman/v4/pkg/domain/infra/abi" + domainUtils "github.com/containers/podman/v4/pkg/domain/utils" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/util" utils2 "github.com/containers/podman/v4/utils" "github.com/containers/storage" "github.com/gorilla/schema" - "github.com/pkg/errors" ) // Commit @@ -48,11 +50,11 @@ func ImageExists(w http.ResponseWriter, r *http.Request) { ir := abi.ImageEngine{Libpod: runtime} report, err := ir.Exists(r.Context(), name) if err != nil { - utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s: %w", name, err)) return } if !report.Value { - utils.Error(w, http.StatusNotFound, errors.Errorf("failed to find image %s", name)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s", name)) return } utils.WriteResponse(w, http.StatusNoContent, "") @@ -68,18 +70,18 @@ func ImageTree(w http.ResponseWriter, r *http.Request) { WhatRequires: false, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } ir := abi.ImageEngine{Libpod: runtime} options := entities.ImageTreeOptions{WhatRequires: query.WhatRequires} report, err := ir.Tree(r.Context(), name, options) if err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { - utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) + if errors.Is(err, storage.ErrImageUnknown) { + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s: %w", name, err)) return } - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to generate image tree for %s", name)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to generate image tree for %s: %w", name, err)) return } utils.WriteResponse(w, http.StatusOK, report) @@ -89,13 +91,13 @@ func GetImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) newImage, err := utils.GetImage(r, name) if err != nil { - utils.Error(w, http.StatusNotFound, errors.Wrapf(err, "failed to find image %s", name)) + utils.Error(w, http.StatusNotFound, fmt.Errorf("failed to find image %s: %w", name, err)) return } options := &libimage.InspectOptions{WithParent: true, WithSize: true} inspect, err := newImage.Inspect(r.Context(), options) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed in inspect image %s", inspect.ID)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed in inspect image %s: %w", inspect.ID, err)) return } utils.WriteResponse(w, http.StatusOK, inspect) @@ -115,15 +117,13 @@ func PruneImages(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { utils.Error(w, http.StatusInternalServerError, - errors. - Wrapf(err, "failed to decode filter parameters for %s", r.URL.String())) + fmt.Errorf("failed to decode filter parameters for %s: %w", r.URL.String(), err)) return } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors. - Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -172,7 +172,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusBadRequest, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } name := utils.GetName(r) @@ -186,23 +186,23 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { case define.OCIArchive, define.V2s2Archive: tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } output = tmpfile.Name() if err := tmpfile.Close(); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to close tempfile: %w", err)) return } case define.OCIManifestDir, define.V2s2ManifestDir: tmpdir, err := ioutil.TempDir("", "save") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempdir")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempdir: %w", err)) return } output = tmpdir default: - utils.Error(w, http.StatusInternalServerError, errors.Errorf("unknown format %q", query.Format)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unknown format %q", query.Format)) return } @@ -231,7 +231,7 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { } rdr, err := os.Open(output) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to read the exported tarfile: %w", err)) return } defer rdr.Close() @@ -252,20 +252,20 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } // References are mandatory! if len(query.References) == 0 { - utils.Error(w, http.StatusBadRequest, errors.New("No references")) + utils.Error(w, http.StatusBadRequest, errors.New("no references")) return } // Format is mandatory! Currently, we only support multi-image docker // archives. if len(query.References) > 1 && query.Format != define.V2s2Archive { - utils.Error(w, http.StatusInternalServerError, errors.Errorf("multi-image archives must use format of %s", define.V2s2Archive)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("multi-image archives must use format of %s", define.V2s2Archive)) return } @@ -283,23 +283,23 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { case define.V2s2Archive, define.OCIArchive: tmpfile, err := ioutil.TempFile("", "api.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } output = tmpfile.Name() if err := tmpfile.Close(); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to close tempfile: %w", err)) return } case define.OCIManifestDir, define.V2s2ManifestDir: tmpdir, err := ioutil.TempDir("", "save") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tmpdir")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tmpdir: %w", err)) return } output = tmpdir default: - utils.Error(w, http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unsupported format %q", query.Format)) return } defer os.RemoveAll(output) @@ -321,7 +321,7 @@ func ExportImages(w http.ResponseWriter, r *http.Request) { rdr, err := os.Open(output) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to read the exported tarfile: %w", err)) return } defer rdr.Close() @@ -333,7 +333,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { tmpfile, err := ioutil.TempFile("", "libpod-images-load.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } defer os.Remove(tmpfile.Name()) @@ -342,7 +342,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { tmpfile.Close() if err != nil && err != io.EOF { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to write archive to temporary file: %w", err)) return } @@ -351,7 +351,7 @@ func ImagesLoad(w http.ResponseWriter, r *http.Request) { loadOptions := entities.ImageLoadOptions{Input: tmpfile.Name()} loadReport, err := imageEngine.Load(r.Context(), loadOptions) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to load image")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to load image: %w", err)) return } utils.WriteResponse(w, http.StatusOK, loadReport) @@ -373,7 +373,7 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -382,14 +382,14 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { if len(query.URL) == 0 { tmpfile, err := ioutil.TempFile("", "libpod-images-import.tar") if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to create tempfile: %w", err)) return } defer os.Remove(tmpfile.Name()) defer tmpfile.Close() if _, err := io.Copy(tmpfile, r.Body); err != nil && err != io.EOF { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to write archive to temporary file")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to write archive to temporary file: %w", err)) return } @@ -409,7 +409,7 @@ func ImagesImport(w http.ResponseWriter, r *http.Request) { } report, err := imageEngine.Import(r.Context(), importOptions) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "unable to import tarball")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("unable to import tarball: %w", err)) return } @@ -431,7 +431,7 @@ func PushImage(w http.ResponseWriter, r *http.Request) { // This is where you can override the golang default value for one of fields } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -477,7 +477,7 @@ func PushImage(w http.ResponseWriter, r *http.Request) { imageEngine := abi.ImageEngine{Libpod: runtime} if err := imageEngine.Push(context.Background(), source, destination, options); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "error pushing image %q", destination)) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("error pushing image %q: %w", destination, err)) return } @@ -507,12 +507,12 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } rtc, err := runtime.GetConfig() if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to get runtime config")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to get runtime config: %w", err)) return } sc := runtime.SystemContext() @@ -530,7 +530,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { case "docker": mimeType = manifest.DockerV2Schema2MediaType default: - utils.InternalServerError(w, errors.Errorf("unrecognized image format %q", query.Format)) + utils.InternalServerError(w, fmt.Errorf("unrecognized image format %q", query.Format)) return } options.CommitOptions = buildah.CommitOptions{ @@ -559,7 +559,7 @@ func CommitContainer(w http.ResponseWriter, r *http.Request) { } commitImage, err := ctr.Commit(r.Context(), destImage, options) if err != nil && !strings.Contains(err.Error(), "is not running") { - utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "CommitFailure")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("CommitFailure: %w", err)) return } utils.WriteResponse(w, http.StatusOK, entities.IDResponse{ID: commitImage.ID()}) @@ -598,8 +598,8 @@ func UntagImage(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) if err := imageEngine.Untag(r.Context(), name, tags, opts); err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { - utils.ImageNotFound(w, name, errors.Wrapf(err, "failed to find image %s", name)) + if errors.Is(err, storage.ErrImageUnknown) { + utils.ImageNotFound(w, name, fmt.Errorf("failed to find image %s: %w", name, err)) } else { utils.Error(w, http.StatusInternalServerError, err) } @@ -613,18 +613,19 @@ func ImagesBatchRemove(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - All bool `schema:"all"` - Force bool `schema:"force"` - Ignore bool `schema:"ignore"` - Images []string `schema:"images"` + All bool `schema:"all"` + Force bool `schema:"force"` + Ignore bool `schema:"ignore"` + LookupManifest bool `schema:"lookupManifest"` + Images []string `schema:"images"` }{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } - opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore} + opts := entities.ImageRemoveOptions{All: query.All, Force: query.Force, Ignore: query.Ignore, LookupManifest: query.LookupManifest} imageEngine := abi.ImageEngine{Libpod: runtime} rmReport, rmErrors := imageEngine.Remove(r.Context(), query.Images, opts) strErrs := errorhandling.ErrorsToStrings(rmErrors) @@ -637,17 +638,18 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - Force bool `schema:"force"` + Force bool `schema:"force"` + LookupManifest bool `schema:"lookupManifest"` }{ Force: false, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } - opts := entities.ImageRemoveOptions{Force: query.Force} + opts := entities.ImageRemoveOptions{Force: query.Force, LookupManifest: query.LookupManifest} imageEngine := abi.ImageEngine{Libpod: runtime} rmReport, rmErrors := imageEngine.Remove(r.Context(), []string{utils.GetName(r)}, opts) @@ -670,3 +672,32 @@ func ImagesRemove(w http.ResponseWriter, r *http.Request) { utils.Error(w, http.StatusInternalServerError, errorhandling.JoinErrors(rmErrors)) } } + +func ImageScp(w http.ResponseWriter, r *http.Request) { + decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) + query := struct { + Destination string `schema:"destination"` + Quiet bool `schema:"quiet"` + }{ + // This is where you can override the golang default value for one of fields + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) + return + } + + sourceArg := utils.GetName(r) + + rep, source, dest, _, err := domainUtils.ExecuteTransfer(sourceArg, query.Destination, []string{}, query.Quiet) + if err != nil { + utils.Error(w, http.StatusInternalServerError, err) + return + } + + if source != nil || dest != nil { + utils.Error(w, http.StatusBadRequest, fmt.Errorf("cannot use the user transfer function on the remote client: %w", define.ErrInvalidArg)) + return + } + + utils.WriteResponse(w, http.StatusOK, &reports.ScpReport{Id: rep.Names[0]}) +} diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 16f499d4c..7169a6d44 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -2,8 +2,11 @@ package libpod import ( "encoding/json" + "fmt" "net/http" + "errors" + "github.com/containers/common/libnetwork/types" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" @@ -13,7 +16,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/podman/v4/pkg/util" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func CreateNetwork(w http.ResponseWriter, r *http.Request) { @@ -25,7 +27,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) network := types.Network{} if err := json.NewDecoder(r.Body).Decode(&network); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to decode request JSON payload")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode request JSON payload: %w", err)) return } @@ -52,7 +54,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -83,7 +85,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } name := utils.GetName(r) @@ -99,7 +101,7 @@ func RemoveNetwork(w http.ResponseWriter, r *http.Request) { } if reports[0].Err != nil { // If the network cannot be found, we return a 404. - if errors.Cause(reports[0].Err) == define.ErrNoSuchNetwork { + if errors.Is(reports[0].Err, define.ErrNoSuchNetwork) { utils.Error(w, http.StatusNotFound, reports[0].Err) return } @@ -142,18 +144,18 @@ func Connect(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) var netConnect entities.NetworkConnectOptions if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "failed to decode request JSON payload")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("failed to decode request JSON payload: %w", err)) return } name := utils.GetName(r) err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.PerNetworkOptions) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { utils.ContainerNotFound(w, netConnect.Container, err) return } - if errors.Cause(err) == define.ErrNoSuchNetwork { + if errors.Is(err, define.ErrNoSuchNetwork) { utils.Error(w, http.StatusNotFound, err) return } diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 5b92358fa..92fd94390 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -2,6 +2,7 @@ package libpod import ( "encoding/json" + "errors" "fmt" "net/http" "strings" @@ -19,7 +20,6 @@ import ( "github.com/containers/podman/v4/pkg/specgenutil" "github.com/containers/podman/v4/pkg/util" "github.com/gorilla/schema" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -33,11 +33,11 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { ) psg := specgen.PodSpecGenerator{InfraContainerSpec: &specgen.SpecGenerator{}} if err := json.NewDecoder(r.Body).Decode(&psg); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, failedToDecodeSpecgen)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("%v: %w", failedToDecodeSpecgen, err)) return } if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, failedToDecodeSpecgen)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("%v: %w", failedToDecodeSpecgen, err)) return } if !psg.NoInfra { @@ -51,17 +51,17 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { } err = specgenutil.FillOutSpecGen(psg.InfraContainerSpec, &infraOptions, []string{}) // necessary for default values in many cases (userns, idmappings) if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "error filling out specgen")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("error filling out specgen: %w", err)) return } out, err := json.Marshal(psg) // marshal our spec so the matching options can be unmarshaled into infra if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, failedToDecodeSpecgen)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("%v: %w", failedToDecodeSpecgen, err)) return } err = json.Unmarshal(out, psg.InfraContainerSpec) // unmarhal matching options if err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, failedToDecodeSpecgen)) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("%v: %w", failedToDecodeSpecgen, err)) return } // a few extra that do not have the same json tags @@ -75,10 +75,10 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { pod, err := generate.MakePod(&podSpecComplete, runtime) if err != nil { httpCode := http.StatusInternalServerError - if errors.Cause(err) == define.ErrPodExists { + if errors.Is(err, define.ErrPodExists) { httpCode = http.StatusConflict } - utils.Error(w, httpCode, errors.Wrap(err, "failed to make pod")) + utils.Error(w, httpCode, fmt.Errorf("failed to make pod: %w", err)) return } utils.WriteResponse(w, http.StatusCreated, entities.IDResponse{ID: pod.ID()}) @@ -89,7 +89,7 @@ func Pods(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -139,7 +139,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } name := utils.GetName(r) @@ -164,7 +164,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) { } else { responses, stopError = pod.Stop(r.Context(), false) } - if stopError != nil && errors.Cause(stopError) != define.ErrPodPartialFail { + if stopError != nil && !errors.Is(stopError, define.ErrPodPartialFail) { utils.Error(w, http.StatusInternalServerError, err) return } @@ -178,7 +178,7 @@ func PodStop(w http.ResponseWriter, r *http.Request) { report := entities.PodStopReport{Id: pod.ID()} for id, err := range responses { - report.Errs = append(report.Errs, errors.Wrapf(err, "error stopping container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error stopping container %s: %w", id, err)) } code := http.StatusOK @@ -207,14 +207,14 @@ func PodStart(w http.ResponseWriter, r *http.Request) { } responses, err := pod.Start(r.Context()) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { utils.Error(w, http.StatusConflict, err) return } report := entities.PodStartReport{Id: pod.ID()} for id, err := range responses { - report.Errs = append(report.Errs, errors.Wrapf(err, "error starting container "+id)) + report.Errs = append(report.Errs, fmt.Errorf("%v: %w", "error starting container "+id, err)) } code := http.StatusOK @@ -237,7 +237,7 @@ func PodDelete(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } name := utils.GetName(r) @@ -263,14 +263,14 @@ func PodRestart(w http.ResponseWriter, r *http.Request) { return } responses, err := pod.Restart(r.Context()) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { utils.Error(w, http.StatusInternalServerError, err) return } report := entities.PodRestartReport{Id: pod.ID()} for id, err := range responses { - report.Errs = append(report.Errs, errors.Wrapf(err, "error restarting container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error restarting container %s: %w", id, err)) } code := http.StatusOK @@ -314,14 +314,14 @@ func PodPause(w http.ResponseWriter, r *http.Request) { return } responses, err := pod.Pause(r.Context()) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { utils.Error(w, http.StatusInternalServerError, err) return } report := entities.PodPauseReport{Id: pod.ID()} for id, v := range responses { - report.Errs = append(report.Errs, errors.Wrapf(v, "error pausing container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error pausing container %s: %w", id, v)) } code := http.StatusOK @@ -340,14 +340,14 @@ func PodUnpause(w http.ResponseWriter, r *http.Request) { return } responses, err := pod.Unpause(r.Context()) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { utils.Error(w, http.StatusInternalServerError, err) return } report := entities.PodUnpauseReport{Id: pod.ID()} for id, v := range responses { - report.Errs = append(report.Errs, errors.Wrapf(v, "error unpausing container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error unpausing container %s: %w", id, v)) } code := http.StatusOK @@ -374,7 +374,7 @@ func PodTop(w http.ResponseWriter, r *http.Request) { PsArgs: psArgs, } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -456,7 +456,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { // override any golang type defaults } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } if _, found := r.URL.Query()["signal"]; found { @@ -465,7 +465,7 @@ func PodKill(w http.ResponseWriter, r *http.Request) { sig, err := util.ParseSignal(signal) if err != nil { - utils.InternalServerError(w, errors.Wrapf(err, "unable to parse signal value")) + utils.InternalServerError(w, fmt.Errorf("unable to parse signal value: %w", err)) return } name := utils.GetName(r) @@ -488,12 +488,12 @@ func PodKill(w http.ResponseWriter, r *http.Request) { } } if !hasRunning { - utils.Error(w, http.StatusConflict, errors.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) + utils.Error(w, http.StatusConflict, fmt.Errorf("cannot kill a pod with no running containers: %s", pod.ID())) return } responses, err := pod.Kill(r.Context(), uint(sig)) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { utils.Error(w, http.StatusInternalServerError, err) return } @@ -534,7 +534,7 @@ func PodStats(w http.ResponseWriter, r *http.Request) { // default would go here } if err := decoder.Decode(&query, r.URL.Query()); err != nil { - utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -549,13 +549,12 @@ func PodStats(w http.ResponseWriter, r *http.Request) { reports, err := containerEngine.PodStats(r.Context(), query.NamesOrIDs, options) // Error checks as documented in swagger. - switch errors.Cause(err) { - case define.ErrNoSuchPod: - utils.Error(w, http.StatusNotFound, err) - return - case nil: - // Nothing to do. - default: + if err != nil { + if errors.Is(err, define.ErrNoSuchPod) { + utils.Error(w, http.StatusNotFound, err) + return + } + utils.InternalServerError(w, err) return } diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index e792dea35..5eac76f5b 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -2,9 +2,12 @@ package libpod import ( "encoding/json" + "fmt" "net/http" "net/url" + "errors" + "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/api/handlers/utils" @@ -16,7 +19,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/infra/abi/parse" "github.com/containers/podman/v4/pkg/util" "github.com/gorilla/schema" - "github.com/pkg/errors" ) func CreateVolume(w http.ResponseWriter, r *http.Request) { @@ -30,14 +32,14 @@ func CreateVolume(w http.ResponseWriter, r *http.Request) { } if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } input := entities.VolumeCreateOptions{} // decode params from body if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("Decode(): %w", err)) return } @@ -108,7 +110,7 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { filterMap, err := util.PrepareFilters(r) if err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -181,7 +183,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&query, r.URL.Query()); err != nil { utils.Error(w, http.StatusInternalServerError, - errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } name := utils.GetName(r) @@ -191,7 +193,7 @@ func RemoveVolume(w http.ResponseWriter, r *http.Request) { return } if err := runtime.RemoveVolume(r.Context(), vol, query.Force, query.Timeout); err != nil { - if errors.Cause(err) == define.ErrVolumeBeingUsed { + if errors.Is(err, define.ErrVolumeBeingUsed) { utils.Error(w, http.StatusConflict, err) return } diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go index 55fc1a77f..93a508b39 100644 --- a/pkg/api/handlers/swagger/responses.go +++ b/pkg/api/handlers/swagger/responses.go @@ -41,6 +41,13 @@ type imagesLoadResponseLibpod struct { Body entities.ImageLoadReport } +// Image Scp +// swagger:response +type imagesScpResponseLibpod struct { + // in:body + Body reports.ScpReport +} + // Image Import // swagger:response type imagesImportResponseLibpod struct { diff --git a/pkg/api/handlers/utils/containers.go b/pkg/api/handlers/utils/containers.go index 8588b49ba..80f8522fd 100644 --- a/pkg/api/handlers/utils/containers.go +++ b/pkg/api/handlers/utils/containers.go @@ -2,6 +2,7 @@ package utils import ( "context" + "errors" "fmt" "net/http" "strconv" @@ -19,7 +20,6 @@ import ( "github.com/containers/podman/v4/libpod" "github.com/gorilla/schema" - "github.com/pkg/errors" ) type waitQueryDocker struct { @@ -39,7 +39,7 @@ func WaitContainerDocker(w http.ResponseWriter, r *http.Request) { decoder := ctx.Value(api.DecoderKey).(*schema.Decoder) if err = decoder.Decode(&query, r.URL.Query()); err != nil { - Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -106,7 +106,7 @@ func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) { decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := waitQueryLibpod{} if err := decoder.Decode(&query, r.URL.Query()); err != nil { - Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) return } @@ -130,7 +130,7 @@ func WaitContainerLibpod(w http.ResponseWriter, r *http.Request) { exitCode, err := waitFn(conditions...) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Is(err, define.ErrNoSuchCtr) { ContainerNotFound(w, name, err) return } @@ -191,14 +191,13 @@ func waitDockerCondition(ctx context.Context, containerName string, interval tim var notRunningStates = []define.ContainerStatus{ define.ContainerStateCreated, define.ContainerStateRemoving, - define.ContainerStateStopped, define.ContainerStateExited, define.ContainerStateConfigured, } func waitRemoved(ctrWait containerWaitFn) (int32, error) { code, err := ctrWait(define.ContainerStateUnknown) - if err != nil && errors.Cause(err) == define.ErrNoSuchCtr { + if err != nil && errors.Is(err, define.ErrNoSuchCtr) { return code, nil } return code, err diff --git a/pkg/api/handlers/utils/errors.go b/pkg/api/handlers/utils/errors.go index bf60b2c84..ab1b6f227 100644 --- a/pkg/api/handlers/utils/errors.go +++ b/pkg/api/handlers/utils/errors.go @@ -1,17 +1,18 @@ package utils import ( + "errors" + "fmt" "net/http" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/storage" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" ) var ( - ErrLinkNotSupport = errors.New("Link is not supported") + ErrLinkNotSupport = errors.New("link is not supported") ) // TODO: document the exported functions in this file and make them more @@ -25,7 +26,7 @@ func Error(w http.ResponseWriter, code int, err error) { // Log detailed message of what happened to machine running podman service log.Infof("Request Failed(%s): %s", http.StatusText(code), err.Error()) em := errorhandling.ErrorModel{ - Because: (errors.Cause(err)).Error(), + Because: errorhandling.Cause(err).Error(), Message: err.Error(), ResponseCode: code, } @@ -33,51 +34,50 @@ func Error(w http.ResponseWriter, code int, err error) { } func VolumeNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchVolume { + if !errors.Is(err, define.ErrNoSuchVolume) { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) } func ContainerNotFound(w http.ResponseWriter, name string, err error) { - switch errors.Cause(err) { - case define.ErrNoSuchCtr, define.ErrCtrExists: + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrExists) { Error(w, http.StatusNotFound, err) - default: + } else { InternalServerError(w, err) } } func ImageNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != storage.ErrImageUnknown { + if !errors.Is(err, storage.ErrImageUnknown) { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) } func NetworkNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchNetwork { + if !errors.Is(err, define.ErrNoSuchNetwork) { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) } func PodNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchPod { + if !errors.Is(err, define.ErrNoSuchPod) { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) } func SessionNotFound(w http.ResponseWriter, name string, err error) { - if errors.Cause(err) != define.ErrNoSuchExecSession { + if !errors.Is(err, define.ErrNoSuchExecSession) { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) } func SecretNotFound(w http.ResponseWriter, nameOrID string, err error) { - if errors.Cause(err).Error() != "no such secret" { + if errorhandling.Cause(err).Error() != "no such secret" { InternalServerError(w, err) } Error(w, http.StatusNotFound, err) @@ -92,7 +92,7 @@ func InternalServerError(w http.ResponseWriter, err error) { } func BadRequest(w http.ResponseWriter, key string, value string, err error) { - e := errors.Wrapf(err, "failed to parse query parameter '%s': %q", key, value) + e := fmt.Errorf("failed to parse query parameter '%s': %q: %w", key, value, err) Error(w, http.StatusBadRequest, e) } diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 77f6dcf1d..357fa2990 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "fmt" "net/http" "strings" @@ -15,7 +16,6 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage" "github.com/docker/distribution/reference" - "github.com/pkg/errors" ) // NormalizeToDockerHub normalizes the specified nameOrID to Docker Hub if the @@ -32,7 +32,7 @@ func NormalizeToDockerHub(r *http.Request, nameOrID string) (string, error) { // 'busybox' -> 'registry.com/busybox'. img, candidate, err := runtime.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { - if errors.Cause(err) != storage.ErrImageUnknown { + if !errors.Is(err, storage.ErrImageUnknown) { return "", fmt.Errorf("normalizing name for compat API: %v", err) } // If the image could not be resolved locally, set the @@ -73,7 +73,7 @@ func IsRegistryReference(name string) error { if imageRef.Transport().Name() == docker.Transport.Name() { return nil } - return errors.Errorf("unsupported transport %s in %q: only docker transport is supported", imageRef.Transport().Name(), name) + return fmt.Errorf("unsupported transport %s in %q: only docker transport is supported", imageRef.Transport().Name(), name) } // ParseStorageReference parses the specified image name to a @@ -83,12 +83,12 @@ func ParseStorageReference(name string) (types.ImageReference, error) { storagePrefix := storageTransport.Transport.Name() imageRef, err := alltransports.ParseImageName(name) if err == nil && imageRef.Transport().Name() != docker.Transport.Name() { - return nil, errors.Errorf("reference %q must be a storage reference", name) + return nil, fmt.Errorf("reference %q must be a storage reference", name) } else if err != nil { origErr := err imageRef, err = alltransports.ParseImageName(fmt.Sprintf("%s:%s", storagePrefix, name)) if err != nil { - return nil, errors.Wrapf(origErr, "reference %q must be a storage reference", name) + return nil, fmt.Errorf("reference %q must be a storage reference: %w", name, origErr) } } return imageRef, nil diff --git a/pkg/api/server/listener_api.go b/pkg/api/server/listener_api.go index 2d02df7dc..aaaf6688e 100644 --- a/pkg/api/server/listener_api.go +++ b/pkg/api/server/listener_api.go @@ -11,7 +11,7 @@ import ( // ListenUnix follows stdlib net.Listen() API, providing a unix listener for given path // ListenUnix will delete and create files/directories as needed func ListenUnix(network string, path string) (net.Listener, error) { - // setup custom listener for API server + // set up custom listener for API server err := os.MkdirAll(filepath.Dir(path), 0770) if err != nil { return nil, errors.Wrapf(err, "api.ListenUnix() failed to create %s", filepath.Dir(path)) diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 1617a5dd7..a2f46cb35 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -948,6 +948,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: ignore // description: Ignore if a specified image does not exist and do not throw an error. // type: boolean + // - in: query + // name: lookupManifest + // description: Resolves to manifest list instead of image. + // type: boolean // produces: // - application/json // responses: @@ -1615,5 +1619,39 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/internalError" r.Handle(VersionedPath("/libpod/build"), s.APIHandler(compat.BuildImage)).Methods(http.MethodPost) + + // swagger:operation POST /libpod/images/scp/{name} libpod ImageScpLibpod + // --- + // tags: + // - images + // summary: Copy an image from one host to another + // description: Copy an image from one host to another + // parameters: + // - in: path + // name: name + // required: true + // description: source connection/image + // type: string + // - in: query + // name: destination + // required: false + // description: dest connection/image + // type: string + // - in: query + // name: quiet + // required: false + // description: quiet output + // type: boolean + // default: false + // produces: + // - application/json + // responses: + // 200: + // $ref: "#/responses/imagesScpResponseLibpod" + // 400: + // $ref: "#/responses/badParamError" + // 500: + // $ref: '#/responses/internalError' + r.Handle(VersionedPath("/libpod/images/scp/{name:.*}"), s.APIHandler(libpod.ImageScp)).Methods(http.MethodPost) return nil } diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index c21834e35..6b3576f31 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -95,7 +95,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string) return nil, errors.Wrapf(err, "Value of CONTAINER_HOST is not a valid url: %s", uri) } - // Now we setup the http Client to use the connection above + // Now we set up the http Client to use the connection above var connection Connection switch _url.Scheme { case "ssh": @@ -164,7 +164,7 @@ func pingNewConnection(ctx context.Context) (*semver.Version, error) { if response.StatusCode == http.StatusOK { versionHdr := response.Header.Get("Libpod-API-Version") if versionHdr == "" { - logrus.Info("Service did not provide Libpod-API-Version Header") + logrus.Warn("Service did not provide Libpod-API-Version Header") return new(semver.Version), nil } versionSrv, err := semver.ParseTolerant(versionHdr) diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index d84b47052..2bfccdd3b 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -14,9 +14,9 @@ import ( "strconv" "time" + "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/bindings" - "github.com/containers/podman/v4/utils" "github.com/moby/term" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -54,8 +54,6 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri stderr = (io.Writer)(nil) } - logrus.Infof("Going to attach to container %q", nameOrID) - conn, err := bindings.GetClient(ctx) if err != nil { return err @@ -161,7 +159,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri go func() { logrus.Debugf("Copying STDIN to socket") - _, err := utils.CopyDetachable(socket, stdin, detachKeysInBytes) + _, err := util.CopyDetachable(socket, stdin, detachKeysInBytes) if err != nil && err != define.ErrDetach { logrus.Errorf("Failed to write input to service: %v", err) } @@ -357,7 +355,7 @@ func attachHandleResize(ctx, winCtx context.Context, winChange chan os.Signal, i resizeErr = ResizeContainerTTY(ctx, id, new(ResizeTTYOptions).WithHeight(h).WithWidth(w)) } if resizeErr != nil { - logrus.Infof("Failed to resize TTY: %v", resizeErr) + logrus.Debugf("Failed to resize TTY: %v", resizeErr) } } @@ -499,7 +497,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if options.GetAttachInput() { go func() { logrus.Debugf("Copying STDIN to socket") - _, err := utils.CopyDetachable(socket, options.InputStream, []byte{}) + _, err := util.CopyDetachable(socket, options.InputStream, []byte{}) if err != nil { logrus.Errorf("Failed to write input to service: %v", err) } @@ -520,7 +518,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar return fmt.Errorf("exec session %s has a terminal and must have STDOUT enabled", sessionID) } // If not multiplex'ed, read from server and write to stdout - _, err := utils.CopyDetachable(options.GetOutputStream(), socket, []byte{}) + _, err := util.CopyDetachable(options.GetOutputStream(), socket, []byte{}) if err != nil { return err } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index 2d3422411..ea01bc7d9 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -13,7 +13,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities/reports" "github.com/pkg/errors" - "github.com/sirupsen/logrus" ) var ( @@ -201,7 +200,6 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) error { if options == nil { options = new(StartOptions) } - logrus.Infof("Going to start container %q", nameOrID) conn, err := bindings.GetClient(ctx) if err != nil { return err diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 32372019b..57c8bd597 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -346,3 +346,23 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie return results, nil } + +func Scp(ctx context.Context, source, destination *string, options ScpOptions) (reports.ScpReport, error) { + rep := reports.ScpReport{} + + conn, err := bindings.GetClient(ctx) + if err != nil { + return rep, err + } + params, err := options.ToParams() + if err != nil { + return rep, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodPost, fmt.Sprintf("/images/scp/%s", *source), params, nil) + if err != nil { + return rep, err + } + defer response.Body.Close() + + return rep, response.Process(&rep) +} diff --git a/pkg/bindings/images/types.go b/pkg/bindings/images/types.go index 16dbad380..3728ae5c0 100644 --- a/pkg/bindings/images/types.go +++ b/pkg/bindings/images/types.go @@ -13,6 +13,8 @@ type RemoveOptions struct { Force *bool // Ignore if a specified image does not exist and do not throw an error. Ignore *bool + // Confirms if given name is a manifest list and removes it, otherwise returns error. + LookupManifest *bool } //go:generate go run ../generator/generator.go DiffOptions @@ -188,3 +190,8 @@ type BuildOptions struct { // ExistsOptions are optional options for checking if an image exists type ExistsOptions struct { } + +type ScpOptions struct { + Quiet *bool + Destination *string +} diff --git a/pkg/bindings/images/types_remove_options.go b/pkg/bindings/images/types_remove_options.go index 613a33183..559ebcfd5 100644 --- a/pkg/bindings/images/types_remove_options.go +++ b/pkg/bindings/images/types_remove_options.go @@ -61,3 +61,18 @@ func (o *RemoveOptions) GetIgnore() bool { } return *o.Ignore } + +// WithLookupManifest set field LookupManifest to given value +func (o *RemoveOptions) WithLookupManifest(value bool) *RemoveOptions { + o.LookupManifest = &value + return o +} + +// GetLookupManifest returns value of field LookupManifest +func (o *RemoveOptions) GetLookupManifest() bool { + if o.LookupManifest == nil { + var z bool + return z + } + return *o.LookupManifest +} diff --git a/pkg/bindings/images/types_scp_options.go b/pkg/bindings/images/types_scp_options.go new file mode 100644 index 000000000..5a1178cb1 --- /dev/null +++ b/pkg/bindings/images/types_scp_options.go @@ -0,0 +1,12 @@ +package images + +import ( + "net/url" + + "github.com/containers/podman/v4/pkg/bindings/internal/util" +) + +// ToParams formats struct fields to be passed to API service +func (o *ScpOptions) ToParams() (url.Values, error) { + return util.ToParams(o) +} diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index feff5d6e8..a68dd5a4e 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -117,6 +117,26 @@ func Remove(ctx context.Context, name, digest string, _ *RemoveOptions) (string, return Modify(ctx, name, []string{digest}, optionsv4) } +// Delete removes specified manifest from local storage. +func Delete(ctx context.Context, name string) (*entities.ManifestRemoveReport, error) { + var report entities.ManifestRemoveReport + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + response, err := conn.DoRequest(ctx, nil, http.MethodDelete, "/manifests/%s", nil, nil, name) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if err := response.Process(&report); err != nil { + return nil, err + } + + return &report, errorhandling.JoinErrors(errorhandling.StringsToErrors(report.Errors)) +} + // Push takes a manifest list and pushes to a destination. If the destination is not specified, // the name will be used instead. If the optional all boolean is specified, all images specified // in the list will be pushed as well. @@ -211,7 +231,7 @@ func Modify(ctx context.Context, name string, images []string, options *ModifyOp err = errorhandling.JoinErrors(report.Errors) if err != nil { errModel := errorhandling.ErrorModel{ - Because: (errors.Cause(err)).Error(), + Because: errorhandling.Cause(err).Error(), Message: err.Error(), ResponseCode: response.StatusCode, } diff --git a/pkg/bindings/manifests/types.go b/pkg/bindings/manifests/types.go index d0b0b2e71..e23ef798d 100644 --- a/pkg/bindings/manifests/types.go +++ b/pkg/bindings/manifests/types.go @@ -44,16 +44,18 @@ type RemoveOptions struct { type ModifyOptions struct { // Operation values are "update", "remove" and "annotate". This allows the service to // efficiently perform each update on a manifest list. - Operation *string - All *bool // All when true, operate on all images in a manifest list that may be included in Images - Annotations map[string]string // Annotations to add to manifest list - Arch *string // Arch overrides the architecture for the image - Features []string // Feature list for the image - Images []string // Images is an optional list of images to add/remove to/from manifest list depending on operation - OS *string // OS overrides the operating system for the image - OSFeatures []string // OS features for the image - OSVersion *string // OSVersion overrides the operating system for the image - Variant *string // Variant overrides the operating system variant for the image + Operation *string + All *bool // All when true, operate on all images in a manifest list that may be included in Images + Annotations map[string]string // Annotations to add to manifest list + Arch *string // Arch overrides the architecture for the image + Features []string // Feature list for the image + Images []string // Images is an optional list of images to add/remove to/from manifest list depending on operation + OS *string // OS overrides the operating system for the image + // OS features for the image + OSFeatures []string `json:"os_features" schema:"os_features"` + // OSVersion overrides the operating system for the image + OSVersion *string `json:"os_version" schema:"os_version"` + Variant *string // Variant overrides the operating system variant for the image Authfile *string Password *string Username *string diff --git a/pkg/bindings/manifests/types_modify_options.go b/pkg/bindings/manifests/types_modify_options.go index 9d2ed2613..ab00cb2c5 100644 --- a/pkg/bindings/manifests/types_modify_options.go +++ b/pkg/bindings/manifests/types_modify_options.go @@ -122,13 +122,13 @@ func (o *ModifyOptions) GetOS() string { return *o.OS } -// WithOSFeatures set oS features for the image +// WithOSFeatures set field OSFeatures to given value func (o *ModifyOptions) WithOSFeatures(value []string) *ModifyOptions { o.OSFeatures = value return o } -// GetOSFeatures returns value of oS features for the image +// GetOSFeatures returns value of field OSFeatures func (o *ModifyOptions) GetOSFeatures() []string { if o.OSFeatures == nil { var z []string @@ -137,13 +137,13 @@ func (o *ModifyOptions) GetOSFeatures() []string { return o.OSFeatures } -// WithOSVersion set oSVersion overrides the operating system for the image +// WithOSVersion set field OSVersion to given value func (o *ModifyOptions) WithOSVersion(value string) *ModifyOptions { o.OSVersion = &value return o } -// GetOSVersion returns value of oSVersion overrides the operating system for the image +// GetOSVersion returns value of field OSVersion func (o *ModifyOptions) GetOSVersion() string { if o.OSVersion == nil { var z string diff --git a/pkg/bindings/test/manifests_test.go b/pkg/bindings/test/manifests_test.go index e6c93817d..6a34ef5a6 100644 --- a/pkg/bindings/test/manifests_test.go +++ b/pkg/bindings/test/manifests_test.go @@ -62,6 +62,19 @@ var _ = Describe("podman manifest", func() { Expect(len(list.Manifests)).To(BeNumerically("==", 1)) }) + It("delete manifest", func() { + id, err := manifests.Create(bt.conn, "quay.io/libpod/foobar:latest", []string{}, nil) + Expect(err).ToNot(HaveOccurred(), err) + list, err := manifests.Inspect(bt.conn, id, nil) + Expect(err).ToNot(HaveOccurred()) + + Expect(len(list.Manifests)).To(BeZero()) + + removeReport, err := manifests.Delete(bt.conn, "quay.io/libpod/foobar:latest") + Expect(err).ToNot(HaveOccurred()) + Expect(len(removeReport.Deleted)).To(BeNumerically("==", 1)) + }) + It("inspect", func() { _, err := manifests.Inspect(bt.conn, "larry", nil) Expect(err).To(HaveOccurred()) diff --git a/pkg/criu/criu.go b/pkg/criu/criu.go index 6570159d7..0b0bbff5d 100644 --- a/pkg/criu/criu.go +++ b/pkg/criu/criu.go @@ -1,51 +1,8 @@ -//go:build linux -// +build linux - package criu -import ( - "github.com/checkpoint-restore/go-criu/v5" - "github.com/checkpoint-restore/go-criu/v5/rpc" - - "google.golang.org/protobuf/proto" -) - // MinCriuVersion for Podman at least CRIU 3.11 is required const MinCriuVersion = 31100 // PodCriuVersion is the version of CRIU needed for // checkpointing and restoring containers out of and into Pods. const PodCriuVersion = 31600 - -// CheckForCriu uses CRIU's go bindings to check if the CRIU -// binary exists and if it at least the version Podman needs. -func CheckForCriu(version int) bool { - c := criu.MakeCriu() - result, err := c.IsCriuAtLeast(version) - if err != nil { - return false - } - return result -} - -func GetCriuVestion() (int, error) { - c := criu.MakeCriu() - return c.GetCriuVersion() -} - -func MemTrack() bool { - features, err := criu.MakeCriu().FeatureCheck( - &rpc.CriuFeatures{ - MemTrack: proto.Bool(true), - }, - ) - if err != nil { - return false - } - - if features == nil || features.MemTrack == nil { - return false - } - - return *features.MemTrack -} diff --git a/pkg/criu/criu_linux.go b/pkg/criu/criu_linux.go new file mode 100644 index 000000000..c28e23fd7 --- /dev/null +++ b/pkg/criu/criu_linux.go @@ -0,0 +1,44 @@ +//go:build linux +// +build linux + +package criu + +import ( + "github.com/checkpoint-restore/go-criu/v5" + "github.com/checkpoint-restore/go-criu/v5/rpc" + + "google.golang.org/protobuf/proto" +) + +// CheckForCriu uses CRIU's go bindings to check if the CRIU +// binary exists and if it at least the version Podman needs. +func CheckForCriu(version int) bool { + c := criu.MakeCriu() + result, err := c.IsCriuAtLeast(version) + if err != nil { + return false + } + return result +} + +func MemTrack() bool { + features, err := criu.MakeCriu().FeatureCheck( + &rpc.CriuFeatures{ + MemTrack: proto.Bool(true), + }, + ) + if err != nil { + return false + } + + if features == nil || features.MemTrack == nil { + return false + } + + return *features.MemTrack +} + +func GetCriuVersion() (int, error) { + c := criu.MakeCriu() + return c.GetCriuVersion() +} diff --git a/pkg/criu/criu_unsupported.go b/pkg/criu/criu_unsupported.go index 3e3ed9c6c..437482a0e 100644 --- a/pkg/criu/criu_unsupported.go +++ b/pkg/criu/criu_unsupported.go @@ -3,6 +3,14 @@ package criu +func CheckForCriu(version int) bool { + return false +} + func MemTrack() bool { return false } + +func GetCriuVersion() (int, error) { + return MinCriuVersion, nil +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index df42876f6..e4eb808b4 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -104,4 +104,5 @@ type ContainerEngine interface { VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error) VolumeRm(ctx context.Context, namesOrIds []string, opts VolumeRmOptions) ([]*VolumeRmReport, error) VolumeUnmount(ctx context.Context, namesOrIds []string) ([]*VolumeUnmountReport, error) + VolumeReload(ctx context.Context) (*VolumeReloadReport, error) } diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index 5011d82aa..5f76ae50b 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -22,12 +22,12 @@ type ImageEngine interface { Push(ctx context.Context, source string, destination string, opts ImagePushOptions) error Remove(ctx context.Context, images []string, opts ImageRemoveOptions) (*ImageRemoveReport, []error) Save(ctx context.Context, nameOrID string, tags []string, options ImageSaveOptions) error + Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error Search(ctx context.Context, term string, opts ImageSearchOptions) ([]ImageSearchReport, error) SetTrust(ctx context.Context, args []string, options SetTrustOptions) error ShowTrust(ctx context.Context, args []string, options ShowTrustOptions) (*ShowTrustReport, error) Shutdown(ctx context.Context) Tag(ctx context.Context, nameOrID string, tags []string, options ImageTagOptions) error - Transfer(ctx context.Context, source ImageScpOptions, dest ImageScpOptions, parentFlags []string) error Tree(ctx context.Context, nameOrID string, options ImageTreeOptions) (*ImageTreeReport, error) Unmount(ctx context.Context, images []string, options ImageUnmountOptions) ([]*ImageUnmountReport, error) Untag(ctx context.Context, nameOrID string, tags []string, options ImageUntagOptions) error diff --git a/pkg/domain/entities/events.go b/pkg/domain/entities/events.go index d8ba0f1d3..de218b285 100644 --- a/pkg/domain/entities/events.go +++ b/pkg/domain/entities/events.go @@ -14,6 +14,7 @@ type Event struct { // TODO: it would be nice to have full control over the types at some // point and fork such Docker types. dockerEvents.Message + HealthStatus string } // ConvertToLibpodEvent converts an entities event to a libpod one. @@ -44,6 +45,7 @@ func ConvertToLibpodEvent(e Event) *libpodEvents.Event { Status: status, Time: time.Unix(0, e.TimeNano), Type: t, + HealthStatus: e.HealthStatus, Details: libpodEvents.Details{ Attributes: details, }, @@ -59,7 +61,7 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event { attributes["image"] = e.Image attributes["name"] = e.Name attributes["containerExitCode"] = strconv.Itoa(e.ContainerExitCode) - return &Event{dockerEvents.Message{ + message := dockerEvents.Message{ // Compatibility with clients that still look for deprecated API elements Status: e.Status.String(), ID: e.ID, @@ -73,5 +75,9 @@ func ConvertToEntitiesEvent(e libpodEvents.Event) *Event { Scope: "local", Time: e.Time.Unix(), TimeNano: e.Time.UnixNano(), - }} + } + return &Event{ + message, + e.HealthStatus, + } } diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 11f6e8687..da317cfad 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -325,6 +325,8 @@ type ImageScpOptions struct { Image string `json:"image,omitempty"` // User is used in conjunction with Transfer to determine if a valid user was given to save from/load into User string `json:"user,omitempty"` + // Tag is the name to be used for the image on the destination + Tag string `json:"tag,omitempty"` } // ImageScpConnections provides the ssh related information used in remote image transfer diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 81f3e837b..e88c5f854 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -67,6 +67,21 @@ type ManifestModifyOptions struct { type ManifestRemoveOptions struct { } +// ManifestRemoveReport provides the model for the removed manifest +// +// swagger:model +type ManifestRemoveReport struct { + // Deleted manifest list. + Deleted []string `json:",omitempty"` + // Untagged images. Can be longer than Deleted. + Untagged []string `json:",omitempty"` + // Errors associated with operation + Errors []string `json:",omitempty"` + // ExitCode describes the exit codes as described in the `podman rmi` + // man page. + ExitCode int +} + // ManifestModifyReport provides the model for removed digests and changed manifest // // swagger:model diff --git a/pkg/domain/entities/reports/scp.go b/pkg/domain/entities/reports/scp.go new file mode 100644 index 000000000..1e102bab3 --- /dev/null +++ b/pkg/domain/entities/reports/scp.go @@ -0,0 +1,5 @@ +package reports + +type ScpReport struct { + Id string `json:"Id"` //nolint:revive,stylecheck +} diff --git a/pkg/domain/entities/volumes.go b/pkg/domain/entities/volumes.go index 556df16c1..9a06b2238 100644 --- a/pkg/domain/entities/volumes.go +++ b/pkg/domain/entities/volumes.go @@ -54,6 +54,11 @@ type VolumeListReport struct { VolumeConfigResponse } +// VolumeReloadReport describes the response from reload volume plugins +type VolumeReloadReport struct { + define.VolumeReload +} + /* * Docker API compatibility types */ diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index e2ab8d70c..f88a165e7 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -1,6 +1,7 @@ package filters import ( + "errors" "fmt" "strconv" "strings" @@ -10,7 +11,6 @@ import ( "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" ) // GenerateContainerFilterFuncs return ContainerFilter functions based of filter. @@ -36,7 +36,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo for _, exitCode := range filterValues { ec, err := strconv.ParseInt(exitCode, 10, 32) if err != nil { - return nil, errors.Wrapf(err, "exited code out of range %q", ec) + return nil, fmt.Errorf("exited code out of range %q: %w", ec, err) } exitCodes = append(exitCodes, int32(ec)) } @@ -184,7 +184,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo for _, podNameOrID := range filterValues { p, err := r.LookupPod(podNameOrID) if err != nil { - if errors.Cause(err) == define.ErrNoSuchPod { + if errors.Is(err, define.ErrNoSuchPod) { continue } return nil, err @@ -291,7 +291,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo return false }, filterValueError } - return nil, errors.Errorf("%s is an invalid filter", filter) + return nil, fmt.Errorf("%s is an invalid filter", filter) } // GeneratePruneContainerFilterFuncs return ContainerFilter functions based of filter for prune operation @@ -304,7 +304,7 @@ func GeneratePruneContainerFilterFuncs(filter string, filterValues []string, r * case "until": return prepareUntilFilterFunc(filterValues) } - return nil, errors.Errorf("%s is an invalid filter", filter) + return nil, fmt.Errorf("%s is an invalid filter", filter) } func prepareUntilFilterFunc(filterValues []string) (func(container *libpod.Container) bool, error) { diff --git a/pkg/domain/filters/volumes.go b/pkg/domain/filters/volumes.go index e88bd4228..a18e6332c 100644 --- a/pkg/domain/filters/volumes.go +++ b/pkg/domain/filters/volumes.go @@ -2,6 +2,7 @@ package filters import ( "net/url" + "regexp" "strings" "github.com/containers/podman/v4/libpod" @@ -15,9 +16,12 @@ func GenerateVolumeFilters(filters url.Values) ([]libpod.VolumeFilter, error) { for _, val := range v { switch filter { case "name": - nameVal := val + nameRegexp, err := regexp.Compile(val) + if err != nil { + return nil, err + } vf = append(vf, func(v *libpod.Volume) bool { - return nameVal == v.Name() + return nameRegexp.MatchString(v.Name()) }) case "driver": driverVal := val diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index c7cd0cb56..1688be57e 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -2,6 +2,7 @@ package abi import ( "context" + "errors" "fmt" "io/ioutil" "os" @@ -16,7 +17,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" - "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/libpod/logs" "github.com/containers/podman/v4/pkg/checkpoint" "github.com/containers/podman/v4/pkg/domain/entities" @@ -33,12 +33,11 @@ import ( "github.com/containers/podman/v4/pkg/specgenutil" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) // getContainersAndInputByContext gets containers whether all, latest, or a slice of names/ids -// is specified. It also returns a list of the corresponding input name used to lookup each container. +// is specified. It also returns a list of the corresponding input name used to look up each container. func getContainersAndInputByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, rawInput []string, err error) { var ctr *libpod.Container ctrs = []*libpod.Container{} @@ -81,7 +80,7 @@ func getContainersByContext(all, latest bool, names []string, runtime *libpod.Ru func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrID string, options entities.ContainerExistsOptions) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupContainer(nameOrID) if err != nil { - if errors.Cause(err) != define.ErrNoSuchCtr { + if !errors.Is(err, define.ErrNoSuchCtr) { return nil, err } if options.External { @@ -121,7 +120,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri report := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := c.Pause() - if err != nil && options.All && errors.Cause(err) == define.ErrCtrStateInvalid { + if err != nil && options.All && errors.Is(err, define.ErrCtrStateInvalid) { logrus.Debugf("Container %s is not running", c.ID()) continue } @@ -138,7 +137,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st report := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := c.Unpause() - if err != nil && options.All && errors.Cause(err) == define.ErrCtrStateInvalid { + if err != nil && options.All && errors.Is(err, define.ErrCtrStateInvalid) { logrus.Debugf("Container %s is not paused", c.ID()) continue } @@ -149,7 +148,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) { names := namesOrIds ctrs, rawInputs, err := getContainersAndInputByContext(options.All, options.Latest, names, ic.Libpod) - if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { + if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { return nil, err } ctrMap := map[string]string{} @@ -167,13 +166,13 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin } if err != nil { switch { - case errors.Cause(err) == define.ErrCtrStopped: + case errors.Is(err, define.ErrCtrStopped): logrus.Debugf("Container %s is already stopped", c.ID()) - case options.All && errors.Cause(err) == define.ErrCtrStateInvalid: + case options.All && errors.Is(err, define.ErrCtrStateInvalid): logrus.Debugf("Container %s is not running, could not stop", c.ID()) // container never created in OCI runtime // docker parity: do nothing just return container id - case errors.Cause(err) == define.ErrCtrStateInvalid: + case errors.Is(err, define.ErrCtrStateInvalid): logrus.Debugf("Container %s is either not created on runtime or is in a invalid state", c.ID()) default: return err @@ -183,7 +182,7 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin if err != nil { // Issue #7384 and #11384: If the container is configured for // auto-removal, it might already have been removed at this point. - // We still need to to cleanup since we do not know if the other cleanup process is successful + // We still need to clean up since we do not know if the other cleanup process is successful if c.AutoRemove() && (errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved)) { return nil } @@ -239,7 +238,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin reports := make([]*entities.KillReport, 0, len(ctrs)) for _, con := range ctrs { err := con.Kill(uint(sig)) - if options.All && errors.Cause(err) == define.ErrCtrStateInvalid { + if options.All && errors.Is(err, define.ErrCtrStateInvalid) { logrus.Debugf("Container %s is not running", con.ID()) continue } @@ -290,8 +289,7 @@ func (ic *ContainerEngine) removeContainer(ctx context.Context, ctr *libpod.Cont return nil } logrus.Debugf("Failed to remove container %s: %s", ctr.ID(), err.Error()) - switch errors.Cause(err) { - case define.ErrNoSuchCtr: + if errors.Is(err, define.ErrNoSuchCtr) { // Ignore if the container does not exist (anymore) when either // it has been requested by the user of if the container is a // service one. Service containers are removed along with its @@ -302,7 +300,7 @@ func (ic *ContainerEngine) removeContainer(ctx context.Context, ctr *libpod.Cont logrus.Debugf("Ignoring error (--allow-missing): %v", err) return nil } - case define.ErrCtrRemoved: + } else if errors.Is(err, define.ErrCtrRemoved) { return nil } return err @@ -318,15 +316,15 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, for _, ctr := range names { report := reports.RmReport{Id: ctr} report.Err = ic.Libpod.RemoveStorageContainer(ctr, options.Force) - switch errors.Cause(report.Err) { - case nil: + //nolint:gocritic + if report.Err == nil { // remove container names that we successfully deleted rmReports = append(rmReports, &report) - case define.ErrNoSuchCtr, define.ErrCtrExists: + } else if errors.Is(report.Err, define.ErrNoSuchCtr) || errors.Is(report.Err, define.ErrCtrExists) { // There is still a potential this is a libpod container tmpNames = append(tmpNames, ctr) - default: - if _, err := ic.Libpod.LookupContainer(ctr); errors.Cause(err) == define.ErrNoSuchCtr { + } else { + if _, err := ic.Libpod.LookupContainer(ctr); errors.Is(err, define.ErrNoSuchCtr) { // remove container failed, but not a libpod container rmReports = append(rmReports, &report) continue @@ -338,7 +336,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, names = tmpNames ctrs, err := getContainersByContext(options.All, options.Latest, names, ic.Libpod) - if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { + if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchCtr)) { // Failed to get containers. If force is specified, get the containers ID // and evict them if !options.Force { @@ -350,7 +348,7 @@ func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, report := reports.RmReport{Id: ctr} _, err := ic.Libpod.EvictContainer(ctx, ctr, options.Volumes) if err != nil { - if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr { + if options.Ignore && errors.Is(err, define.ErrNoSuchCtr) { logrus.Debugf("Ignoring error (--allow-missing): %v", err) rmReports = append(rmReports, &report) continue @@ -427,7 +425,7 @@ func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []st ctr, err := ic.Libpod.GetLatestContainer() if err != nil { if errors.Is(err, define.ErrNoSuchCtr) { - return nil, []error{errors.Wrapf(err, "no containers to inspect")}, nil + return nil, []error{fmt.Errorf("no containers to inspect: %w", err)}, nil } return nil, nil, err } @@ -453,7 +451,7 @@ func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []st // ErrNoSuchCtr is non-fatal, other errors will be // treated as fatal. if errors.Is(err, define.ErrNoSuchCtr) { - errs = append(errs, errors.Errorf("no such container %s", name)) + errs = append(errs, fmt.Errorf("no such container %s", name)) continue } return nil, nil, err @@ -464,7 +462,7 @@ func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []st // ErrNoSuchCtr is non-fatal, other errors will be // treated as fatal. if errors.Is(err, define.ErrNoSuchCtr) { - errs = append(errs, errors.Errorf("no such container %s", name)) + errs = append(errs, fmt.Errorf("no such container %s", name)) continue } return nil, nil, err @@ -488,7 +486,7 @@ func (ic *ContainerEngine) ContainerTop(ctx context.Context, options entities.To container, err = ic.Libpod.LookupContainer(options.NameOrID) } if err != nil { - return nil, errors.Wrap(err, "unable to lookup requested container") + return nil, fmt.Errorf("unable to look up requested container: %w", err) } // Run Top. @@ -513,12 +511,12 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, case "oci": mimeType = buildah.OCIv1ImageManifest if len(options.Message) > 0 { - return nil, errors.Errorf("messages are only compatible with the docker image format (-f docker)") + return nil, fmt.Errorf("messages are only compatible with the docker image format (-f docker)") } case "docker": mimeType = manifest.DockerV2Schema2MediaType default: - return nil, errors.Errorf("unrecognized image format %q", options.Format) + return nil, fmt.Errorf("unrecognized image format %q", options.Format) } sc := ic.Libpod.SystemContext() @@ -635,13 +633,13 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st containers, err = getContainersByContext(false, options.Latest, namesOrIds, ic.Libpod) default: for _, nameOrID := range namesOrIds { - logrus.Debugf("lookup container: %q", nameOrID) + logrus.Debugf("look up container: %q", nameOrID) ctr, err := ic.Libpod.LookupContainer(nameOrID) if err == nil { containers = append(containers, ctr) } else { // If container was not found, check if this is a checkpoint image - logrus.Debugf("lookup image: %q", nameOrID) + logrus.Debugf("look up image: %q", nameOrID) img, _, err := ic.Libpod.LibimageRuntime().LookupImage(nameOrID, nil) if err != nil { return nil, fmt.Errorf("no such container or image: %s", nameOrID) @@ -661,7 +659,7 @@ func (ic *ContainerEngine) ContainerRestore(ctx context.Context, namesOrIds []st // CRImportCheckpoint is expected to import exactly one container from checkpoint image checkpointImageImportErrors = append( checkpointImageImportErrors, - errors.Errorf("unable to import checkpoint from image: %q: %v", nameOrID, err), + fmt.Errorf("unable to import checkpoint from image: %q: %v", nameOrID, err), ) } else { containers = append(containers, importedContainers[0]) @@ -721,16 +719,16 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, ctr := ctrs[0] conState, err := ctr.State() if err != nil { - return errors.Wrapf(err, "unable to determine state of %s", ctr.ID()) + return fmt.Errorf("unable to determine state of %s: %w", ctr.ID(), err) } if conState != define.ContainerStateRunning { - return errors.Errorf("you can only attach to running containers") + return fmt.Errorf("you can only attach to running containers") } // If the container is in a pod, also set to recursively start dependencies err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, false) - if err != nil && errors.Cause(err) != define.ErrDetach { - return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) + if err != nil && !errors.Is(err, define.ErrDetach) { + return fmt.Errorf("error attaching to container %s: %w", ctr.ID(), err) } os.Stdout.WriteString("\n") return nil @@ -752,12 +750,12 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime) (*libpod.E storageConfig := rt.StorageConfig() runtimeConfig, err := rt.GetConfig() if err != nil { - return nil, errors.Wrapf(err, "error retrieving Libpod configuration to build exec exit command") + return nil, fmt.Errorf("error retrieving Libpod configuration to build exec exit command: %w", err) } // TODO: Add some ability to toggle syslog exitCommandArgs, err := specgenutil.CreateExitCommandArgs(storageConfig, runtimeConfig, logrus.IsLevelEnabled(logrus.DebugLevel), false, true) if err != nil { - return nil, errors.Wrapf(err, "error constructing exit command for exec session") + return nil, fmt.Errorf("error constructing exit command for exec session: %w", err) } execConfig.ExitCommand = exitCommandArgs @@ -775,7 +773,7 @@ func checkExecPreserveFDs(options entities.ExecOptions) error { for _, e := range entries { i, err := strconv.Atoi(e.Name()) if err != nil { - return errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name()) + return fmt.Errorf("cannot parse %s in /proc/self/fd: %w", e.Name(), err) } m[i] = true } @@ -892,7 +890,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri if options.Attach { err = terminal.StartAttachCtr(ctx, ctr, options.Stdout, options.Stderr, options.Stdin, options.DetachKeys, options.SigProxy, !ctrRunning) - if errors.Cause(err) == define.ErrDetach { + if errors.Is(err, define.ErrDetach) { // User manually detached // Exit cleanly immediately reports = append(reports, &entities.ContainerStartReport{ @@ -904,7 +902,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri return reports, nil } - if errors.Cause(err) == define.ErrWillDeadlock { + if errors.Is(err, define.ErrWillDeadlock) { logrus.Debugf("Deadlock error: %v", err) reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), @@ -912,7 +910,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri Err: err, ExitCode: define.ExitCode(err), }) - return reports, errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + return reports, fmt.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) } if ctrRunning { @@ -937,8 +935,9 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri logrus.Errorf("Removing container %s: %v", ctr.ID(), err) } } - return reports, errors.Wrapf(err, "unable to start container %s", ctr.ID()) + return reports, fmt.Errorf("unable to start container %s: %w", ctr.ID(), err) } + exitCode = ic.GetContainerExitCode(ctx, ctr) reports = append(reports, &entities.ContainerStartReport{ Id: ctr.ID(), @@ -960,12 +959,12 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } if err := ctr.Start(ctx, true); err != nil { report.Err = err - if errors.Cause(err) == define.ErrWillDeadlock { - report.Err = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks") + if errors.Is(err, define.ErrWillDeadlock) { + report.Err = fmt.Errorf("please run 'podman system renumber' to resolve deadlocks: %w", err) reports = append(reports, report) continue } - report.Err = errors.Wrapf(err, "unable to start container %q", ctr.ID()) + report.Err = fmt.Errorf("unable to start container %q: %w", ctr.ID(), err) reports = append(reports, report) if ctr.AutoRemove() { if err := ic.removeContainer(ctx, ctr, entities.RmOptions{}); err != nil { @@ -1001,7 +1000,7 @@ func (ic *ContainerEngine) Diff(ctx context.Context, namesOrIDs []string, opts e if opts.Latest { ctnr, err := ic.Libpod.GetLatestContainer() if err != nil { - return nil, errors.Wrap(err, "unable to get latest container") + return nil, fmt.Errorf("unable to get latest container: %w", err) } base = ctnr.ID() } @@ -1064,7 +1063,7 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta // We've manually detached from the container // Do not perform cleanup, or wait for container exit code // Just exit immediately - if errors.Cause(err) == define.ErrDetach { + if errors.Is(err, define.ErrDetach) { report.ExitCode = 0 return &report, nil } @@ -1074,10 +1073,10 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID()) } } - if errors.Cause(err) == define.ErrWillDeadlock { + if errors.Is(err, define.ErrWillDeadlock) { logrus.Debugf("Deadlock error on %q: %v", ctr.ID(), err) report.ExitCode = define.ExitCode(err) - return &report, errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) + return &report, fmt.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID()) } report.ExitCode = define.ExitCode(err) return &report, err @@ -1086,8 +1085,8 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta if opts.Rm && !ctr.ShouldRestart(ctx) { var timeout *uint if err := ic.Libpod.RemoveContainer(ctx, ctr, false, true, timeout); err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr || - errors.Cause(err) == define.ErrCtrRemoved { + if errors.Is(err, define.ErrNoSuchCtr) || + errors.Is(err, define.ErrCtrRemoved) { logrus.Infof("Container %s was already removed, skipping --rm", ctr.ID()) } else { logrus.Errorf("Removing container %s: %v", ctr.ID(), err) @@ -1099,25 +1098,11 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta func (ic *ContainerEngine) GetContainerExitCode(ctx context.Context, ctr *libpod.Container) int { exitCode, err := ctr.Wait(ctx) - if err == nil { - return int(exitCode) - } - if errors.Cause(err) != define.ErrNoSuchCtr { - logrus.Errorf("Could not retrieve exit code: %v", err) + if err != nil { + logrus.Errorf("Waiting for container %s: %v", ctr.ID(), err) return define.ExecErrorCodeNotFound } - // Make 4 attempt with 0.25s backoff between each for 1 second total - var event *events.Event - for i := 0; i < 4; i++ { - event, err = ic.Libpod.GetLastContainerEvent(ctx, ctr.ID(), events.Exited) - if err != nil { - time.Sleep(250 * time.Millisecond) - continue - } - return event.ContainerExitCode - } - logrus.Errorf("Could not retrieve exit code from event: %v", err) - return define.ExecErrorCodeNotFound + return int(exitCode) } func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { @@ -1194,12 +1179,12 @@ func (ic *ContainerEngine) ContainerCleanup(ctx context.Context, namesOrIds []st var timeout *uint err = ic.Libpod.RemoveContainer(ctx, ctr, false, true, timeout) if err != nil { - report.RmErr = errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID()) + report.RmErr = fmt.Errorf("failed to clean up and remove container %v: %w", ctr.ID(), err) } } else { err := ctr.Cleanup(ctx) if err != nil { - report.CleanErr = errors.Wrapf(err, "failed to cleanup container %v", ctr.ID()) + report.CleanErr = fmt.Errorf("failed to clean up container %v: %w", ctr.ID(), err) } } @@ -1226,7 +1211,7 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin err := ctr.Init(ctx, ctr.PodID() != "") // If we're initializing all containers, ignore invalid state errors - if options.All && errors.Cause(err) == define.ErrCtrStateInvalid { + if options.All && errors.Is(err, define.ErrCtrStateInvalid) { err = nil } report.Err = err @@ -1337,7 +1322,7 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIDs []str if mounted { report := entities.ContainerUnmountReport{Id: sctr.ID} if _, report.Err = ic.Libpod.UnmountStorageContainer(sctr.ID, options.Force); report.Err != nil { - if errors.Cause(report.Err) != define.ErrCtrExists { + if !errors.Is(report.Err, define.ErrCtrExists) { reports = append(reports, &report) } } else { @@ -1371,11 +1356,11 @@ func (ic *ContainerEngine) ContainerUnmount(ctx context.Context, nameOrIDs []str report := entities.ContainerUnmountReport{Id: ctr.ID()} if err := ctr.Unmount(options.Force); err != nil { - if options.All && errors.Cause(err) == storage.ErrLayerNotMounted { + if options.All && errors.Is(err, storage.ErrLayerNotMounted) { logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID()) continue } - report.Err = errors.Wrapf(err, "error unmounting container %s", ctr.ID()) + report.Err = fmt.Errorf("error unmounting container %s: %w", ctr.ID(), err) } reports = append(reports, &report) } @@ -1424,7 +1409,7 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) { func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) { if options.Interval < 1 { - return nil, errors.New("Invalid interval, must be a positive number greater zero") + return nil, errors.New("invalid interval, must be a positive number greater zero") } if rootless.IsRootless() { unified, err := cgroups.IsCgroup2UnifiedMode() @@ -1479,19 +1464,18 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri computeStats := func() ([]define.ContainerStats, error) { containers, err = containerFunc() if err != nil { - return nil, errors.Wrapf(err, "unable to get list of containers") + return nil, fmt.Errorf("unable to get list of containers: %w", err) } reportStats := []define.ContainerStats{} for _, ctr := range containers { stats, err := ctr.GetContainerStats(containerStats[ctr.ID()]) if err != nil { - cause := errors.Cause(err) - if queryAll && (cause == define.ErrCtrRemoved || cause == define.ErrNoSuchCtr || cause == define.ErrCtrStateInvalid) { + if queryAll && (errors.Is(err, define.ErrCtrRemoved) || errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrStateInvalid)) { continue } - if cause == cgroups.ErrCgroupV1Rootless { - err = cause + if errors.Is(err, cgroups.ErrCgroupV1Rootless) { + err = cgroups.ErrCgroupV1Rootless } return nil, err } diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index fac15f72d..463988c87 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -2,6 +2,7 @@ package abi import ( "context" + "errors" "fmt" "os" "path/filepath" @@ -14,7 +15,6 @@ import ( envLib "github.com/containers/podman/v4/pkg/env" "github.com/containers/podman/v4/utils" "github.com/google/shlex" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -40,7 +40,7 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, } if len(pulledImages) != 1 { - return errors.Errorf("internal error: expected an image to be pulled (or an error)") + return errors.New("internal error: expected an image to be pulled (or an error)") } // Extract the runlabel from the image. @@ -57,7 +57,7 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, } } if runlabel == "" { - return errors.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) + return fmt.Errorf("cannot find the value of label: %s in image: %s", label, imageRef) } cmd, env, err := generateRunlabelCommand(runlabel, pulledImages[0], imageRef, args, options) @@ -86,7 +86,7 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, name := cmd[i+1] ctr, err := ic.Libpod.LookupContainer(name) if err != nil { - if errors.Cause(err) != define.ErrNoSuchCtr { + if !errors.Is(err, define.ErrNoSuchCtr) { logrus.Debugf("Error occurred searching for container %s: %v", name, err) return err } diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index d63de2424..38008c7b9 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -2,7 +2,9 @@ package abi import ( "context" + "errors" "fmt" + "io/fs" "io/ioutil" "net/url" "os" @@ -29,12 +31,10 @@ import ( domainUtils "github.com/containers/podman/v4/pkg/domain/utils" "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/rootless" - "github.com/containers/podman/v4/utils" "github.com/containers/storage" dockerRef "github.com/docker/distribution/reference" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -128,14 +128,14 @@ func (ir *ImageEngine) History(ctx context.Context, nameOrID string, opts entiti func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entities.ImageMountOptions) ([]*entities.ImageMountReport, error) { if opts.All && len(nameOrIDs) > 0 { - return nil, errors.Errorf("cannot mix --all with images") + return nil, errors.New("cannot mix --all with images") } if os.Geteuid() != 0 { if driver := ir.Libpod.StorageConfig().GraphDriverName; driver != "vfs" { // Do not allow to mount a graphdriver that is not vfs if we are creating the userns as part // of the mount command. - return nil, errors.Errorf("cannot mount using driver %s in rootless mode", driver) + return nil, fmt.Errorf("cannot mount using driver %s in rootless mode", driver) } became, ret, err := rootless.BecomeRootInUserNS("") @@ -194,7 +194,7 @@ func (ir *ImageEngine) Mount(ctx context.Context, nameOrIDs []string, opts entit func (ir *ImageEngine) Unmount(ctx context.Context, nameOrIDs []string, options entities.ImageUnmountOptions) ([]*entities.ImageUnmountReport, error) { if options.All && len(nameOrIDs) > 0 { - return nil, errors.Errorf("cannot mix --all with images") + return nil, errors.New("cannot mix --all with images") } listImagesOptions := &libimage.ListImagesOptions{} @@ -292,7 +292,7 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri case "v2s2", "docker": manifestType = manifest.DockerV2Schema2MediaType default: - return errors.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) + return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", options.Format) } pushOptions := &libimage.PushOptions{} @@ -350,22 +350,6 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri } return pushError } - -// Transfer moves images between root and rootless storage so the user specified in the scp call can access and use the image modified by root -func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error { - if source.User == "" { - return errors.Wrapf(define.ErrInvalidArg, "you must define a user when transferring from root to rootless storage") - } - podman, err := os.Executable() - if err != nil { - return err - } - if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo - return transferRootless(source, dest, podman, parentFlags) - } - return transferRootful(source, dest, podman, parentFlags) -} - func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, options entities.ImageTagOptions) error { // Allow tagging manifest list instead of resolving instances from manifest lookupOptions := &libimage.LookupImageOptions{ManifestList: true} @@ -539,12 +523,12 @@ func removeErrorsToExitCode(rmErrors []error) int { } for _, e := range rmErrors { - switch errors.Cause(e) { - case storage.ErrImageUnknown, storage.ErrLayerUnknown: + //nolint:gocritic + if errors.Is(e, storage.ErrImageUnknown) || errors.Is(e, storage.ErrLayerUnknown) { noSuchImageErrors = true - case storage.ErrImageUsedByContainer: + } else if errors.Is(e, storage.ErrImageUsedByContainer) { inUseErrors = true - default: + } else { otherErrors = true } } @@ -606,11 +590,11 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { mech, err := signature.NewGPGSigningMechanism() if err != nil { - return nil, errors.Wrap(err, "error initializing GPG") + return nil, fmt.Errorf("error initializing GPG: %w", err) } defer mech.Close() if err := mech.SupportsSigning(); err != nil { - return nil, errors.Wrap(err, "signing is not supported") + return nil, fmt.Errorf("signing is not supported: %w", err) } sc := ir.Libpod.SystemContext() sc.DockerCertPath = options.CertDir @@ -620,11 +604,11 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie err = func() error { srcRef, err := alltransports.ParseImageName(signimage) if err != nil { - return errors.Wrapf(err, "error parsing image name") + return fmt.Errorf("error parsing image name: %w", err) } rawSource, err := srcRef.NewImageSource(ctx, sc) if err != nil { - return errors.Wrapf(err, "error getting image source") + return fmt.Errorf("error getting image source: %w", err) } defer func() { if err = rawSource.Close(); err != nil { @@ -633,17 +617,17 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie }() topManifestBlob, manifestType, err := rawSource.GetManifest(ctx, nil) if err != nil { - return errors.Wrapf(err, "error getting manifest blob") + return fmt.Errorf("error getting manifest blob: %w", err) } dockerReference := rawSource.Reference().DockerReference() if dockerReference == nil { - return errors.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) + return fmt.Errorf("cannot determine canonical Docker reference for destination %s", transports.ImageName(rawSource.Reference())) } var sigStoreDir string if options.Directory != "" { repo := reference.Path(dockerReference) if path.Clean(repo) != repo { // Coverage: This should not be reachable because /./ and /../ components are not valid in docker references - return errors.Errorf("Unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) + return fmt.Errorf("unexpected path elements in Docker reference %s for signature storage", dockerReference.String()) } sigStoreDir = filepath.Join(options.Directory, repo) } else { @@ -663,11 +647,11 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie if options.All { if !manifest.MIMETypeIsMultiImage(manifestType) { - return errors.Errorf("%s is not a multi-architecture image (manifest type %s)", signimage, manifestType) + return fmt.Errorf("%s is not a multi-architecture image (manifest type %s)", signimage, manifestType) } list, err := manifest.ListFromBlob(topManifestBlob, manifestType) if err != nil { - return errors.Wrapf(err, "Error parsing manifest list %q", string(topManifestBlob)) + return fmt.Errorf("error parsing manifest list %q: %w", string(topManifestBlob), err) } instanceDigests := list.Instances() for _, instanceDigest := range instanceDigests { @@ -677,13 +661,13 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie return err } if err = putSignature(man, mech, sigStoreDir, instanceDigest, dockerReference, options); err != nil { - return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), instanceDigest) + return fmt.Errorf("error storing signature for %s, %v: %w", dockerReference.String(), instanceDigest, err) } } return nil } if err = putSignature(topManifestBlob, mech, sigStoreDir, manifestDigest, dockerReference, options); err != nil { - return errors.Wrapf(err, "error storing signature for %s, %v", dockerReference.String(), manifestDigest) + return fmt.Errorf("error storing signature for %s, %v: %w", dockerReference.String(), manifestDigest, err) } return nil }() @@ -694,53 +678,32 @@ func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entitie return nil, nil } -func getSigFilename(sigStoreDirPath string) (string, error) { - sigFileSuffix := 1 - sigFiles, err := ioutil.ReadDir(sigStoreDirPath) +func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error { + rep, source, dest, flags, err := domainUtils.ExecuteTransfer(src, dst, parentFlags, quiet) if err != nil { - return "", err - } - sigFilenames := make(map[string]bool) - for _, file := range sigFiles { - sigFilenames[file.Name()] = true + return err } - for { - sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) - if _, exists := sigFilenames[sigFilename]; !exists { - return sigFilename, nil + if (rep == nil && err == nil) && (source != nil && dest != nil) { // we need to execute the transfer + err := Transfer(ctx, *source, *dest, flags) + if err != nil { + return err } - sigFileSuffix++ } + return nil } -func localPathFromURI(url *url.URL) (string, error) { - if url.Scheme != "file" { - return "", errors.Errorf("writing to %s is not supported. Use a supported scheme", url.String()) - } - return url.Path, nil -} - -// putSignature creates signature and saves it to the signstore file -func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error { - newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy) - if err != nil { - return err - } - signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex()) - if err := os.MkdirAll(signatureDir, 0751); err != nil { - // The directory is allowed to exist - if !os.IsExist(err) { - return err - } +func Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error { + if source.User == "" { + return fmt.Errorf("you must define a user when transferring from root to rootless storage: %w", define.ErrInvalidArg) } - sigFilename, err := getSigFilename(signatureDir) + podman, err := os.Executable() if err != nil { return err } - if err = ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644); err != nil { - return err + if rootless.IsRootless() && (len(dest.User) == 0 || dest.User == "root") { // if we are rootless and do not have a destination user we can just use sudo + return transferRootless(source, dest, podman, parentFlags) } - return nil + return transferRootful(source, dest, podman, parentFlags) } // TransferRootless creates new podman processes using exec.Command and sudo, transferring images between the given source and destination users @@ -763,7 +726,7 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt } else { cmdSave = exec.Command(podman) } - cmdSave = utils.CreateSCPCommand(cmdSave, saveCommand) + cmdSave = domainUtils.CreateSCPCommand(cmdSave, saveCommand) logrus.Debugf("Executing save command: %q", cmdSave) err := cmdSave.Run() if err != nil { @@ -776,8 +739,11 @@ func transferRootless(source entities.ImageScpOptions, dest entities.ImageScpOpt } else { cmdLoad = exec.Command(podman) } - cmdLoad = utils.CreateSCPCommand(cmdLoad, loadCommand) + cmdLoad = domainUtils.CreateSCPCommand(cmdLoad, loadCommand) logrus.Debugf("Executing load command: %q", cmdLoad) + if len(dest.Tag) > 0 { + return domainUtils.ScpTag(cmdLoad, podman, dest) + } return cmdLoad.Run() } @@ -833,11 +799,20 @@ func transferRootful(source entities.ImageScpOptions, dest entities.ImageScpOpti return err } } - err = execPodman(uSave, saveCommand) + _, err = execTransferPodman(uSave, saveCommand, false) if err != nil { return err } - return execPodman(uLoad, loadCommand) + out, err := execTransferPodman(uLoad, loadCommand, (len(dest.Tag) > 0)) + if err != nil { + return err + } + if out != nil { + image := domainUtils.ExtractImage(out) + _, err := execTransferPodman(uLoad, []string{podman, "tag", image, dest.Tag}, false) + return err + } + return nil } func lookupUser(u string) (*user.User, error) { @@ -847,10 +822,10 @@ func lookupUser(u string) (*user.User, error) { return user.Lookup(u) } -func execPodman(execUser *user.User, command []string) error { - cmdLogin, err := utils.LoginUser(execUser.Username) +func execTransferPodman(execUser *user.User, command []string, needToTag bool) ([]byte, error) { + cmdLogin, err := domainUtils.LoginUser(execUser.Username) if err != nil { - return err + return nil, err } defer func() { @@ -864,11 +839,11 @@ func execPodman(execUser *user.User, command []string) error { cmd.Stdout = os.Stdout uid, err := strconv.ParseInt(execUser.Uid, 10, 32) if err != nil { - return err + return nil, err } gid, err := strconv.ParseInt(execUser.Gid, 10, 32) if err != nil { - return err + return nil, err } cmd.SysProcAttr = &syscall.SysProcAttr{ Credential: &syscall.Credential{ @@ -878,5 +853,55 @@ func execPodman(execUser *user.User, command []string) error { NoSetGroups: false, }, } - return cmd.Run() + if needToTag { + cmd.Stdout = nil + return cmd.Output() + } + return nil, cmd.Run() +} + +func getSigFilename(sigStoreDirPath string) (string, error) { + sigFileSuffix := 1 + sigFiles, err := ioutil.ReadDir(sigStoreDirPath) + if err != nil { + return "", err + } + sigFilenames := make(map[string]bool) + for _, file := range sigFiles { + sigFilenames[file.Name()] = true + } + for { + sigFilename := "signature-" + strconv.Itoa(sigFileSuffix) + if _, exists := sigFilenames[sigFilename]; !exists { + return sigFilename, nil + } + sigFileSuffix++ + } +} + +func localPathFromURI(url *url.URL) (string, error) { + if url.Scheme != "file" { + return "", fmt.Errorf("writing to %s is not supported. Use a supported scheme", url.String()) + } + return url.Path, nil +} + +// putSignature creates signature and saves it to the signstore file +func putSignature(manifestBlob []byte, mech signature.SigningMechanism, sigStoreDir string, instanceDigest digest.Digest, dockerReference dockerRef.Reference, options entities.SignOptions) error { + newSig, err := signature.SignDockerManifest(manifestBlob, dockerReference.String(), mech, options.SignBy) + if err != nil { + return err + } + signatureDir := fmt.Sprintf("%s@%s=%s", sigStoreDir, instanceDigest.Algorithm(), instanceDigest.Hex()) + if err := os.MkdirAll(signatureDir, 0751); err != nil { + // The directory is allowed to exist + if !errors.Is(err, fs.ErrExist) { + return err + } + } + sigFilename, err := getSigFilename(signatureDir) + if err != nil { + return err + } + return ioutil.WriteFile(filepath.Join(signatureDir, sigFilename), newSig, 0644) } diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 8b52c335c..d20744d76 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -4,9 +4,12 @@ import ( "bytes" "context" "encoding/json" + "fmt" "os" "strings" + "errors" + "github.com/containers/common/libimage" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/manifest" @@ -17,7 +20,6 @@ import ( "github.com/containers/storage" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -46,7 +48,7 @@ func (ir *ImageEngine) ManifestCreate(ctx context.Context, name string, images [ func (ir *ImageEngine) ManifestExists(ctx context.Context, name string) (*entities.BoolReport, error) { _, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { - if errors.Cause(err) == storage.ErrImageUnknown { + if errors.Is(err, storage.ErrImageUnknown) { return &entities.BoolReport{Value: false}, nil } return nil, err @@ -63,15 +65,13 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { - switch errors.Cause(err) { - // Do a remote inspect if there's no local image or if the - // local image is not a manifest list. - case storage.ErrImageUnknown, libimage.ErrNotAManifestList: + if errors.Is(err, storage.ErrImageUnknown) || errors.Is(err, libimage.ErrNotAManifestList) { + // Do a remote inspect if there's no local image or if the + // local image is not a manifest list. return ir.remoteManifestInspect(ctx, name) - - default: - return nil, err } + + return nil, err } schema2List, err := manifestList.Inspect() @@ -86,7 +86,7 @@ func (ir *ImageEngine) ManifestInspect(ctx context.Context, name string) ([]byte var b bytes.Buffer if err := json.Indent(&b, rawSchema2List, "", " "); err != nil { - return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) + return nil, fmt.Errorf("error rendering manifest %s for display: %w", name, err) } return b.Bytes(), nil } @@ -113,8 +113,7 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ( // FIXME should we use multierror package instead? // we want the new line here so ignore the linter - //nolint:revive - latestErr = errors.Wrapf(latestErr, "tried %v\n", e) + latestErr = fmt.Errorf("tried %v\n: %w", e, latestErr) } } @@ -125,14 +124,14 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ( } src, err := ref.NewImageSource(ctx, sys) if err != nil { - appendErr(errors.Wrapf(err, "reading image %q", transports.ImageName(ref))) + appendErr(fmt.Errorf("reading image %q: %w", transports.ImageName(ref), err)) continue } defer src.Close() manifestBytes, manifestType, err := src.GetManifest(ctx, nil) if err != nil { - appendErr(errors.Wrapf(err, "loading manifest %q", transports.ImageName(ref))) + appendErr(fmt.Errorf("loading manifest %q: %w", transports.ImageName(ref), err)) continue } @@ -150,7 +149,7 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ( logrus.Warnf("The manifest type %s is not a manifest list but a single image.", manType) schema2Manifest, err := manifest.Schema2FromManifest(result) if err != nil { - return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType) + return nil, fmt.Errorf("error parsing manifest blob %q as a %q: %w", string(result), manType, err) } if result, err = schema2Manifest.Serialize(); err != nil { return nil, err @@ -158,7 +157,7 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ( default: listBlob, err := manifest.ListFromBlob(result, manType) if err != nil { - return nil, errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(result), manType) + return nil, fmt.Errorf("error parsing manifest blob %q as a %q: %w", string(result), manType, err) } list, err := listBlob.ConvertToMIMEType(manifest.DockerV2ListMediaType) if err != nil { @@ -170,7 +169,7 @@ func (ir *ImageEngine) remoteManifestInspect(ctx context.Context, name string) ( } if err = json.Indent(&b, result, "", " "); err != nil { - return nil, errors.Wrapf(err, "error rendering manifest %s for display", name) + return nil, fmt.Errorf("error rendering manifest %s for display: %w", name, err) } return b.Bytes(), nil } @@ -213,7 +212,7 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []st for _, annotationSpec := range opts.Annotation { spec := strings.SplitN(annotationSpec, "=", 2) if len(spec) != 2 { - return "", errors.Errorf("no value given for annotation %q", spec[0]) + return "", fmt.Errorf("no value given for annotation %q", spec[0]) } annotations[spec[0]] = spec[1] } @@ -231,7 +230,7 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, name string, images []st func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, opts entities.ManifestAnnotateOptions) (string, error) { instanceDigest, err := digest.Parse(image) if err != nil { - return "", errors.Errorf(`invalid image digest "%s": %v`, image, err) + return "", fmt.Errorf(`invalid image digest "%s": %v`, image, err) } manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) @@ -251,7 +250,7 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, for _, annotationSpec := range opts.Annotation { spec := strings.SplitN(annotationSpec, "=", 2) if len(spec) != 2 { - return "", errors.Errorf("no value given for annotation %q", spec[0]) + return "", fmt.Errorf("no value given for annotation %q", spec[0]) } annotations[spec[0]] = spec[1] } @@ -269,7 +268,7 @@ func (ir *ImageEngine) ManifestAnnotate(ctx context.Context, name, image string, func (ir *ImageEngine) ManifestRemoveDigest(ctx context.Context, name, image string) (string, error) { instanceDigest, err := digest.Parse(image) if err != nil { - return "", errors.Errorf(`invalid image digest "%s": %v`, image, err) + return "", fmt.Errorf(`invalid image digest "%s": %v`, image, err) } manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) @@ -293,7 +292,7 @@ func (ir *ImageEngine) ManifestRm(ctx context.Context, names []string) (report * func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination string, opts entities.ImagePushOptions) (string, error) { manifestList, err := ir.Libpod.LibimageRuntime().LookupManifestList(name) if err != nil { - return "", errors.Wrapf(err, "error retrieving local image from image name %s", name) + return "", fmt.Errorf("error retrieving local image from image name %s: %w", name, err) } var manifestType string @@ -304,7 +303,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin case "v2s2", "docker": manifestType = manifest.DockerV2Schema2MediaType default: - return "", errors.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format) + return "", fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci' or 'v2s2'", opts.Format) } } @@ -333,7 +332,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, name, destination strin if opts.Rm { if _, rmErrors := ir.Libpod.LibimageRuntime().RemoveImages(ctx, []string{manifestList.ID()}, nil); len(rmErrors) > 0 { - return "", errors.Wrap(rmErrors[0], "error removing manifest after push") + return "", fmt.Errorf("error removing manifest after push: %w", rmErrors[0]) } } diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index c5f3cf8b3..2428abfe9 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -2,20 +2,51 @@ package abi import ( "context" + "errors" + "fmt" + "strconv" "github.com/containers/common/libnetwork/types" netutil "github.com/containers/common/libnetwork/util" "github.com/containers/common/pkg/util" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" - "github.com/pkg/errors" ) func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]types.Network, error) { + // dangling filter is not provided by netutil + var wantDangling bool + + val, filterDangling := options.Filters["dangling"] + if filterDangling { + switch len(val) { + case 0: + return nil, fmt.Errorf("got no values for filter key \"dangling\"") + case 1: + var err error + wantDangling, err = strconv.ParseBool(val[0]) + if err != nil { + return nil, fmt.Errorf("invalid dangling filter value \"%v\"", val[0]) + } + delete(options.Filters, "dangling") + default: + return nil, fmt.Errorf("got more than one value for filter key \"dangling\"") + } + } + filters, err := netutil.GenerateNetworkFilters(options.Filters) if err != nil { return nil, err } + + if filterDangling { + danglingFilterFunc, err := ic.createDanglingFilterFunc(wantDangling) + if err != nil { + return nil, err + } + + filters = append(filters, danglingFilterFunc) + } nets, err := ic.Libpod.Network().NetworkList(filters...) return nets, err } @@ -26,11 +57,11 @@ func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []stri for _, name := range namesOrIds { net, err := ic.Libpod.Network().NetworkInspect(name) if err != nil { - if errors.Cause(err) == define.ErrNoSuchNetwork { - errs = append(errs, errors.Wrapf(err, "network %s", name)) + if errors.Is(err, define.ErrNoSuchNetwork) { + errs = append(errs, fmt.Errorf("network %s: %w", name, err)) continue } else { - return nil, nil, errors.Wrapf(err, "error inspecting network %s", name) + return nil, nil, fmt.Errorf("error inspecting network %s: %w", name, err) } } networks = append(networks, net) @@ -50,8 +81,8 @@ func (ic *ContainerEngine) NetworkReload(ctx context.Context, names []string, op report.Id = ctr.ID() report.Err = ctr.ReloadNetwork() // ignore errors for invalid ctr state and network mode when --all is used - if options.All && (errors.Cause(report.Err) == define.ErrCtrStateInvalid || - errors.Cause(report.Err) == define.ErrNetworkModeInvalid) { + if options.All && (errors.Is(report.Err, define.ErrCtrStateInvalid) || + errors.Is(report.Err, define.ErrNetworkModeInvalid)) { continue } reports = append(reports, report) @@ -83,7 +114,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o // if user passes force, we nuke containers and pods if !options.Force { // Without the force option, we return an error - return reports, errors.Wrapf(define.ErrNetworkInUse, "%q has associated containers with it. Use -f to forcibly delete containers and pods", name) + return reports, fmt.Errorf("%q has associated containers with it. Use -f to forcibly delete containers and pods: %w", name, define.ErrNetworkInUse) } if c.IsInfra() { // if we have a infra container we need to remove the pod @@ -94,7 +125,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o if err := ic.Libpod.RemovePod(ctx, pod, true, true, options.Timeout); err != nil { return reports, err } - } else if err := ic.Libpod.RemoveContainer(ctx, c, true, true, options.Timeout); err != nil && errors.Cause(err) != define.ErrNoSuchCtr { + } else if err := ic.Libpod.RemoveContainer(ctx, c, true, true, options.Timeout); err != nil && !errors.Is(err, define.ErrNoSuchCtr) { return reports, err } } @@ -109,7 +140,7 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o func (ic *ContainerEngine) NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error) { if util.StringInSlice(network.Name, []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"}) { - return nil, errors.Errorf("cannot create network with name %q because it conflicts with a valid network mode", network.Name) + return nil, fmt.Errorf("cannot create network with name %q because it conflicts with a valid network mode", network.Name) } network, err := ic.Libpod.Network().NetworkCreate(network) if err != nil { @@ -144,6 +175,33 @@ func (ic *ContainerEngine) NetworkExists(ctx context.Context, networkname string // Network prune removes unused networks func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.NetworkPruneOptions) ([]*entities.NetworkPruneReport, error) { + // get all filters + filters, err := netutil.GenerateNetworkPruneFilters(options.Filters) + if err != nil { + return nil, err + } + danglingFilterFunc, err := ic.createDanglingFilterFunc(true) + if err != nil { + return nil, err + } + filters = append(filters, danglingFilterFunc) + nets, err := ic.Libpod.Network().NetworkList(filters...) + if err != nil { + return nil, err + } + + pruneReport := make([]*entities.NetworkPruneReport, 0, len(nets)) + for _, net := range nets { + pruneReport = append(pruneReport, &entities.NetworkPruneReport{ + Name: net.Name, + Error: ic.Libpod.Network().NetworkRemove(net.Name), + }) + } + return pruneReport, nil +} + +// danglingFilter function is special and not implemented in libnetwork filters +func (ic *ContainerEngine) createDanglingFilterFunc(wantDangling bool) (types.FilterFunc, error) { cons, err := ic.Libpod.GetAllContainers() if err != nil { return nil, err @@ -163,31 +221,12 @@ func (ic *ContainerEngine) NetworkPrune(ctx context.Context, options entities.Ne // ignore the default network, this one cannot be deleted networksToKeep[ic.Libpod.GetDefaultNetworkName()] = true - // get all filters - filters, err := netutil.GenerateNetworkPruneFilters(options.Filters) - if err != nil { - return nil, err - } - danglingFilterFunc := func(net types.Network) bool { + return func(net types.Network) bool { for network := range networksToKeep { if network == net.Name { - return false + return !wantDangling } } - return true - } - filters = append(filters, danglingFilterFunc) - nets, err := ic.Libpod.Network().NetworkList(filters...) - if err != nil { - return nil, err - } - - pruneReport := make([]*entities.NetworkPruneReport, 0, len(nets)) - for _, net := range nets { - pruneReport = append(pruneReport, &entities.NetworkPruneReport{ - Name: net.Name, - Error: ic.Libpod.Network().NetworkRemove(net.Name), - }) - } - return pruneReport, nil + return wantDangling + }, nil } diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go index 66794e592..4e8c2e508 100644 --- a/pkg/domain/infra/abi/parse/parse.go +++ b/pkg/domain/infra/abi/parse/parse.go @@ -78,6 +78,16 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) libpodOptions = append(libpodOptions, libpod.WithVolumeDisableQuota()) // set option "NOQUOTA": "true" volumeOptions["NOQUOTA"] = "true" + case "timeout": + if len(splitO) != 2 { + return nil, errors.Wrapf(define.ErrInvalidArg, "timeout option must provide a valid timeout in seconds") + } + intTimeout, err := strconv.Atoi(splitO[1]) + if err != nil { + return nil, errors.Wrapf(err, "cannot convert Timeout %s to an integer", splitO[1]) + } + logrus.Debugf("Removing timeout from options and adding WithTimeout for Timeout %d", intTimeout) + libpodOptions = append(libpodOptions, libpod.WithVolumeDriverTimeout(intTimeout)) default: finalVal = append(finalVal, o) } diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index e04ab3a1a..c913fdb69 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -3,6 +3,7 @@ package abi import ( "bytes" "context" + "errors" "fmt" "io" "io/ioutil" @@ -29,9 +30,8 @@ import ( "github.com/containers/podman/v4/pkg/util" "github.com/ghodss/yaml" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" "github.com/sirupsen/logrus" - yamlv2 "gopkg.in/yaml.v2" + yamlv3 "gopkg.in/yaml.v3" ) // createServiceContainer creates a container that can later on @@ -114,7 +114,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options // sort kube kinds documentList, err = sortKubeKinds(documentList) if err != nil { - return nil, errors.Wrap(err, "unable to sort kube kinds") + return nil, fmt.Errorf("unable to sort kube kinds: %w", err) } ipIndex := 0 @@ -126,7 +126,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options for _, document := range documentList { kind, err := getKubeKind(document) if err != nil { - return nil, errors.Wrap(err, "unable to read kube YAML") + return nil, fmt.Errorf("unable to read kube YAML: %w", err) } // TODO: create constants for the various "kinds" of yaml files. @@ -154,14 +154,14 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options var podTemplateSpec v1.PodTemplateSpec if err := yaml.Unmarshal(document, &podYAML); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube Pod") + return nil, fmt.Errorf("unable to read YAML as Kube Pod: %w", err) } podTemplateSpec.ObjectMeta = podYAML.ObjectMeta podTemplateSpec.Spec = podYAML.Spec for name, val := range podYAML.Annotations { if len(val) > define.MaxKubeAnnotation { - return nil, errors.Errorf("invalid annotation %q=%q value length exceeds Kubernetetes max %d", name, val, define.MaxKubeAnnotation) + return nil, fmt.Errorf("invalid annotation %q=%q value length exceeds Kubernetetes max %d", name, val, define.MaxKubeAnnotation) } } for name, val := range options.Annotations { @@ -182,7 +182,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options var deploymentYAML v1apps.Deployment if err := yaml.Unmarshal(document, &deploymentYAML); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube Deployment") + return nil, fmt.Errorf("unable to read YAML as Kube Deployment: %w", err) } r, err := ic.playKubeDeployment(ctx, &deploymentYAML, options, &ipIndex, configMaps, serviceContainer) @@ -196,7 +196,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options var pvcYAML v1.PersistentVolumeClaim if err := yaml.Unmarshal(document, &pvcYAML); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube PersistentVolumeClaim") + return nil, fmt.Errorf("unable to read YAML as Kube PersistentVolumeClaim: %w", err) } r, err := ic.playKubePVC(ctx, &pvcYAML) @@ -210,7 +210,7 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options var configMap v1.ConfigMap if err := yaml.Unmarshal(document, &configMap); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube ConfigMap") + return nil, fmt.Errorf("unable to read YAML as Kube ConfigMap: %w", err) } configMaps = append(configMaps, configMap) default: @@ -240,7 +240,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM deploymentName = deploymentYAML.ObjectMeta.Name if deploymentName == "" { - return nil, errors.Errorf("Deployment does not have a name") + return nil, errors.New("deployment does not have a name") } numReplicas = 1 if deploymentYAML.Spec.Replicas != nil { @@ -253,7 +253,7 @@ func (ic *ContainerEngine) playKubeDeployment(ctx context.Context, deploymentYAM podName := fmt.Sprintf("%s-pod-%d", deploymentName, i) podReport, err := ic.playKubePod(ctx, podName, &podSpec, options, ipIndex, deploymentYAML.Annotations, configMaps, serviceContainer) if err != nil { - return nil, errors.Wrapf(err, "error encountered while bringing up pod %s", podName) + return nil, fmt.Errorf("error encountered while bringing up pod %s: %w", podName, err) } report.Pods = append(report.Pods, podReport.Pods...) } @@ -275,7 +275,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // Assert the pod has a name if podName == "" { - return nil, errors.Errorf("pod does not have a name") + return nil, fmt.Errorf("pod does not have a name") } podOpt := entities.PodCreateOptions{ @@ -295,7 +295,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } if (ns.IsBridge() && len(networks) == 0) || ns.IsHost() { - return nil, errors.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML") + return nil, fmt.Errorf("invalid value passed to --network: bridge or host networking must be configured in YAML") } podOpt.Net.Network = ns @@ -316,10 +316,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // FIXME This is very hard to support properly with a good ux if len(options.StaticIPs) > *ipIndex { if !podOpt.Net.Network.IsBridge() { - return nil, errors.Wrap(define.ErrInvalidArg, "static ip addresses can only be set when the network mode is bridge") + return nil, fmt.Errorf("static ip addresses can only be set when the network mode is bridge: %w", define.ErrInvalidArg) } if len(podOpt.Net.Networks) != 1 { - return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static ip addresses for more than network, use netname:ip=<ip> syntax to specify ips for more than network") + return nil, fmt.Errorf("cannot set static ip addresses for more than network, use netname:ip=<ip> syntax to specify ips for more than network: %w", define.ErrInvalidArg) } for name, netOpts := range podOpt.Net.Networks { netOpts.StaticIPs = append(netOpts.StaticIPs, options.StaticIPs[*ipIndex]) @@ -331,10 +331,10 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } if len(options.StaticMACs) > *ipIndex { if !podOpt.Net.Network.IsBridge() { - return nil, errors.Wrap(define.ErrInvalidArg, "static mac address can only be set when the network mode is bridge") + return nil, fmt.Errorf("static mac address can only be set when the network mode is bridge: %w", define.ErrInvalidArg) } if len(podOpt.Net.Networks) != 1 { - return nil, errors.Wrap(define.ErrInvalidArg, "cannot set static mac address for more than network, use netname:mac=<mac> syntax to specify mac for more than network") + return nil, fmt.Errorf("cannot set static mac address for more than network, use netname:mac=<mac> syntax to specify mac for more than network: %w", define.ErrInvalidArg) } for name, netOpts := range podOpt.Net.Networks { netOpts.StaticMAC = nettypes.HardwareAddr(options.StaticMACs[*ipIndex]) @@ -370,11 +370,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY cm, err := readConfigMapFromFile(f) if err != nil { - return nil, errors.Wrapf(err, "%q", p) + return nil, fmt.Errorf("%q: %w", p, err) } if _, present := configMapIndex[cm.Name]; present { - return nil, errors.Errorf("ambiguous configuration: the same config map %s is present in YAML and in --configmaps %s file", cm.Name, p) + return nil, fmt.Errorf("ambiguous configuration: the same config map %s is present in YAML and in --configmaps %s file", cm.Name, p) } configMaps = append(configMaps, cm) @@ -396,22 +396,22 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY // error out instead reuse the current volume. vol, err = ic.Libpod.GetVolume(v.Source) if err != nil { - return nil, errors.Wrapf(err, "cannot re-use local volume for volume from configmap %q", v.Source) + return nil, fmt.Errorf("cannot re-use local volume for volume from configmap %q: %w", v.Source, err) } } else { - return nil, errors.Wrapf(err, "cannot create a local volume for volume from configmap %q", v.Source) + return nil, fmt.Errorf("cannot create a local volume for volume from configmap %q: %w", v.Source, err) } } mountPoint, err := vol.MountPoint() if err != nil || mountPoint == "" { - return nil, errors.Wrapf(err, "unable to get mountpoint of volume %q", vol.Name()) + return nil, fmt.Errorf("unable to get mountpoint of volume %q: %w", vol.Name(), err) } // Create files and add data to the volume mountpoint based on the Items in the volume for k, v := range v.Items { dataPath := filepath.Join(mountPoint, k) f, err := os.Create(dataPath) if err != nil { - return nil, errors.Wrapf(err, "cannot create file %q at volume mountpoint %q", k, mountPoint) + return nil, fmt.Errorf("cannot create file %q at volume mountpoint %q: %w", k, mountPoint, err) } defer f.Close() _, err = f.WriteString(v) @@ -492,12 +492,12 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY for _, initCtr := range podYAML.Spec.InitContainers { // Error out if same name is used for more than one container if _, ok := ctrNames[initCtr.Name]; ok { - return nil, errors.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, initCtr.Name) + return nil, fmt.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, initCtr.Name) } ctrNames[initCtr.Name] = "" // Init containers cannot have either of lifecycle, livenessProbe, readinessProbe, or startupProbe set if initCtr.Lifecycle != nil || initCtr.LivenessProbe != nil || initCtr.ReadinessProbe != nil || initCtr.StartupProbe != nil { - return nil, errors.Errorf("cannot create an init container that has either of lifecycle, livenessProbe, readinessProbe, or startupProbe set") + return nil, fmt.Errorf("cannot create an init container that has either of lifecycle, livenessProbe, readinessProbe, or startupProbe set") } pulledImage, labels, err := ic.getImageAndLabelInfo(ctx, cwd, annotations, writer, initCtr, options) if err != nil { @@ -548,7 +548,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY for _, container := range podYAML.Spec.Containers { // Error out if the same name is used for more than one container if _, ok := ctrNames[container.Name]; ok { - return nil, errors.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, container.Name) + return nil, fmt.Errorf("the pod %q is invalid; duplicate container name %q detected", podName, container.Name) } ctrNames[container.Name] = "" pulledImage, labels, err := ic.getImageAndLabelInfo(ctx, cwd, annotations, writer, container, options) @@ -599,11 +599,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY if options.Start != types.OptionalBoolFalse { // Start the containers podStartErrors, err := pod.Start(ctx) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { return nil, err } for id, err := range podStartErrors { - playKubePod.ContainerErrors = append(playKubePod.ContainerErrors, errors.Wrapf(err, "error starting container %s", id).Error()) + playKubePod.ContainerErrors = append(playKubePod.ContainerErrors, fmt.Errorf("error starting container %s: %w", id, err).Error()) fmt.Println(playKubePod.ContainerErrors) } } @@ -735,14 +735,14 @@ func (ic *ContainerEngine) playKubePVC(ctx context.Context, pvcYAML *v1.Persiste case util.VolumeUIDAnnotation: uid, err := strconv.Atoi(v) if err != nil { - return nil, errors.Wrapf(err, "cannot convert uid %s to integer", v) + return nil, fmt.Errorf("cannot convert uid %s to integer: %w", v, err) } volOptions = append(volOptions, libpod.WithVolumeUID(uid)) opts["UID"] = v case util.VolumeGIDAnnotation: gid, err := strconv.Atoi(v) if err != nil { - return nil, errors.Wrapf(err, "cannot convert gid %s to integer", v) + return nil, fmt.Errorf("cannot convert gid %s to integer: %w", v, err) } volOptions = append(volOptions, libpod.WithVolumeGID(gid)) opts["GID"] = v @@ -771,15 +771,15 @@ func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) { content, err := ioutil.ReadAll(r) if err != nil { - return cm, errors.Wrapf(err, "unable to read ConfigMap YAML content") + return cm, fmt.Errorf("unable to read ConfigMap YAML content: %w", err) } if err := yaml.Unmarshal(content, &cm); err != nil { - return cm, errors.Wrapf(err, "unable to read YAML as Kube ConfigMap") + return cm, fmt.Errorf("unable to read YAML as Kube ConfigMap: %w", err) } if cm.Kind != "ConfigMap" { - return cm, errors.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind) + return cm, fmt.Errorf("invalid YAML kind: %q. [ConfigMap] is the only supported by --configmap", cm.Kind) } return cm, nil @@ -790,7 +790,7 @@ func readConfigMapFromFile(r io.Reader) (v1.ConfigMap, error) { func splitMultiDocYAML(yamlContent []byte) ([][]byte, error) { var documentList [][]byte - d := yamlv2.NewDecoder(bytes.NewReader(yamlContent)) + d := yamlv3.NewDecoder(bytes.NewReader(yamlContent)) for { var o interface{} // read individual document @@ -799,14 +799,14 @@ func splitMultiDocYAML(yamlContent []byte) ([][]byte, error) { break } if err != nil { - return nil, errors.Wrapf(err, "multi doc yaml could not be split") + return nil, fmt.Errorf("multi doc yaml could not be split: %w", err) } if o != nil { // back to bytes - document, err := yamlv2.Marshal(o) + document, err := yamlv3.Marshal(o) if err != nil { - return nil, errors.Wrapf(err, "individual doc yaml could not be marshalled") + return nil, fmt.Errorf("individual doc yaml could not be marshalled: %w", err) } documentList = append(documentList, document) @@ -915,27 +915,27 @@ func (ic *ContainerEngine) PlayKubeDown(ctx context.Context, body io.Reader, _ e // sort kube kinds documentList, err = sortKubeKinds(documentList) if err != nil { - return nil, errors.Wrap(err, "unable to sort kube kinds") + return nil, fmt.Errorf("unable to sort kube kinds: %w", err) } for _, document := range documentList { kind, err := getKubeKind(document) if err != nil { - return nil, errors.Wrap(err, "unable to read as kube YAML") + return nil, fmt.Errorf("unable to read as kube YAML: %w", err) } switch kind { case "Pod": var podYAML v1.Pod if err := yaml.Unmarshal(document, &podYAML); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube Pod") + return nil, fmt.Errorf("unable to read YAML as Kube Pod: %w", err) } podNames = append(podNames, podYAML.ObjectMeta.Name) case "Deployment": var deploymentYAML v1apps.Deployment if err := yaml.Unmarshal(document, &deploymentYAML); err != nil { - return nil, errors.Wrap(err, "unable to read YAML as Kube Deployment") + return nil, fmt.Errorf("unable to read YAML as Kube Deployment: %w", err) } var numReplicas int32 = 1 deploymentName := deploymentYAML.ObjectMeta.Name diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 8638f4783..9a16f7fcd 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -2,6 +2,8 @@ package abi import ( "context" + "errors" + "fmt" "strconv" "strings" @@ -12,7 +14,6 @@ import ( "github.com/containers/podman/v4/pkg/signal" "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/specgen/generate" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -49,7 +50,7 @@ func getPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { _, err := ic.Libpod.LookupPod(nameOrID) - if err != nil && errors.Cause(err) != define.ErrNoSuchPod { + if err != nil && !errors.Is(err, define.ErrNoSuchPod) { return nil, err } return &entities.BoolReport{Value: err == nil}, nil @@ -69,14 +70,14 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt for _, p := range pods { report := entities.PodKillReport{Id: p.ID()} conErrs, err := p.Kill(ctx, uint(sig)) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} reports = append(reports, &report) continue } if len(conErrs) > 0 { for id, err := range conErrs { - report.Errs = append(report.Errs, errors.Wrapf(err, "error killing container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error killing container %s: %w", id, err)) } reports = append(reports, &report) continue @@ -110,7 +111,7 @@ func (ic *ContainerEngine) PodLogs(ctx context.Context, nameOrID string, options } } if !ctrFound { - return errors.Wrapf(define.ErrNoSuchCtr, "container %s is not in pod %s", options.ContainerName, nameOrID) + return fmt.Errorf("container %s is not in pod %s: %w", options.ContainerName, nameOrID, define.ErrNoSuchCtr) } } else { // No container name specified select all containers @@ -135,13 +136,13 @@ func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, op for _, p := range pods { report := entities.PodPauseReport{Id: p.ID()} errs, err := p.Pause(ctx) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} continue } if len(errs) > 0 { for id, v := range errs { - report.Errs = append(report.Errs, errors.Wrapf(v, "error pausing container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error pausing container %s: %w", id, v)) } reports = append(reports, &report) continue @@ -158,15 +159,23 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, return nil, err } for _, p := range pods { + status, err := p.GetPodStatus() + if err != nil { + return nil, err + } + // If the pod is not paused or degraded, there is no need to attempt an unpause on it + if status != define.PodStatePaused && status != define.PodStateDegraded { + continue + } report := entities.PodUnpauseReport{Id: p.ID()} errs, err := p.Unpause(ctx) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} continue } if len(errs) > 0 { for id, v := range errs { - report.Errs = append(report.Errs, errors.Wrapf(v, "error unpausing container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error unpausing container %s: %w", id, v)) } reports = append(reports, &report) continue @@ -179,19 +188,19 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, options entities.PodStopOptions) ([]*entities.PodStopReport, error) { reports := []*entities.PodStopReport{} pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) - if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { + if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchPod)) { return nil, err } for _, p := range pods { report := entities.PodStopReport{Id: p.ID()} errs, err := p.StopWithTimeout(ctx, false, options.Timeout) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} continue } if len(errs) > 0 { for id, v := range errs { - report.Errs = append(report.Errs, errors.Wrapf(v, "error stopping container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error stopping container %s: %w", id, v)) } reports = append(reports, &report) continue @@ -210,14 +219,14 @@ func (ic *ContainerEngine) PodRestart(ctx context.Context, namesOrIds []string, for _, p := range pods { report := entities.PodRestartReport{Id: p.ID()} errs, err := p.Restart(ctx) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} reports = append(reports, &report) continue } if len(errs) > 0 { for id, v := range errs { - report.Errs = append(report.Errs, errors.Wrapf(v, "error restarting container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error restarting container %s: %w", id, v)) } reports = append(reports, &report) continue @@ -237,14 +246,14 @@ func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, op for _, p := range pods { report := entities.PodStartReport{Id: p.ID()} errs, err := p.Start(ctx) - if err != nil && errors.Cause(err) != define.ErrPodPartialFail { + if err != nil && !errors.Is(err, define.ErrPodPartialFail) { report.Errs = []error{err} reports = append(reports, &report) continue } if len(errs) > 0 { for id, v := range errs { - report.Errs = append(report.Errs, errors.Wrapf(v, "error starting container %s", id)) + report.Errs = append(report.Errs, fmt.Errorf("error starting container %s: %w", id, v)) } reports = append(reports, &report) continue @@ -256,7 +265,7 @@ func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, op func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, options entities.PodRmOptions) ([]*entities.PodRmReport, error) { pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod) - if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { + if err != nil && !(options.Ignore && errors.Is(err, define.ErrNoSuchPod)) { return nil, err } reports := make([]*entities.PodRmReport, 0, len(pods)) @@ -393,7 +402,7 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp pod, err = ic.Libpod.LookupPod(options.NameOrID) } if err != nil { - return nil, errors.Wrap(err, "unable to lookup requested container") + return nil, fmt.Errorf("unable to look up requested container: %w", err) } // Run Top. @@ -402,6 +411,56 @@ func (ic *ContainerEngine) PodTop(ctx context.Context, options entities.PodTopOp return report, err } +func (ic *ContainerEngine) listPodReportFromPod(p *libpod.Pod) (*entities.ListPodsReport, error) { + status, err := p.GetPodStatus() + if err != nil { + return nil, err + } + cons, err := p.AllContainers() + if err != nil { + return nil, err + } + lpcs := make([]*entities.ListPodContainer, len(cons)) + for i, c := range cons { + state, err := c.State() + if err != nil { + return nil, err + } + lpcs[i] = &entities.ListPodContainer{ + Id: c.ID(), + Names: c.Name(), + Status: state.String(), + } + } + infraID, err := p.InfraContainerID() + if err != nil { + return nil, err + } + networks := []string{} + if len(infraID) > 0 { + infra, err := p.InfraContainer() + if err != nil { + return nil, err + } + networks, err = infra.Networks() + if err != nil { + return nil, err + } + } + return &entities.ListPodsReport{ + Cgroup: p.CgroupParent(), + Containers: lpcs, + Created: p.CreatedTime(), + Id: p.ID(), + InfraId: infraID, + Name: p.Name(), + Namespace: p.Namespace(), + Networks: networks, + Status: status, + Labels: p.Labels(), + }, nil +} + func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOptions) ([]*entities.ListPodsReport, error) { var ( err error @@ -431,53 +490,14 @@ func (ic *ContainerEngine) PodPs(ctx context.Context, options entities.PodPSOpti reports := make([]*entities.ListPodsReport, 0, len(pds)) for _, p := range pds { - var lpcs []*entities.ListPodContainer - status, err := p.GetPodStatus() - if err != nil { - return nil, err - } - cons, err := p.AllContainers() + r, err := ic.listPodReportFromPod(p) if err != nil { - return nil, err - } - for _, c := range cons { - state, err := c.State() - if err != nil { - return nil, err + if errors.Is(err, define.ErrNoSuchPod) || errors.Is(err, define.ErrNoSuchCtr) { + continue } - lpcs = append(lpcs, &entities.ListPodContainer{ - Id: c.ID(), - Names: c.Name(), - Status: state.String(), - }) - } - infraID, err := p.InfraContainerID() - if err != nil { return nil, err } - networks := []string{} - if len(infraID) > 0 { - infra, err := p.InfraContainer() - if err != nil { - return nil, err - } - networks, err = infra.Networks() - if err != nil { - return nil, err - } - } - reports = append(reports, &entities.ListPodsReport{ - Cgroup: p.CgroupParent(), - Containers: lpcs, - Created: p.CreatedTime(), - Id: p.ID(), - InfraId: infraID, - Name: p.Name(), - Namespace: p.Namespace(), - Networks: networks, - Status: status, - Labels: p.Labels(), - }) + reports = append(reports, r) } return reports, nil } @@ -494,7 +514,7 @@ func (ic *ContainerEngine) PodInspect(ctx context.Context, options entities.PodI pod, err = ic.Libpod.LookupPod(options.NameOrID) } if err != nil { - return nil, errors.Wrap(err, "unable to lookup requested container") + return nil, fmt.Errorf("unable to look up requested container: %w", err) } inspect, err := pod.Inspect() if err != nil { diff --git a/pkg/domain/infra/abi/secrets.go b/pkg/domain/infra/abi/secrets.go index b9380ad73..7321ef715 100644 --- a/pkg/domain/infra/abi/secrets.go +++ b/pkg/domain/infra/abi/secrets.go @@ -2,13 +2,14 @@ package abi import ( "context" + "fmt" "io" "io/ioutil" "path/filepath" + "strings" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/utils" - "github.com/pkg/errors" ) func (ic *ContainerEngine) SecretCreate(ctx context.Context, name string, reader io.Reader, options entities.SecretCreateOptions) (*entities.SecretCreateReport, error) { @@ -60,11 +61,11 @@ func (ic *ContainerEngine) SecretInspect(ctx context.Context, nameOrIDs []string for _, nameOrID := range nameOrIDs { secret, err := manager.Lookup(nameOrID) if err != nil { - if errors.Cause(err).Error() == "no such secret" { + if strings.Contains(err.Error(), "no such secret") { errs = append(errs, err) continue } else { - return nil, nil, errors.Wrapf(err, "error inspecting secret %s", nameOrID) + return nil, nil, fmt.Errorf("error inspecting secret %s: %w", nameOrID, err) } } report := &entities.SecretInfoReport{ @@ -141,7 +142,7 @@ func (ic *ContainerEngine) SecretRm(ctx context.Context, nameOrIDs []string, opt } for _, nameOrID := range toRemove { deletedID, err := manager.Delete(nameOrID) - if err == nil || errors.Cause(err).Error() == "no such secret" { + if err == nil || strings.Contains(err.Error(), "no such secret") { reports = append(reports, &entities.SecretRmReport{ Err: err, ID: deletedID, diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index 820ba529b..96690afef 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -2,6 +2,7 @@ package abi import ( "context" + "errors" "fmt" "net/url" "os" @@ -19,7 +20,6 @@ import ( "github.com/containers/podman/v4/utils" "github.com/containers/storage" "github.com/containers/storage/pkg/unshare" - "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/pflag" ) @@ -99,7 +99,7 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) } pausePidPath, err := util.GetRootlessPauseProcessPidPathGivenDir(tmpDir) if err != nil { - return errors.Wrapf(err, "could not get pause process pid file path") + return fmt.Errorf("could not get pause process pid file path: %w", err) } became, ret, err := rootless.TryJoinPauseProcess(pausePidPath) @@ -125,10 +125,16 @@ func (ic *ContainerEngine) SetupRootless(_ context.Context, noMoveProcess bool) paths = append(paths, ctr.Config().ConmonPidFile) } - became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) - utils.MovePauseProcessToScope(pausePidPath) + if len(paths) > 0 { + became, ret, err = rootless.TryJoinFromFilePaths(pausePidPath, true, paths) + } else { + became, ret, err = rootless.BecomeRootInUserNS(pausePidPath) + if err == nil { + utils.MovePauseProcessToScope(pausePidPath) + } + } if err != nil { - logrus.Error(errors.Wrapf(err, "invalid internal status, try resetting the pause process with %q", os.Args[0]+" system migrate")) + logrus.Error(fmt.Errorf("invalid internal status, try resetting the pause process with %q: %w", os.Args[0]+" system migrate", err)) os.Exit(1) } if became { @@ -265,22 +271,22 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System iid, _ := c.Image() state, err := c.State() if err != nil { - return nil, errors.Wrapf(err, "Failed to get state of container %s", c.ID()) + return nil, fmt.Errorf("failed to get state of container %s: %w", c.ID(), err) } conSize, err := c.RootFsSize() if err != nil { - if errors.Cause(err) == storage.ErrContainerUnknown { - logrus.Error(errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID())) + if errors.Is(err, storage.ErrContainerUnknown) { + logrus.Error(fmt.Errorf("failed to get root file system size of container %s: %w", c.ID(), err)) } else { - return nil, errors.Wrapf(err, "Failed to get root file system size of container %s", c.ID()) + return nil, fmt.Errorf("failed to get root file system size of container %s: %w", c.ID(), err) } } rwsize, err := c.RWSize() if err != nil { - if errors.Cause(err) == storage.ErrContainerUnknown { - logrus.Error(errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID())) + if errors.Is(err, storage.ErrContainerUnknown) { + logrus.Error(fmt.Errorf("failed to get read/write size of container %s: %w", c.ID(), err)) } else { - return nil, errors.Wrapf(err, "Failed to get read/write size of container %s", c.ID()) + return nil, fmt.Errorf("failed to get read/write size of container %s: %w", c.ID(), err) } } report := entities.SystemDfContainerReport{ @@ -313,8 +319,8 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } dfVolumes := make([]*entities.SystemDfVolumeReport, 0, len(vols)) - var reclaimableSize uint64 for _, v := range vols { + var reclaimableSize uint64 var consInUse int mountPoint, err := v.MountPoint() if err != nil { @@ -335,7 +341,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System return nil, err } if len(inUse) == 0 { - reclaimableSize += volSize + reclaimableSize = volSize } for _, viu := range inUse { if cutil.StringInSlice(viu, runningContainers) { @@ -398,9 +404,9 @@ func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options e } // Make sure to unlock, unshare can run for a long time. rootlessNetNS.Lock.Unlock() - // We do not want to cleanup the netns after unshare. - // The problem is that we cannot know if we need to cleanup and - // secondly unshare should allow user to setup the namespace with + // We do not want to clean up the netns after unshare. + // The problem is that we cannot know if we need to clean up and + // secondly unshare should allow user to set up the namespace with // special things, e.g. potentially macvlan or something like that. return rootlessNetNS.Do(unshare) } diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index e02c0532c..16d345f06 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -1,6 +1,7 @@ package terminal import ( + "errors" "os" "syscall" @@ -8,7 +9,6 @@ import ( "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/libpod/shutdown" "github.com/containers/podman/v4/pkg/signal" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -39,7 +39,7 @@ func ProxySignals(ctr *libpod.Container) { } if err := ctr.Kill(uint(s.(syscall.Signal))); err != nil { - if errors.Cause(err) == define.ErrCtrStateInvalid { + if errors.Is(err, define.ErrCtrStateInvalid) { logrus.Infof("Ceasing signal forwarding to container %s as it has stopped", ctr.ID()) } else { logrus.Errorf("forwarding signal %d to container %s: %v", s, ctr.ID(), err) diff --git a/pkg/domain/infra/abi/terminal/terminal.go b/pkg/domain/infra/abi/terminal/terminal.go index 692f8dcd5..45ebded26 100644 --- a/pkg/domain/infra/abi/terminal/terminal.go +++ b/pkg/domain/infra/abi/terminal/terminal.go @@ -5,7 +5,7 @@ import ( "os" "os/signal" - "github.com/containers/podman/v4/libpod/define" + "github.com/containers/common/pkg/resize" lsignal "github.com/containers/podman/v4/pkg/signal" "github.com/moby/term" "github.com/pkg/errors" @@ -18,20 +18,20 @@ type RawTtyFormatter struct { // getResize returns a TerminalSize command matching stdin's current // size on success, and nil on errors. -func getResize() *define.TerminalSize { +func getResize() *resize.TerminalSize { winsize, err := term.GetWinsize(os.Stdin.Fd()) if err != nil { logrus.Warnf("Could not get terminal size %v", err) return nil } - return &define.TerminalSize{ + return &resize.TerminalSize{ Width: winsize.Width, Height: winsize.Height, } } // Helper for prepareAttach - set up a goroutine to generate terminal resize events -func resizeTty(ctx context.Context, resize chan define.TerminalSize) { +func resizeTty(ctx context.Context, resize chan resize.TerminalSize) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, lsignal.SIGWINCH) go func() { @@ -78,7 +78,7 @@ func (f *RawTtyFormatter) Format(entry *logrus.Entry) ([]byte, error) { return bytes, err } -func handleTerminalAttach(ctx context.Context, resize chan define.TerminalSize) (context.CancelFunc, *term.State, error) { +func handleTerminalAttach(ctx context.Context, resize chan resize.TerminalSize) (context.CancelFunc, *term.State, error) { logrus.Debugf("Handling terminal attach") subCtx, cancel := context.WithCancel(ctx) diff --git a/pkg/domain/infra/abi/terminal/terminal_linux.go b/pkg/domain/infra/abi/terminal/terminal_linux.go index 62d36f28d..e8f338418 100644 --- a/pkg/domain/infra/abi/terminal/terminal_linux.go +++ b/pkg/domain/infra/abi/terminal/terminal_linux.go @@ -6,6 +6,7 @@ import ( "fmt" "os" + "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/pkg/errors" @@ -15,14 +16,14 @@ import ( // ExecAttachCtr execs and attaches to a container func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpod.ExecConfig, streams *define.AttachStreams) (int, error) { - var resize chan define.TerminalSize + var resizechan chan resize.TerminalSize haveTerminal := term.IsTerminal(int(os.Stdin.Fd())) // Check if we are attached to a terminal. If we are, generate resize // events, and set the terminal to raw mode if haveTerminal && execConfig.Terminal { - resize = make(chan define.TerminalSize) - cancel, oldTermState, err := handleTerminalAttach(ctx, resize) + resizechan = make(chan resize.TerminalSize) + cancel, oldTermState, err := handleTerminalAttach(ctx, resizechan) if err != nil { return -1, err } @@ -33,14 +34,14 @@ func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, execConfig *libpo } }() } - return ctr.Exec(execConfig, streams, resize) + return ctr.Exec(execConfig, streams, resizechan) } // StartAttachCtr starts and (if required) attaches to a container // if you change the signature of this function from os.File to io.Writer, it will trigger a downstream // error. we may need to just lint disable this one. func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, stdin *os.File, detachKeys string, sigProxy bool, startContainer bool) error { //nolint: interfacer - resize := make(chan define.TerminalSize) + resize := make(chan resize.TerminalSize) haveTerminal := term.IsTerminal(int(os.Stdin.Fd())) diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index a9c53c140..5e95a0551 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -2,6 +2,8 @@ package abi import ( "context" + "errors" + "fmt" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" @@ -9,7 +11,6 @@ import ( "github.com/containers/podman/v4/pkg/domain/entities/reports" "github.com/containers/podman/v4/pkg/domain/filters" "github.com/containers/podman/v4/pkg/domain/infra/abi/parse" - "github.com/pkg/errors" ) func (ic *ContainerEngine) VolumeCreate(ctx context.Context, opts entities.VolumeCreateOptions) (*entities.IDOrNameResponse, error) { @@ -91,11 +92,11 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin for _, v := range namesOrIds { vol, err := ic.Libpod.LookupVolume(v) if err != nil { - if errors.Cause(err) == define.ErrNoSuchVolume { - errs = append(errs, errors.Errorf("no such volume %s", v)) + if errors.Is(err, define.ErrNoSuchVolume) { + errs = append(errs, fmt.Errorf("no such volume %s", v)) continue } else { - return nil, nil, errors.Wrapf(err, "error inspecting volume %s", v) + return nil, nil, fmt.Errorf("error inspecting volume %s: %w", v, err) } } vols = append(vols, vol) @@ -211,3 +212,8 @@ func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string return reports, nil } + +func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) { + report := ic.Libpod.UpdateVolumePlugins(ctx) + return &entities.VolumeReloadReport{VolumeReload: *report}, nil +} diff --git a/pkg/domain/infra/runtime_libpod.go b/pkg/domain/infra/runtime_libpod.go index 03e7ffb5d..162025969 100644 --- a/pkg/domain/infra/runtime_libpod.go +++ b/pkg/domain/infra/runtime_libpod.go @@ -342,7 +342,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin options.HostUIDMapping = false options.HostGIDMapping = false - // Simply ignore the setting and do not setup an inner namespace for root as it is a no-op + // Simply ignore the setting and do not set up an inner namespace for root as it is a no-op return &options, nil } @@ -394,7 +394,7 @@ func ParseIDMapping(mode namespaces.UsernsMode, uidMapSlice, gidMapSlice []strin // StartWatcher starts a new SIGHUP go routine for the current config. func StartWatcher(rt *libpod.Runtime) { - // Setup the signal notifier + // Set up the signal notifier ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGHUP) diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index fb0be629c..5568ccde8 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -2,6 +2,7 @@ package tunnel import ( "context" + "errors" "fmt" "io" "os" @@ -23,7 +24,6 @@ import ( "github.com/containers/podman/v4/pkg/specgen" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/types" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -64,7 +64,7 @@ func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []stri reports := make([]*entities.PauseUnpauseReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Pause(ic.ClientCtx, c.ID, nil) - if err != nil && options.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { + if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running", c.ID) continue } @@ -81,7 +81,7 @@ func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []st } for _, c := range ctrs { err := containers.Unpause(ic.ClientCtx, c.ID, nil) - if err != nil && options.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { + if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not paused", c.ID) continue } @@ -111,11 +111,11 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin } if err = containers.Stop(ic.ClientCtx, c.ID, options); err != nil { // These first two are considered non-fatal under the right conditions - if errors.Cause(err).Error() == define.ErrCtrStopped.Error() { + if strings.Contains(err.Error(), define.ErrCtrStopped.Error()) { logrus.Debugf("Container %s is already stopped", c.ID) reports = append(reports, &report) continue - } else if opts.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { + } else if opts.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running, could not stop", c.ID) reports = append(reports, &report) continue @@ -146,7 +146,7 @@ func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []strin reports := make([]*entities.KillReport, 0, len(ctrs)) for _, c := range ctrs { err := containers.Kill(ic.ClientCtx, c.ID, options) - if err != nil && opts.All && errors.Cause(err).Error() == define.ErrCtrStateInvalid.Error() { + if err != nil && opts.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { logrus.Debugf("Container %s is not running", c.ID) continue } @@ -258,7 +258,7 @@ func (ic *ContainerEngine) ContainerInspect(ctx context.Context, namesOrIds []st return nil, nil, err } if errModel.ResponseCode == 404 { - errs = append(errs, errors.Errorf("no such container %q", name)) + errs = append(errs, fmt.Errorf("no such container %q", name)) continue } return nil, nil, err @@ -291,7 +291,7 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, if len(opts.ImageName) > 0 { ref, err := reference.Parse(opts.ImageName) if err != nil { - return nil, errors.Wrapf(err, "error parsing reference %q", opts.ImageName) + return nil, fmt.Errorf("error parsing reference %q: %w", opts.ImageName, err) } if t, ok := ref.(reference.Tagged); ok { tag = t.Tag() @@ -300,7 +300,7 @@ func (ic *ContainerEngine) ContainerCommit(ctx context.Context, nameOrID string, repo = r.Name() } if len(repo) < 1 { - return nil, errors.Errorf("invalid image name %q", opts.ImageName) + return nil, fmt.Errorf("invalid image name %q", opts.ImageName) } } options := new(containers.CommitOptions).WithAuthor(opts.Author).WithChanges(opts.Changes).WithComment(opts.Message).WithSquash(opts.Squash) @@ -502,7 +502,7 @@ func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrID string, } ctr := ctrs[0] if ctr.State != define.ContainerStateRunning.String() { - return errors.Errorf("you can only attach to running containers") + return fmt.Errorf("you can only attach to running containers") } options := new(containers.AttachOptions).WithStream(true).WithDetachKeys(opts.DetachKeys) return containers.Attach(ic.ClientCtx, nameOrID, opts.Stdin, opts.Stdout, opts.Stderr, nil, options) @@ -695,7 +695,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri report.ExitCode = define.ExitCode(report.Err) report.Err = err reports = append(reports, &report) - return reports, errors.Wrapf(report.Err, "unable to start container %s", name) + return reports, fmt.Errorf("unable to start container %s: %w", name, report.Err) } if ctr.AutoRemove { // Defer the removal, so we can return early if needed and @@ -739,7 +739,7 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri reports, err := containers.Remove(ic.ClientCtx, ctr.ID, rmOptions) logIfRmError(ctr.ID, err, reports) } - report.Err = errors.Wrapf(err, "unable to start container %q", name) + report.Err = fmt.Errorf("unable to start container %q: %w", name, err) report.ExitCode = define.ExitCode(err) reports = append(reports, &report) continue @@ -899,7 +899,7 @@ func (ic *ContainerEngine) ContainerInit(ctx context.Context, namesOrIds []strin err := containers.ContainerInit(ic.ClientCtx, ctr.ID, nil) // When using all, it is NOT considered an error if a container // has already been init'd. - if err != nil && options.All && strings.Contains(errors.Cause(err).Error(), define.ErrCtrStateInvalid.Error()) { + if err != nil && options.All && strings.Contains(err.Error(), define.ErrCtrStateInvalid.Error()) { err = nil } reports = append(reports, &entities.ContainerInitReport{ diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go index 5b14fac37..6c043465c 100644 --- a/pkg/domain/infra/tunnel/helpers.go +++ b/pkg/domain/infra/tunnel/helpers.go @@ -20,7 +20,7 @@ func getContainersByContext(contextWithConnection context.Context, all, ignore b func getContainersAndInputByContext(contextWithConnection context.Context, all, ignore bool, namesOrIDs []string) ([]entities.ListContainer, []string, error) { if all && len(namesOrIDs) > 0 { - return nil, nil, errors.New("cannot lookup containers and all") + return nil, nil, errors.New("cannot look up containers and all") } options := new(containers.ListOptions).WithAll(true).WithSync(true) allContainers, err := containers.List(contextWithConnection, options) @@ -77,7 +77,7 @@ func getContainersAndInputByContext(contextWithConnection context.Context, all, func getPodsByContext(contextWithConnection context.Context, all bool, namesOrIDs []string) ([]*entities.ListPodsReport, error) { if all && len(namesOrIDs) > 0 { - return nil, errors.New("cannot lookup specific pods and all") + return nil, errors.New("cannot look up specific pods and all") } allPods, err := pods.List(contextWithConnection, nil) diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 97838d596..09f8ac4c3 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -2,6 +2,7 @@ package tunnel import ( "context" + "fmt" "io/ioutil" "os" "strconv" @@ -12,7 +13,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" - "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/bindings/images" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/domain/entities/reports" @@ -28,7 +28,7 @@ func (ir *ImageEngine) Exists(_ context.Context, nameOrID string) (*entities.Boo } func (ir *ImageEngine) Remove(ctx context.Context, imagesArg []string, opts entities.ImageRemoveOptions) (*entities.ImageRemoveReport, []error) { - options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All) + options := new(images.RemoveOptions).WithForce(opts.Force).WithIgnore(opts.Ignore).WithAll(opts.All).WithLookupManifest(opts.LookupManifest) return images.Remove(ir.ClientCtx, imagesArg, options) } @@ -123,10 +123,6 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities. return &entities.ImagePullReport{Images: pulledImages}, nil } -func (ir *ImageEngine) Transfer(ctx context.Context, source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string) error { - return errors.Wrapf(define.ErrNotImplemented, "cannot use the remote client to transfer images between root and rootless storage") -} - func (ir *ImageEngine) Tag(ctx context.Context, nameOrID string, tags []string, opt entities.ImageTagOptions) error { options := new(images.TagOptions) for _, newTag := range tags { @@ -367,3 +363,23 @@ func (ir *ImageEngine) Shutdown(_ context.Context) { func (ir *ImageEngine) Sign(ctx context.Context, names []string, options entities.SignOptions) (*entities.SignReport, error) { return nil, errors.New("not implemented yet") } + +func (ir *ImageEngine) Scp(ctx context.Context, src, dst string, parentFlags []string, quiet bool) error { + options := new(images.ScpOptions) + + var destination *string + if len(dst) > 1 { + destination = &dst + } + options.Quiet = &quiet + options.Destination = destination + + rep, err := images.Scp(ir.ClientCtx, &src, destination, *options) + if err != nil { + return err + } + + fmt.Println("Loaded Image(s):", rep.Id) + + return nil +} diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 7b1fa231f..bcbd32d1b 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -2,12 +2,12 @@ package tunnel import ( "context" + "errors" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/bindings/pods" "github.com/containers/podman/v4/pkg/domain/entities" "github.com/containers/podman/v4/pkg/util" - "github.com/pkg/errors" ) func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { @@ -80,6 +80,10 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, } reports := make([]*entities.PodUnpauseReport, 0, len(foundPods)) for _, p := range foundPods { + // If the pod is not paused or degraded, there is no need to attempt an unpause on it + if p.Status != define.PodStatePaused && p.Status != define.PodStateDegraded { + continue + } response, err := pods.Unpause(ic.ClientCtx, p.Id, nil) if err != nil { report := entities.PodUnpauseReport{ @@ -97,7 +101,7 @@ func (ic *ContainerEngine) PodUnpause(ctx context.Context, namesOrIds []string, func (ic *ContainerEngine) PodStop(ctx context.Context, namesOrIds []string, opts entities.PodStopOptions) ([]*entities.PodStopReport, error) { timeout := -1 foundPods, err := getPodsByContext(ic.ClientCtx, opts.All, namesOrIds) - if err != nil && !(opts.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { + if err != nil && !(opts.Ignore && errors.Is(err, define.ErrNoSuchPod)) { return nil, err } if opts.Timeout != -1 { @@ -164,7 +168,7 @@ func (ic *ContainerEngine) PodStart(ctx context.Context, namesOrIds []string, op func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, opts entities.PodRmOptions) ([]*entities.PodRmReport, error) { foundPods, err := getPodsByContext(ic.ClientCtx, opts.All, namesOrIds) - if err != nil && !(opts.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { + if err != nil && !(opts.Ignore && errors.Is(err, define.ErrNoSuchPod)) { return nil, err } reports := make([]*entities.PodRmReport, 0, len(foundPods)) diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index 33e090148..6ec35e836 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -108,3 +108,7 @@ func (ic *ContainerEngine) VolumeMount(ctx context.Context, nameOrIDs []string) func (ic *ContainerEngine) VolumeUnmount(ctx context.Context, nameOrIDs []string) ([]*entities.VolumeUnmountReport, error) { return nil, errors.New("unmounting volumes is not supported for remote clients") } + +func (ic *ContainerEngine) VolumeReload(ctx context.Context) (*entities.VolumeReloadReport, error) { + return nil, errors.New("volume reload is not supported for remote clients") +} diff --git a/pkg/domain/utils/scp.go b/pkg/domain/utils/scp.go new file mode 100644 index 000000000..a4ff6b950 --- /dev/null +++ b/pkg/domain/utils/scp.go @@ -0,0 +1,581 @@ +package utils + +import ( + "bytes" + "fmt" + "io/ioutil" + "net" + "net/url" + "os" + "os/exec" + "os/user" + "strconv" + "strings" + "time" + + scpD "github.com/dtylman/scp" + + "github.com/containers/common/pkg/config" + "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/terminal" + "github.com/docker/distribution/reference" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" +) + +func ExecuteTransfer(src, dst string, parentFlags []string, quiet bool) (*entities.ImageLoadReport, *entities.ImageScpOptions, *entities.ImageScpOptions, []string, error) { + source := entities.ImageScpOptions{} + dest := entities.ImageScpOptions{} + sshInfo := entities.ImageScpConnections{} + report := entities.ImageLoadReport{Names: []string{}} + + podman, err := os.Executable() + if err != nil { + return nil, nil, nil, nil, err + } + + f, err := ioutil.TempFile("", "podman") // open temp file for load/save output + if err != nil { + return nil, nil, nil, nil, err + } + + confR, err := config.NewConfig("") // create a hand made config for the remote engine since we might use remote and native at once + if err != nil { + return nil, nil, nil, nil, errors.Wrapf(err, "could not make config") + } + + cfg, err := config.ReadCustomConfig() // get ready to set ssh destination if necessary + if err != nil { + return nil, nil, nil, nil, err + } + locations := []*entities.ImageScpOptions{} + cliConnections := []string{} + args := []string{src} + if len(dst) > 0 { + args = append(args, dst) + } + for _, arg := range args { + loc, connect, err := ParseImageSCPArg(arg) + if err != nil { + return nil, nil, nil, nil, err + } + locations = append(locations, loc) + cliConnections = append(cliConnections, connect...) + } + source = *locations[0] + switch { + case len(locations) > 1: + if err = ValidateSCPArgs(locations); err != nil { + return nil, nil, nil, nil, err + } + dest = *locations[1] + case len(locations) == 1: + switch { + case len(locations[0].Image) == 0: + return nil, nil, nil, nil, errors.Wrapf(define.ErrInvalidArg, "no source image specified") + case len(locations[0].Image) > 0 && !locations[0].Remote && len(locations[0].User) == 0: // if we have podman image scp $IMAGE + return nil, nil, nil, nil, errors.Wrapf(define.ErrInvalidArg, "must specify a destination") + } + } + + source.Quiet = quiet + source.File = f.Name() // after parsing the arguments, set the file for the save/load + dest.File = source.File + if err = os.Remove(source.File); err != nil { // remove the file and simply use its name so podman creates the file upon save. avoids umask errors + return nil, nil, nil, nil, err + } + + allLocal := true // if we are all localhost, do not validate connections but if we are using one localhost and one non we need to use sshd + for _, val := range cliConnections { + if !strings.Contains(val, "@localhost::") { + allLocal = false + break + } + } + if allLocal { + cliConnections = []string{} + } + + var serv map[string]config.Destination + serv, err = GetServiceInformation(&sshInfo, cliConnections, cfg) + if err != nil { + return nil, nil, nil, nil, err + } + + confR.Engine = config.EngineConfig{Remote: true, CgroupManager: "cgroupfs", ServiceDestinations: serv} // pass the service dest (either remote or something else) to engine + saveCmd, loadCmd := CreateCommands(source, dest, parentFlags, podman) + + switch { + case source.Remote: // if we want to load FROM the remote, dest can either be local or remote in this case + err = SaveToRemote(source.Image, source.File, "", sshInfo.URI[0], sshInfo.Identities[0]) + if err != nil { + return nil, nil, nil, nil, err + } + if dest.Remote { // we want to load remote -> remote, both source and dest are remote + rep, id, err := LoadToRemote(dest, dest.File, "", sshInfo.URI[1], sshInfo.Identities[1]) + if err != nil { + return nil, nil, nil, nil, err + } + if len(rep) > 0 { + fmt.Println(rep) + } + if len(id) > 0 { + report.Names = append(report.Names, id) + } + break + } + id, err := ExecPodman(dest, podman, loadCmd) + if err != nil { + return nil, nil, nil, nil, err + } + if len(id) > 0 { + report.Names = append(report.Names, id) + } + case dest.Remote: // remote host load, implies source is local + _, err = ExecPodman(dest, podman, saveCmd) + if err != nil { + return nil, nil, nil, nil, err + } + rep, id, err := LoadToRemote(dest, source.File, "", sshInfo.URI[0], sshInfo.Identities[0]) + if err != nil { + return nil, nil, nil, nil, err + } + if len(rep) > 0 { + fmt.Println(rep) + } + if len(id) > 0 { + report.Names = append(report.Names, id) + } + if err = os.Remove(source.File); err != nil { + return nil, nil, nil, nil, err + } + default: // else native load, both source and dest are local and transferring between users + if source.User == "" { // source user has to be set, destination does not + source.User = os.Getenv("USER") + if source.User == "" { + u, err := user.Current() + if err != nil { + return nil, nil, nil, nil, errors.Wrapf(err, "could not obtain user, make sure the environmental variable $USER is set") + } + source.User = u.Username + } + } + return nil, &source, &dest, parentFlags, nil // transfer needs to be done in ABI due to cross issues + } + + return &report, nil, nil, nil, nil +} + +// CreateSCPCommand takes an existing command, appends the given arguments and returns a configured podman command for image scp +func CreateSCPCommand(cmd *exec.Cmd, command []string) *exec.Cmd { + cmd.Args = append(cmd.Args, command...) + cmd.Env = os.Environ() + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + return cmd +} + +// ScpTag is a helper function for native podman to tag an image after a local load from image SCP +func ScpTag(cmd *exec.Cmd, podman string, dest entities.ImageScpOptions) error { + cmd.Stdout = nil + out, err := cmd.Output() // this function captures the output temporarily in order to execute the next command + if err != nil { + return err + } + image := ExtractImage(out) + if cmd.Args[0] == "sudo" { // transferRootless will need the sudo since we are loading to sudo from a user acct + cmd = exec.Command("sudo", podman, "tag", image, dest.Tag) + } else { + cmd = exec.Command(podman, "tag", image, dest.Tag) + } + cmd.Stdout = os.Stdout + return cmd.Run() +} + +// ExtractImage pulls out the last line of output from save/load (image id) +func ExtractImage(out []byte) string { + fmt.Println(strings.TrimSuffix(string(out), "\n")) // print output + stringOut := string(out) // get all output + arrOut := strings.Split(stringOut, " ") // split it into an array + return strings.ReplaceAll(arrOut[len(arrOut)-1], "\n", "") // replace the trailing \n +} + +// LoginUser starts the user process on the host so that image scp can use systemd-run +func LoginUser(user string) (*exec.Cmd, error) { + sleep, err := exec.LookPath("sleep") + if err != nil { + return nil, err + } + machinectl, err := exec.LookPath("machinectl") + if err != nil { + return nil, err + } + cmd := exec.Command(machinectl, "shell", "-q", user+"@.host", sleep, "inf") + err = cmd.Start() + return cmd, err +} + +// loadToRemote takes image and remote connection information. it connects to the specified client +// and copies the saved image dir over to the remote host and then loads it onto the machine +// returns a string containing output or an error +func LoadToRemote(dest entities.ImageScpOptions, localFile string, tag string, url *url.URL, iden string) (string, string, error) { + dial, remoteFile, err := CreateConnection(url, iden) + if err != nil { + return "", "", err + } + defer dial.Close() + + n, err := scpD.CopyTo(dial, localFile, remoteFile) + if err != nil { + errOut := strconv.Itoa(int(n)) + " Bytes copied before error" + return " ", "", errors.Wrapf(err, errOut) + } + var run string + if tag != "" { + return "", "", errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") + } + podman := os.Args[0] + run = podman + " image load --input=" + remoteFile + ";rm " + remoteFile // run ssh image load of the file copied via scp + out, err := ExecRemoteCommand(dial, run) + if err != nil { + return "", "", err + } + rep := strings.TrimSuffix(string(out), "\n") + outArr := strings.Split(rep, " ") + id := outArr[len(outArr)-1] + if len(dest.Tag) > 0 { // tag the remote image using the output ID + run = podman + " tag " + id + " " + dest.Tag + _, err = ExecRemoteCommand(dial, run) + if err != nil { + return "", "", err + } + } + return rep, id, nil +} + +// saveToRemote takes image information and remote connection information. it connects to the specified client +// and saves the specified image on the remote machine and then copies it to the specified local location +// returns an error if one occurs. +func SaveToRemote(image, localFile string, tag string, uri *url.URL, iden string) error { + dial, remoteFile, err := CreateConnection(uri, iden) + + if err != nil { + return err + } + defer dial.Close() + + if tag != "" { + return errors.Wrapf(define.ErrInvalidArg, "Renaming of an image is currently not supported") + } + podman := os.Args[0] + run := podman + " image save " + image + " --format=oci-archive --output=" + remoteFile // run ssh image load of the file copied via scp. Files are reverse in this case... + _, err = ExecRemoteCommand(dial, run) + if err != nil { + return err + } + n, err := scpD.CopyFrom(dial, remoteFile, localFile) + if _, conErr := ExecRemoteCommand(dial, "rm "+remoteFile); conErr != nil { + logrus.Errorf("Removing file on endpoint: %v", conErr) + } + if err != nil { + errOut := strconv.Itoa(int(n)) + " Bytes copied before error" + return errors.Wrapf(err, errOut) + } + return nil +} + +// makeRemoteFile creates the necessary remote file on the host to +// save or load the image to. returns a string with the file name or an error +func MakeRemoteFile(dial *ssh.Client) (string, error) { + run := "mktemp" + remoteFile, err := ExecRemoteCommand(dial, run) + if err != nil { + return "", err + } + return strings.TrimSuffix(string(remoteFile), "\n"), nil +} + +// createConnections takes a boolean determining which ssh client to dial +// and returns the dials client, its newly opened remote file, and an error if applicable. +func CreateConnection(url *url.URL, iden string) (*ssh.Client, string, error) { + cfg, err := ValidateAndConfigure(url, iden) + if err != nil { + return nil, "", err + } + dialAdd, err := ssh.Dial("tcp", url.Host, cfg) // dial the client + if err != nil { + return nil, "", errors.Wrapf(err, "failed to connect") + } + file, err := MakeRemoteFile(dialAdd) + if err != nil { + return nil, "", err + } + + return dialAdd, file, nil +} + +// GetSerivceInformation takes the parsed list of hosts to connect to and validates the information +func GetServiceInformation(sshInfo *entities.ImageScpConnections, cliConnections []string, cfg *config.Config) (map[string]config.Destination, error) { + var serv map[string]config.Destination + var urlS string + var iden string + for i, val := range cliConnections { + splitEnv := strings.SplitN(val, "::", 2) + sshInfo.Connections = append(sshInfo.Connections, splitEnv[0]) + conn, found := cfg.Engine.ServiceDestinations[sshInfo.Connections[i]] + if found { + urlS = conn.URI + iden = conn.Identity + } else { // no match, warn user and do a manual connection. + urlS = "ssh://" + sshInfo.Connections[i] + iden = "" + logrus.Warnf("Unknown connection name given. Please use system connection add to specify the default remote socket location") + } + urlFinal, err := url.Parse(urlS) // create an actual url to pass to exec command + if err != nil { + return nil, err + } + if urlFinal.User.Username() == "" { + if urlFinal.User, err = GetUserInfo(urlFinal); err != nil { + return nil, err + } + } + sshInfo.URI = append(sshInfo.URI, urlFinal) + sshInfo.Identities = append(sshInfo.Identities, iden) + } + return serv, nil +} + +// execPodman executes the podman save/load command given the podman binary +func ExecPodman(dest entities.ImageScpOptions, podman string, command []string) (string, error) { + cmd := exec.Command(podman) + CreateSCPCommand(cmd, command[1:]) + logrus.Debugf("Executing podman command: %q", cmd) + if strings.Contains(strings.Join(command, " "), "load") { // need to tag + if len(dest.Tag) > 0 { + return "", ScpTag(cmd, podman, dest) + } + cmd.Stdout = nil + out, err := cmd.Output() + if err != nil { + return "", err + } + img := ExtractImage(out) + return img, nil + } + return "", cmd.Run() +} + +// createCommands forms the podman save and load commands used by SCP +func CreateCommands(source entities.ImageScpOptions, dest entities.ImageScpOptions, parentFlags []string, podman string) ([]string, []string) { + var parentString string + quiet := "" + if source.Quiet { + quiet = "-q " + } + if len(parentFlags) > 0 { + parentString = strings.Join(parentFlags, " ") + " " // if there are parent args, an extra space needs to be added + } else { + parentString = strings.Join(parentFlags, " ") + } + loadCmd := strings.Split(fmt.Sprintf("%s %sload %s--input %s", podman, parentString, quiet, dest.File), " ") + saveCmd := strings.Split(fmt.Sprintf("%s %vsave %s--output %s %s", podman, parentString, quiet, source.File, source.Image), " ") + return saveCmd, loadCmd +} + +// parseImageSCPArg returns the valid connection, and source/destination data based off of the information provided by the user +// arg is a string containing one of the cli arguments returned is a filled out source/destination options structs as well as a connections array and an error if applicable +func ParseImageSCPArg(arg string) (*entities.ImageScpOptions, []string, error) { + location := entities.ImageScpOptions{} + var err error + cliConnections := []string{} + + switch { + case strings.Contains(arg, "@localhost::"): // image transfer between users + location.User = strings.Split(arg, "@")[0] + location, err = ValidateImagePortion(location, arg) + if err != nil { + return nil, nil, err + } + cliConnections = append(cliConnections, arg) + case strings.Contains(arg, "::"): + location, err = ValidateImagePortion(location, arg) + if err != nil { + return nil, nil, err + } + location.Remote = true + cliConnections = append(cliConnections, arg) + default: + location.Image = arg + } + return &location, cliConnections, nil +} + +// validateImagePortion is a helper function to validate the image name in an SCP argument +func ValidateImagePortion(location entities.ImageScpOptions, arg string) (entities.ImageScpOptions, error) { + if RemoteArgLength(arg, 1) > 0 { + err := ValidateImageName(strings.Split(arg, "::")[1]) + if err != nil { + return location, err + } + location.Image = strings.Split(arg, "::")[1] // this will get checked/set again once we validate connections + } + return location, nil +} + +// validateSCPArgs takes the array of source and destination options and checks for common errors +func ValidateSCPArgs(locations []*entities.ImageScpOptions) error { + if len(locations) > 2 { + return errors.Wrapf(define.ErrInvalidArg, "cannot specify more than two arguments") + } + switch { + case len(locations[0].Image) > 0 && len(locations[1].Image) > 0: + locations[1].Tag = locations[1].Image + locations[1].Image = "" + case len(locations[0].Image) == 0 && len(locations[1].Image) == 0: + return errors.Wrapf(define.ErrInvalidArg, "a source image must be specified") + } + return nil +} + +// validateImageName makes sure that the image given is valid and no injections are occurring +// we simply use this for error checking, bot setting the image +func ValidateImageName(input string) error { + // ParseNormalizedNamed transforms a shortname image into its + // full name reference so busybox => docker.io/library/busybox + // we want to keep our shortnames, so only return an error if + // we cannot parse what the user has given us + _, err := reference.ParseNormalizedNamed(input) + return err +} + +// remoteArgLength is a helper function to simplify the extracting of host argument data +// returns an int which contains the length of a specified index in a host::image string +func RemoteArgLength(input string, side int) int { + if strings.Contains(input, "::") { + return len((strings.Split(input, "::"))[side]) + } + return -1 +} + +// ExecRemoteCommand takes a ssh client connection and a command to run and executes the +// command on the specified client. The function returns the Stdout from the client or the Stderr +func ExecRemoteCommand(dial *ssh.Client, run string) ([]byte, error) { + sess, err := dial.NewSession() // new ssh client session + if err != nil { + return nil, err + } + defer sess.Close() + + var buffer bytes.Buffer + var bufferErr bytes.Buffer + sess.Stdout = &buffer // output from client funneled into buffer + sess.Stderr = &bufferErr // err form client funneled into buffer + if err := sess.Run(run); err != nil { // run the command on the ssh client + return nil, errors.Wrapf(err, bufferErr.String()) + } + return buffer.Bytes(), nil +} + +func GetUserInfo(uri *url.URL) (*url.Userinfo, error) { + var ( + usr *user.User + err error + ) + if u, found := os.LookupEnv("_CONTAINERS_ROOTLESS_UID"); found { + usr, err = user.LookupId(u) + if err != nil { + return nil, errors.Wrapf(err, "failed to lookup rootless user") + } + } else { + usr, err = user.Current() + if err != nil { + return nil, errors.Wrapf(err, "failed to obtain current user") + } + } + + pw, set := uri.User.Password() + if set { + return url.UserPassword(usr.Username, pw), nil + } + return url.User(usr.Username), nil +} + +// ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid +// iden iden can be blank to mean no identity key +// once the function validates the information it creates and returns an ssh.ClientConfig. +func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) { + var signers []ssh.Signer + passwd, passwdSet := uri.User.Password() + if iden != "" { // iden might be blank if coming from image scp or if no validation is needed + value := iden + s, err := terminal.PublicKey(value, []byte(passwd)) + if err != nil { + return nil, errors.Wrapf(err, "failed to read identity %q", value) + } + signers = append(signers, s) + logrus.Debugf("SSH Ident Key %q %s %s", value, ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found { // validate ssh information, specifically the unix file socket used by the ssh agent. + logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock) + + c, err := net.Dial("unix", sock) + if err != nil { + return nil, err + } + agentSigners, err := agent.NewClient(c).Signers() + if err != nil { + return nil, err + } + + signers = append(signers, agentSigners...) + + if logrus.IsLevelEnabled(logrus.DebugLevel) { + for _, s := range agentSigners { + logrus.Debugf("SSH Agent Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + } + } + var authMethods []ssh.AuthMethod // now we validate and check for the authorization methods, most notaibly public key authorization + if len(signers) > 0 { + var dedup = make(map[string]ssh.Signer) + for _, s := range signers { + fp := ssh.FingerprintSHA256(s.PublicKey()) + if _, found := dedup[fp]; found { + logrus.Debugf("Dedup SSH Key %s %s", ssh.FingerprintSHA256(s.PublicKey()), s.PublicKey().Type()) + } + dedup[fp] = s + } + + var uniq []ssh.Signer + for _, s := range dedup { + uniq = append(uniq, s) + } + authMethods = append(authMethods, ssh.PublicKeysCallback(func() ([]ssh.Signer, error) { + return uniq, nil + })) + } + if passwdSet { // if password authentication is given and valid, add to the list + authMethods = append(authMethods, ssh.Password(passwd)) + } + if len(authMethods) == 0 { + authMethods = append(authMethods, ssh.PasswordCallback(func() (string, error) { + pass, err := terminal.ReadPassword(fmt.Sprintf("%s's login password:", uri.User.Username())) + return string(pass), err + })) + } + tick, err := time.ParseDuration("40s") + if err != nil { + return nil, err + } + cfg := &ssh.ClientConfig{ + User: uri.User.Username(), + Auth: authMethods, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: tick, + } + return cfg, nil +} diff --git a/pkg/domain/utils/utils_test.go b/pkg/domain/utils/utils_test.go index 952a4b5be..291567f6b 100644 --- a/pkg/domain/utils/utils_test.go +++ b/pkg/domain/utils/utils_test.go @@ -5,6 +5,7 @@ import ( "sort" "testing" + "github.com/containers/podman/v4/pkg/domain/entities" "github.com/stretchr/testify/assert" ) @@ -74,3 +75,41 @@ func TestToURLValues(t *testing.T) { }) } } + +func TestParseSCPArgs(t *testing.T) { + args := []string{"alpine", "root@localhost::"} + var source *entities.ImageScpOptions + var dest *entities.ImageScpOptions + var err error + source, _, err = ParseImageSCPArg(args[0]) + assert.Nil(t, err) + assert.Equal(t, source.Image, "alpine") + + dest, _, err = ParseImageSCPArg(args[1]) + assert.Nil(t, err) + assert.Equal(t, dest.Image, "") + assert.Equal(t, dest.User, "root") + + args = []string{"root@localhost::alpine"} + source, _, err = ParseImageSCPArg(args[0]) + assert.Nil(t, err) + assert.Equal(t, source.User, "root") + assert.Equal(t, source.Image, "alpine") + + args = []string{"charliedoern@192.168.68.126::alpine", "foobar@192.168.68.126::"} + source, _, err = ParseImageSCPArg(args[0]) + assert.Nil(t, err) + assert.True(t, source.Remote) + assert.Equal(t, source.Image, "alpine") + + dest, _, err = ParseImageSCPArg(args[1]) + assert.Nil(t, err) + assert.True(t, dest.Remote) + assert.Equal(t, dest.Image, "") + + args = []string{"charliedoern@192.168.68.126::alpine"} + source, _, err = ParseImageSCPArg(args[0]) + assert.Nil(t, err) + assert.True(t, source.Remote) + assert.Equal(t, source.Image, "alpine") +} diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index fc6772c08..9b456c9c0 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -1,11 +1,11 @@ package errorhandling import ( + "errors" "os" "strings" "github.com/hashicorp/go-multierror" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -121,3 +121,22 @@ func (e PodConflictErrorModel) Error() string { func (e PodConflictErrorModel) Code() int { return 409 } + +// Cause returns the most underlying error for the provided one. There is a +// maximum error depth of 100 to avoid endless loops. An additional error log +// message will be created if this maximum has reached. +func Cause(err error) (cause error) { + cause = err + + const maxDepth = 100 + for i := 0; i <= maxDepth; i++ { + res := errors.Unwrap(cause) + if res == nil { + return cause + } + cause = res + } + + logrus.Errorf("Max error depth of %d reached, cannot unwrap until root cause: %v", maxDepth, err) + return cause +} diff --git a/pkg/errorhandling/errorhandling_test.go b/pkg/errorhandling/errorhandling_test.go new file mode 100644 index 000000000..ec720c5e7 --- /dev/null +++ b/pkg/errorhandling/errorhandling_test.go @@ -0,0 +1,53 @@ +package errorhandling + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCause(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + err func() error + expectedErr error + }{ + { + name: "nil error", + err: func() error { return nil }, + expectedErr: nil, + }, + { + name: "equal errors", + err: func() error { return errors.New("foo") }, + expectedErr: errors.New("foo"), + }, + { + name: "wrapped error", + err: func() error { return fmt.Errorf("baz: %w", fmt.Errorf("bar: %w", errors.New("foo"))) }, + expectedErr: errors.New("foo"), + }, + { + name: "max depth reached", + err: func() error { + err := errors.New("error") + for i := 0; i <= 101; i++ { + err = fmt.Errorf("%d: %w", i, err) + } + return err + }, + expectedErr: fmt.Errorf("0: %w", errors.New("error")), + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + err := Cause(tc.err()) + assert.Equal(t, tc.expectedErr, err) + }) + } +} diff --git a/pkg/hooks/exec/runtimeconfigfilter_test.go b/pkg/hooks/exec/runtimeconfigfilter_test.go index 5c13a76e1..a4e9b1fdb 100644 --- a/pkg/hooks/exec/runtimeconfigfilter_test.go +++ b/pkg/hooks/exec/runtimeconfigfilter_test.go @@ -3,12 +3,12 @@ package exec import ( "context" "encoding/json" + "errors" "os" "testing" "time" spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -18,13 +18,14 @@ func TestRuntimeConfigFilter(t *testing.T) { rootUint32 := uint32(0) binUser := int(1) for _, tt := range []struct { - name string - contextTimeout time.Duration - hooks []spec.Hook - input *spec.Spec - expected *spec.Spec - expectedHookError string - expectedRunError error + name string + contextTimeout time.Duration + hooks []spec.Hook + input *spec.Spec + expected *spec.Spec + expectedHookError string + expectedRunError error + expectedRunErrorString string }{ { name: "no-op", @@ -231,7 +232,8 @@ func TestRuntimeConfigFilter(t *testing.T) { Path: "rootfs", }, }, - expectedRunError: unexpectedEndOfJSONInput, + expectedRunError: unexpectedEndOfJSONInput, + expectedRunErrorString: unexpectedEndOfJSONInput.Error(), }, } { test := tt @@ -243,7 +245,13 @@ func TestRuntimeConfigFilter(t *testing.T) { defer cancel() } hookErr, err := RuntimeConfigFilter(ctx, test.hooks, test.input, DefaultPostKillTimeout) - assert.Equal(t, test.expectedRunError, errors.Cause(err)) + if test.expectedRunError != nil { + if test.expectedRunErrorString != "" { + assert.Contains(t, err.Error(), test.expectedRunErrorString) + } else { + assert.True(t, errors.Is(err, test.expectedRunError)) + } + } if test.expectedHookError == "" { if hookErr != nil { t.Fatal(hookErr) diff --git a/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go b/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go index d05984dac..69613321f 100644 --- a/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go +++ b/pkg/k8s.io/apimachinery/pkg/api/resource/amount.go @@ -48,7 +48,7 @@ const ( var ( Zero = int64Amount{} - // Used by quantity strings - treat as read only + // Used by quantity strings - treat as read-only zeroBytes = []byte("0") ) diff --git a/pkg/k8s.io/apimachinery/pkg/api/resource/math.go b/pkg/k8s.io/apimachinery/pkg/api/resource/math.go index 9d03f5c05..59a4c14de 100644 --- a/pkg/k8s.io/apimachinery/pkg/api/resource/math.go +++ b/pkg/k8s.io/apimachinery/pkg/api/resource/math.go @@ -29,13 +29,13 @@ const ( ) var ( - // Commonly needed big.Int values-- treat as read only! + // Commonly needed big.Int values-- treat as read-only! bigTen = big.NewInt(10) bigZero = big.NewInt(0) bigOne = big.NewInt(1) big1024 = big.NewInt(1024) - // Commonly needed inf.Dec values-- treat as read only! + // Commonly needed inf.Dec values-- treat as read-only! decZero = inf.NewDec(0, 0) decOne = inf.NewDec(1, 0) diff --git a/pkg/kubeutils/LICENSE b/pkg/kubeutils/LICENSE deleted file mode 100644 index 9b259bdfc..000000000 --- a/pkg/kubeutils/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/pkg/kubeutils/resize.go b/pkg/kubeutils/resize.go deleted file mode 100644 index a744c66cc..000000000 --- a/pkg/kubeutils/resize.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2015 The Kubernetes Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package kubeutils - -import ( - "github.com/containers/podman/v4/libpod/define" -) - -// HandleResizing spawns a goroutine that processes the resize channel, calling resizeFunc for each -// remotecommand.TerminalSize received from the channel. The resize channel must be closed elsewhere to stop the -// goroutine. -func HandleResizing(resize <-chan define.TerminalSize, resizeFunc func(size define.TerminalSize)) { - if resize == nil { - return - } - - go func() { - for { - size, ok := <-resize - if !ok { - return - } - if size.Height < 1 || size.Width < 1 { - continue - } - resizeFunc(size) - } - }() -} diff --git a/pkg/machine/config_test.go b/pkg/machine/config_test.go index d9fc5425e..ca08660b9 100644 --- a/pkg/machine/config_test.go +++ b/pkg/machine/config_test.go @@ -1,3 +1,6 @@ +//go:build amd64 || arm64 +// +build amd64 arm64 + package machine import ( diff --git a/pkg/machine/e2e/list_test.go b/pkg/machine/e2e/list_test.go index 1c8c6ac81..e2121e7bf 100644 --- a/pkg/machine/e2e/list_test.go +++ b/pkg/machine/e2e/list_test.go @@ -29,7 +29,7 @@ var _ = Describe("podman machine list", func() { firstList, err := mb.setCmd(list).run() Expect(err).NotTo(HaveOccurred()) Expect(firstList).Should(Exit(0)) - Expect(len(firstList.outputToStringSlice())).To(Equal(1)) // just the header + Expect(firstList.outputToStringSlice()).To(HaveLen(1)) // just the header i := new(initMachine) session, err := mb.setCmd(i.withImagePath(mb.imagePath)).run() @@ -39,7 +39,7 @@ var _ = Describe("podman machine list", func() { secondList, err := mb.setCmd(list).run() Expect(err).NotTo(HaveOccurred()) Expect(secondList).To(Exit(0)) - Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // one machine and the header + Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // one machine and the header }) It("list machines with quiet or noheading", func() { @@ -51,12 +51,12 @@ var _ = Describe("podman machine list", func() { firstList, err := mb.setCmd(list.withQuiet()).run() Expect(err).NotTo(HaveOccurred()) Expect(firstList).Should(Exit(0)) - Expect(len(firstList.outputToStringSlice())).To(Equal(0)) // No header with quiet + Expect(firstList.outputToStringSlice()).To(HaveLen(0)) // No header with quiet noheaderSession, err := mb.setCmd(list.withNoHeading()).run() // noheader Expect(err).NotTo(HaveOccurred()) Expect(noheaderSession).Should(Exit(0)) - Expect(len(noheaderSession.outputToStringSlice())).To(Equal(0)) + Expect(noheaderSession.outputToStringSlice()).To(HaveLen(0)) i := new(initMachine) session, err := mb.setName(name1).setCmd(i.withImagePath(mb.imagePath)).run() @@ -70,7 +70,7 @@ var _ = Describe("podman machine list", func() { secondList, err := mb.setCmd(list.withQuiet()).run() Expect(err).NotTo(HaveOccurred()) Expect(secondList).To(Exit(0)) - Expect(len(secondList.outputToStringSlice())).To(Equal(2)) // two machines, no header + Expect(secondList.outputToStringSlice()).To(HaveLen(2)) // two machines, no header listNames := secondList.outputToStringSlice() stripAsterisk(listNames) @@ -116,10 +116,10 @@ var _ = Describe("podman machine list", func() { // go format list := new(listMachine) - listSession, err := mb.setCmd(list.withFormat("{{.Name}}").withNoHeading()).run() + listSession, err := mb.setCmd(list.withFormat("{{.Name}}")).run() Expect(err).NotTo(HaveOccurred()) Expect(listSession).To(Exit(0)) - Expect(len(listSession.outputToStringSlice())).To(Equal(1)) + Expect(listSession.outputToStringSlice()).To(HaveLen(1)) listNames := listSession.outputToStringSlice() stripAsterisk(listNames) @@ -135,6 +135,15 @@ var _ = Describe("podman machine list", func() { var listResponse []*machine.ListReporter err = jsoniter.Unmarshal(listSession.Bytes(), &listResponse) Expect(err).To(BeNil()) + + // table format includes the header + list = new(listMachine) + listSession3, err3 := mb.setCmd(list.withFormat("table {{.Name}}")).run() + Expect(err3).NotTo(HaveOccurred()) + Expect(listSession3).To(Exit(0)) + listNames3 := listSession3.outputToStringSlice() + Expect(listNames3).To(HaveLen(2)) + Expect(listNames3).To(ContainSubstring("NAME")) }) }) diff --git a/pkg/machine/ignition.go b/pkg/machine/ignition.go index 35a9a30cb..f4602cc95 100644 --- a/pkg/machine/ignition.go +++ b/pkg/machine/ignition.go @@ -93,7 +93,7 @@ func NewIgnitionFile(ign DynamicIgnition) error { tz string ) // local means the same as the host - // lookup where it is pointing to on the host + // look up where it is pointing to on the host if ign.TimeZone == "local" { tz, err = getLocalTimeZone() if err != nil { @@ -348,7 +348,7 @@ Delegate=memory pids cpu io }, }) - // Setup /etc/subuid and /etc/subgid + // Set up /etc/subuid and /etc/subgid for _, sub := range []string{"/etc/subuid", "/etc/subgid"} { files = append(files, File{ Node: Node{ diff --git a/pkg/machine/keys.go b/pkg/machine/keys.go index 45d9801cc..463271427 100644 --- a/pkg/machine/keys.go +++ b/pkg/machine/keys.go @@ -4,14 +4,15 @@ package machine import ( - "errors" "fmt" + "io" "io/ioutil" "os" "os/exec" "path/filepath" "strings" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -51,7 +52,23 @@ func CreateSSHKeysPrefix(dir string, file string, passThru bool, skipExisting bo // generatekeys creates an ed25519 set of keys func generatekeys(writeLocation string) error { args := append(append([]string{}, sshCommand[1:]...), writeLocation) - return exec.Command(sshCommand[0], args...).Run() + cmd := exec.Command(sshCommand[0], args...) + stdErr, err := cmd.StderrPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + waitErr := cmd.Wait() + if waitErr == nil { + return nil + } + errMsg, err := io.ReadAll(stdErr) + if err != nil { + return fmt.Errorf("key generation failed, unable to read from stderr: %w", waitErr) + } + return fmt.Errorf("failed to generate keys: %s: %w", string(errMsg), waitErr) } // generatekeys creates an ed25519 set of keys diff --git a/pkg/machine/qemu/config.go b/pkg/machine/qemu/config.go index 56c95e3b3..bada1af9b 100644 --- a/pkg/machine/qemu/config.go +++ b/pkg/machine/qemu/config.go @@ -72,8 +72,10 @@ type MachineVM struct { Mounts []machine.Mount // Name of VM Name string - // PidFilePath is the where the PID file lives + // PidFilePath is the where the Proxy PID file lives PidFilePath machine.VMFile + // VMPidFilePath is the where the VM PID file lives + VMPidFilePath machine.VMFile // QMPMonitor is the qemu monitor object for sending commands QMPMonitor Monitor // ReadySocket tells host when vm is booted diff --git a/pkg/machine/qemu/config_test.go b/pkg/machine/qemu/config_test.go index 4d96ec6e7..72cb3ed90 100644 --- a/pkg/machine/qemu/config_test.go +++ b/pkg/machine/qemu/config_test.go @@ -1,3 +1,6 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) +// +build amd64,!windows arm64,!windows + package qemu import ( diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 288b2eeb0..ca7947e34 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -8,6 +8,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io/fs" "io/ioutil" @@ -30,8 +31,8 @@ import ( "github.com/containers/storage/pkg/homedir" "github.com/digitalocean/go-qemu/qmp" "github.com/docker/go-units" - "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) var ( @@ -107,6 +108,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { if err != nil { return nil, err } + if err := vm.setPIDSocket(); err != nil { + return nil, err + } cmd := []string{execPath} // Add memory cmd = append(cmd, []string{"-m", strconv.Itoa(int(vm.Memory))}...) @@ -135,11 +139,9 @@ func (p *Provider) NewMachine(opts machine.InitOptions) (machine.VM, error) { "-device", "virtio-serial", // qemu needs to establish the long name; other connections can use the symlink'd "-chardev", "socket,path=" + vm.ReadySocket.Path + ",server=on,wait=off,id=" + vm.Name + "_ready", - "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0"}...) + "-device", "virtserialport,chardev=" + vm.Name + "_ready" + ",name=org.fedoraproject.port.0", + "-pidfile", vm.VMPidFilePath.GetPath()}...) vm.CmdLine = cmd - if err := vm.setPIDSocket(); err != nil { - return nil, err - } return vm, nil } @@ -209,7 +211,7 @@ func migrateVM(configPath string, config []byte, vm *MachineVM) error { vm.Rootful = old.Rootful vm.UID = old.UID - // Backup the original config file + // Back up the original config file if err := os.Rename(configPath, configPath+".orig"); err != nil { return err } @@ -432,12 +434,12 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if v.Name != machine.DefaultMachineName { suffix = " " + v.Name } - return setErrors, errors.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) + return setErrors, fmt.Errorf("cannot change settings while the vm is running, run 'podman machine stop%s' first", suffix) } if opts.Rootful != nil && v.Rootful != *opts.Rootful { if err := v.setRootful(*opts.Rootful); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to set rootful option")) + setErrors = append(setErrors, fmt.Errorf("failed to set rootful option: %w", err)) } else { v.Rootful = *opts.Rootful } @@ -455,7 +457,7 @@ func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { if opts.DiskSize != nil && v.DiskSize != *opts.DiskSize { if err := v.resizeDisk(*opts.DiskSize, v.DiskSize); err != nil { - setErrors = append(setErrors, errors.Wrapf(err, "failed to resize disk")) + setErrors = append(setErrors, fmt.Errorf("failed to resize disk: %w", err)) } else { v.DiskSize = *opts.DiskSize } @@ -512,7 +514,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { forwardSock, forwardState, err := v.startHostNetworking() if err != nil { - return errors.Errorf("unable to start host networking: %q", err) + return fmt.Errorf("unable to start host networking: %q", err) } rtPath, err := getRuntimeDir() @@ -580,7 +582,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { if !errors.Is(err, os.ErrNotExist) { return err } - // lookup qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394 + // look up qemu again maybe the path was changed, https://github.com/containers/podman/issues/13394 cfg, err := config.Default() if err != nil { return err @@ -591,7 +593,7 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } _, err = os.StartProcess(cmd[0], cmd, attr) if err != nil { - return errors.Wrapf(err, "unable to execute %q", cmd) + return fmt.Errorf("unable to execute %q: %w", cmd, err) } } fmt.Println("Waiting for VM ...") @@ -698,7 +700,7 @@ func (v *MachineVM) checkStatus(monitor *qmp.SocketMonitor) (machine.Status, err } b, err := monitor.Run(input) if err != nil { - if errors.Cause(err) == os.ErrNotExist { + if errors.Is(err, os.ErrNotExist) { return machine.Stopped, nil } return "", err @@ -753,17 +755,17 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { if _, err := os.Stat(v.PidFilePath.GetPath()); os.IsNotExist(err) { return nil } - pidString, err := v.PidFilePath.Read() + proxyPidString, err := v.PidFilePath.Read() if err != nil { return err } - pidNum, err := strconv.Atoi(string(pidString)) + proxyPid, err := strconv.Atoi(string(proxyPidString)) if err != nil { return err } - p, err := os.FindProcess(pidNum) - if p == nil && err != nil { + proxyProc, err := os.FindProcess(proxyPid) + if proxyProc == nil && err != nil { return err } @@ -772,7 +774,7 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { return err } // Kill the process - if err := p.Kill(); err != nil { + if err := proxyProc.Kill(); err != nil { return err } // Remove the pidfile @@ -788,22 +790,50 @@ func (v *MachineVM) Stop(_ string, _ machine.StopOptions) error { // FIXME: this error should probably be returned return nil //nolint: nilerr } - disconnected = true - waitInternal := 250 * time.Millisecond - for i := 0; i < 5; i++ { - state, err := v.State(false) - if err != nil { - return err - } - if state != machine.Running { - break + + if err := v.ReadySocket.Delete(); err != nil { + return err + } + + if v.VMPidFilePath.GetPath() == "" { + // no vm pid file path means it's probably a machine created before we + // started using it, so we revert to the old way of waiting for the + // machine to stop + fmt.Println("Waiting for VM to stop running...") + waitInternal := 250 * time.Millisecond + for i := 0; i < 5; i++ { + state, err := v.State(false) + if err != nil { + return err + } + if state != machine.Running { + break + } + time.Sleep(waitInternal) + waitInternal *= 2 } - time.Sleep(waitInternal) - waitInternal *= 2 + // after the machine stops running it normally takes about 1 second for the + // qemu VM to exit so we wait a bit to try to avoid issues + time.Sleep(2 * time.Second) + return nil + } + + vmPidString, err := v.VMPidFilePath.Read() + if err != nil { + return err + } + vmPid, err := strconv.Atoi(strings.TrimSpace(string(vmPidString))) + if err != nil { + return err + } + + fmt.Println("Waiting for VM to exit...") + for isProcessAlive(vmPid) { + time.Sleep(500 * time.Millisecond) } - return v.ReadySocket.Delete() + return nil } // NewQMPMonitor creates the monitor subsection of our vm @@ -849,7 +879,7 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() } if state == machine.Running { if !opts.Force { - return "", nil, errors.Errorf("running vm %q cannot be destroyed", v.Name) + return "", nil, fmt.Errorf("running vm %q cannot be destroyed", v.Name) } err := v.Stop(v.Name, machine.StopOptions{}) if err != nil { @@ -896,8 +926,11 @@ func (v *MachineVM) Remove(_ string, opts machine.RemoveOptions) (string, func() // remove socket and pid file if any: warn at low priority if things fail // Remove the pidfile + if err := v.VMPidFilePath.Delete(); err != nil { + logrus.Debugf("Error while removing VM pidfile: %v", err) + } if err := v.PidFilePath.Delete(); err != nil { - logrus.Debugf("Error while removing pidfile: %v", err) + logrus.Debugf("Error while removing proxy pidfile: %v", err) } // Remove socket if err := v.QMPMonitor.Address.Delete(); err != nil { @@ -930,7 +963,12 @@ func (v *MachineVM) State(bypass bool) (machine.Status, error) { } monitor, err := qmp.NewSocketMonitor(v.QMPMonitor.Network, v.QMPMonitor.Address.GetPath(), v.QMPMonitor.Timeout) if err != nil { - // FIXME: this error should probably be returned + // If an improper cleanup was done and the socketmonitor was not deleted, + // it can appear as though the machine state is not stopped. Check for ECONNREFUSED + // almost assures us that the vm is stopped. + if errors.Is(err, syscall.ECONNREFUSED) { + return machine.Stopped, nil + } return "", err } if err := monitor.Connect(); err != nil { @@ -963,7 +1001,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { return err } if state != machine.Running { - return errors.Errorf("vm %q is not running", v.Name) + return fmt.Errorf("vm %q is not running", v.Name) } username := opts.Username @@ -975,7 +1013,7 @@ func (v *MachineVM) SSH(_ string, opts machine.SSHOptions) error { port := strconv.Itoa(v.Port) args := []string{"-i", v.IdentityPath, "-p", port, sshDestination, "-o", "UserKnownHostsFile=/dev/null", - "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR"} + "-o", "StrictHostKeyChecking=no", "-o", "LogLevel=ERROR", "-o", "SetEnv=LC_ALL="} if len(opts.Args) > 0 { args = append(args, opts.Args...) } else { @@ -1074,6 +1112,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { listEntry.RemoteUsername = vm.RemoteUsername listEntry.IdentityPath = vm.IdentityPath listEntry.CreatedAt = vm.Created + listEntry.Starting = vm.Starting if listEntry.CreatedAt.IsZero() { listEntry.CreatedAt = time.Now() @@ -1087,6 +1126,7 @@ func getVMInfos() ([]*machine.ListResponse, error) { if err != nil { return err } + listEntry.Running = state == machine.Running if !vm.LastUp.IsZero() { // this means we have already written a time to the config listEntry.LastUp = vm.LastUp @@ -1097,12 +1137,6 @@ func getVMInfos() ([]*machine.ListResponse, error) { return err } } - switch state { - case machine.Running: - listEntry.Running = true - case machine.Starting: - listEntry.Starting = true - } listed = append(listed, listEntry) } @@ -1131,7 +1165,7 @@ func (p *Provider) IsValidVMName(name string) (bool, error) { func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { vms, err := getVMInfos() if err != nil { - return false, "", errors.Wrap(err, "error checking VM active") + return false, "", fmt.Errorf("error checking VM active: %w", err) } for _, vm := range vms { if vm.Running || vm.Starting { @@ -1142,7 +1176,7 @@ func (p *Provider) CheckExclusiveActiveVM() (bool, string, error) { } // startHostNetworking runs a binary on the host system that allows users -// to setup port forwarding to the podman virtual machine +// to set up port forwarding to the podman virtual machine func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { cfg, err := config.Default() if err != nil { @@ -1183,7 +1217,10 @@ func (v *MachineVM) startHostNetworking() (string, apiForwardingState, error) { fmt.Println(cmd) } _, err = os.StartProcess(cmd[0], cmd, attr) - return forwardSock, state, errors.Wrapf(err, "unable to execute: %q", cmd) + if err != nil { + return "", 0, fmt.Errorf("unable to execute: %q: %w", cmd, err) + } + return forwardSock, state, nil } func (v *MachineVM) setupAPIForwarding(cmd []string) ([]string, string, apiForwardingState) { @@ -1309,13 +1346,19 @@ func (v *MachineVM) setPIDSocket() error { if !rootless.IsRootless() { rtPath = "/run" } - pidFileName := fmt.Sprintf("%s.pid", v.Name) socketDir := filepath.Join(rtPath, "podman") - pidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, pidFileName), &pidFileName) + vmPidFileName := fmt.Sprintf("%s_vm.pid", v.Name) + proxyPidFileName := fmt.Sprintf("%s_proxy.pid", v.Name) + vmPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, vmPidFileName), &vmPidFileName) + if err != nil { + return err + } + proxyPidFilePath, err := machine.NewMachineFile(filepath.Join(socketDir, proxyPidFileName), &proxyPidFileName) if err != nil { return err } - v.PidFilePath = *pidFilePath + v.VMPidFilePath = *vmPidFilePath + v.PidFilePath = *proxyPidFilePath return nil } @@ -1446,7 +1489,7 @@ func (v *MachineVM) update() error { b, err := v.ConfigPath.Read() if err != nil { if errors.Is(err, os.ErrNotExist) { - return errors.Wrap(machine.ErrNoSuchVM, v.Name) + return fmt.Errorf("%v: %w", v.Name, machine.ErrNoSuchVM) } return err } @@ -1522,7 +1565,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { // only if the virtualdisk size is less than // the given disk size if diskSize < oldSize { - return errors.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) + return fmt.Errorf("new disk size must be larger than current disk size: %vGB", oldSize) } // Find the qemu executable @@ -1538,7 +1581,7 @@ func (v *MachineVM) resizeDisk(diskSize uint64, oldSize uint64) error { resize.Stdout = os.Stdout resize.Stderr = os.Stderr if err := resize.Run(); err != nil { - return errors.Errorf("resizing image: %q", err) + return fmt.Errorf("resizing image: %q", err) } return nil @@ -1652,3 +1695,12 @@ func (p *Provider) RemoveAndCleanMachines() error { } return prevErr } + +func isProcessAlive(pid int) bool { + err := unix.Kill(pid, syscall.Signal(0)) + if err == nil || err == unix.EPERM { + return true + } + + return false +} diff --git a/pkg/machine/qemu/machine_test.go b/pkg/machine/qemu/machine_test.go index 62ca6068a..4c393d0f4 100644 --- a/pkg/machine/qemu/machine_test.go +++ b/pkg/machine/qemu/machine_test.go @@ -1,3 +1,6 @@ +//go:build (amd64 && !windows) || (arm64 && !windows) +// +build amd64,!windows arm64,!windows + package qemu import ( diff --git a/pkg/machine/wsl/machine.go b/pkg/machine/wsl/machine.go index 075f42cb2..04215d545 100644 --- a/pkg/machine/wsl/machine.go +++ b/pkg/machine/wsl/machine.go @@ -18,6 +18,7 @@ import ( "strings" "time" + "github.com/containers/common/pkg/config" "github.com/containers/podman/v4/pkg/machine" "github.com/containers/podman/v4/utils" "github.com/containers/storage/pkg/homedir" @@ -116,6 +117,43 @@ ln -fs /home/[USER]/.config/systemd/[USER]/linger-example.service \ /home/[USER]/.config/systemd/[USER]/default.target.wants/linger-example.service ` +const proxyConfigSetup = `#!/bin/bash + +SYSTEMD_CONF=/etc/systemd/system.conf.d/default-env.conf +ENVD_CONF=/etc/environment.d/default-env.conf +PROFILE_CONF=/etc/profile.d/default-env.sh + +IFS="|" +read proxies + +mkdir -p /etc/profile.d /etc/environment.d /etc/systemd/system.conf.d/ +rm -f $SYSTEMD_CONF +for proxy in $proxies; do + output+="$proxy " +done +echo "[Manager]" >> $SYSTEMD_CONF +echo -ne "DefaultEnvironment=" >> $SYSTEMD_CONF + +echo $output >> $SYSTEMD_CONF +rm -f $ENVD_CONF +for proxy in $proxies; do + echo "$proxy" >> $ENVD_CONF +done +rm -f $PROFILE_CONF +for proxy in $proxies; do + echo "export $proxy" >> $PROFILE_CONF +done +` + +const proxyConfigAttempt = `if [ -f /usr/local/bin/proxyinit ]; \ +then /usr/local/bin/proxyinit; \ +else exit 42; \ +fi` + +const clearProxySettings = `rm -f /etc/systemd/system.conf.d/default-env.conf \ + /etc/environment.d/default-env.conf \ + /etc/profile.d/default-env.sh` + const wslInstallError = `Could not %s. See previous output for any potential failure details. If you can not resolve the issue, and rerunning fails, try the "wsl --install" process outlined in the following article: @@ -300,6 +338,7 @@ func (v *MachineVM) Init(opts machine.InitOptions) (bool, error) { return cont, err } + _ = setupWslProxyEnv() homeDir := homedir.Get() sshDir := filepath.Join(homeDir, ".ssh") v.IdentityPath = filepath.Join(sshDir, v.Name) @@ -526,6 +565,40 @@ func configureSystem(v *MachineVM, dist string) error { return nil } +func configureProxy(dist string, useProxy bool) error { + if !useProxy { + _ = runCmdPassThrough("wsl", "-d", dist, "sh", "-c", clearProxySettings) + return nil + } + var content string + for i, key := range config.ProxyEnv { + if value, _ := os.LookupEnv(key); len(value) > 0 { + var suffix string + if i < (len(config.ProxyEnv) - 1) { + suffix = "|" + } + content = fmt.Sprintf("%s%s=\"%s\"%s", content, key, value, suffix) + } + } + + if err := pipeCmdPassThrough("wsl", content, "-d", dist, "sh", "-c", proxyConfigAttempt); err != nil { + const failMessage = "Failure creating proxy configuration" + if exitErr, isExit := err.(*exec.ExitError); isExit && exitErr.ExitCode() != 42 { + return errors.Wrap(err, failMessage) + } + + fmt.Println("Installing proxy support") + _ = pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit") + + if err = pipeCmdPassThrough("wsl", content, "-d", dist, "/usr/local/bin/proxyinit"); err != nil { + return errors.Wrap(err, failMessage) + } + } + + return nil +} + func enableUserLinger(v *MachineVM, dist string) error { lingerCmd := "mkdir -p /var/lib/systemd/linger; touch /var/lib/systemd/linger/" + v.RemoteUsername if err := runCmdPassThrough("wsl", "-d", dist, "sh", "-c", lingerCmd); err != nil { @@ -555,6 +628,11 @@ func installScripts(dist string) error { return errors.Wrap(err, "could not create bootstrap script for guest OS") } + if err := pipeCmdPassThrough("wsl", proxyConfigSetup, "-d", dist, "sh", "-c", + "cat > /usr/local/bin/proxyinit; chmod 755 /usr/local/bin/proxyinit"); err != nil { + return errors.Wrap(err, "could not create proxyinit script for guest OS") + } + return nil } @@ -816,6 +894,26 @@ func pipeCmdPassThrough(name string, input string, arg ...string) error { return cmd.Run() } +func setupWslProxyEnv() (hasProxy bool) { + current, _ := os.LookupEnv("WSLENV") + for _, key := range config.ProxyEnv { + if value, _ := os.LookupEnv(key); len(value) < 1 { + continue + } + + hasProxy = true + delim := "" + if len(current) > 0 { + delim = ":" + } + current = fmt.Sprintf("%s%s%s/u", current, delim, key) + } + if hasProxy { + os.Setenv("WSLENV", current) + } + return +} + func (v *MachineVM) Set(_ string, opts machine.SetOptions) ([]error, error) { // If one setting fails to be applied, the others settings will not fail and still be applied. // The setting(s) that failed to be applied will have its errors returned in setErrors @@ -852,6 +950,10 @@ func (v *MachineVM) Start(name string, _ machine.StartOptions) error { } dist := toDist(name) + useProxy := setupWslProxyEnv() + if err := configureProxy(dist, useProxy); err != nil { + return err + } err := runCmdPassThrough("wsl", "-d", dist, "/root/bootstrap") if err != nil { diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go index c95f8e275..8eacb8da7 100644 --- a/pkg/namespaces/namespaces.go +++ b/pkg/namespaces/namespaces.go @@ -112,7 +112,7 @@ func (n UsernsMode) IsDefaultValue() bool { return n == "" || n == defaultType } -// GetAutoOptions returns a AutoUserNsOptions with the settings to setup automatically +// GetAutoOptions returns a AutoUserNsOptions with the settings to automatically set up // a user namespace. func (n UsernsMode) GetAutoOptions() (*types.AutoUserNsOptions, error) { parts := strings.SplitN(string(n), ":", 2) diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 5fe1b83e2..7fc01de31 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -1,6 +1,8 @@ package ps import ( + "errors" + "fmt" "os" "path/filepath" "regexp" @@ -16,7 +18,6 @@ import ( psdefine "github.com/containers/podman/v4/pkg/ps/define" "github.com/containers/storage" "github.com/containers/storage/types" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -65,7 +66,7 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp for _, con := range cons { listCon, err := ListContainerBatch(runtime, con, options) switch { - case errors.Cause(err) == define.ErrNoSuchCtr: + case errors.Is(err, define.ErrNoSuchCtr): continue case err != nil: return nil, err @@ -108,7 +109,7 @@ func GetExternalContainerLists(runtime *libpod.Runtime) ([]entities.ListContaine for _, con := range externCons { listCon, err := ListStorageContainer(runtime, con) switch { - case errors.Cause(err) == types.ErrLoadError: + case errors.Is(err, types.ErrLoadError): continue case err != nil: return nil, err @@ -138,19 +139,19 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities batchErr := ctr.Batch(func(c *libpod.Container) error { if opts.Sync { if err := c.Sync(); err != nil { - return errors.Wrapf(err, "unable to update container state from OCI runtime") + return fmt.Errorf("unable to update container state from OCI runtime: %w", err) } } conConfig = c.Config() conState, err = c.State() if err != nil { - return errors.Wrapf(err, "unable to obtain container state") + return fmt.Errorf("unable to obtain container state: %w", err) } exitCode, exited, err = c.ExitCode() if err != nil { - return errors.Wrapf(err, "unable to obtain container exit code") + return fmt.Errorf("unable to obtain container exit code: %w", err) } startedTime, err = c.StartedTime() if err != nil { @@ -163,7 +164,7 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities pid, err = c.PID() if err != nil { - return errors.Wrapf(err, "unable to obtain container pid") + return fmt.Errorf("unable to obtain container pid: %w", err) } if !opts.Size && !opts.Namespace { @@ -237,8 +238,8 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities if opts.Pod && len(conConfig.Pod) > 0 { podName, err := rt.GetName(conConfig.Pod) if err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { - return entities.ListContainer{}, errors.Wrapf(define.ErrNoSuchPod, "could not find container %s pod (id %s) in state", conConfig.ID, conConfig.Pod) + if errors.Is(err, define.ErrNoSuchCtr) { + return entities.ListContainer{}, fmt.Errorf("could not find container %s pod (id %s) in state: %w", conConfig.ID, conConfig.Pod, define.ErrNoSuchPod) } return entities.ListContainer{}, err } @@ -282,7 +283,7 @@ func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container) (entities.L buildahCtr, err := rt.IsBuildahContainer(ctr.ID) if err != nil { - return ps, errors.Wrapf(err, "error determining buildah container for container %s", ctr.ID) + return ps, fmt.Errorf("error determining buildah container for container %s: %w", ctr.ID, err) } if buildahCtr { @@ -311,7 +312,7 @@ func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container) (entities.L func getNamespaceInfo(path string) (string, error) { val, err := os.Readlink(path) if err != nil { - return "", errors.Wrapf(err, "error getting info from %q", path) + return "", fmt.Errorf("error getting info from %q: %w", path, err) } return getStrFromSquareBrackets(val), nil } diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 94bd40f86..3588313c6 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -178,7 +178,7 @@ get_cmd_line_args () char *tmp = realloc (buffer, allocated); if (tmp == NULL) return NULL; - buffer = tmp; + buffer = tmp; } } @@ -243,7 +243,7 @@ can_use_shortcut () } if (argv[argc+1] != NULL && (strcmp (argv[argc], "container") == 0 || - strcmp (argv[argc], "image") == 0) && + strcmp (argv[argc], "image") == 0) && (strcmp (argv[argc+1], "mount") == 0 || strcmp (argv[argc+1], "scp") == 0)) { ret = false; @@ -512,7 +512,9 @@ create_pause_process (const char *pause_pid_file_path, char **argv) r = TEMP_FAILURE_RETRY (read (p[0], &b, 1)); close (p[0]); - reexec_in_user_namespace_wait (pid, 0); + r = reexec_in_user_namespace_wait (pid, 0); + if (r != 0) + return -1; return r == 1 && b == '0' ? 0 : -1; } @@ -757,6 +759,7 @@ reexec_userns_join (int pid_to_join, char *pause_pid_file_path) } execvp (argv[0], argv); + fprintf (stderr, "failed to execvp %s: %m\n", argv[0]); _exit (EXIT_FAILURE); } @@ -788,7 +791,10 @@ copy_file_to_fd (const char *file_to_read, int outfd) fd = open (file_to_read, O_RDONLY); if (fd < 0) - return fd; + { + fprintf (stderr, "open `%s`: %m\n", file_to_read); + return fd; + } for (;;) { @@ -796,7 +802,10 @@ copy_file_to_fd (const char *file_to_read, int outfd) r = TEMP_FAILURE_RETRY (read (fd, buf, sizeof buf)); if (r < 0) - return r; + { + fprintf (stderr, "read from `%s`: %m\n", file_to_read); + return r; + } if (r == 0) break; @@ -805,7 +814,10 @@ copy_file_to_fd (const char *file_to_read, int outfd) { w = TEMP_FAILURE_RETRY (write (outfd, &buf[t], r - t)); if (w < 0) - return w; + { + fprintf (stderr, "write file to output fd `%s`: %m\n", file_to_read); + return w; + } t += w; } } diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 5af9a978b..fde621b72 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -154,7 +154,7 @@ func tryMappingTool(uid bool, pid int, hostID int, mappings []idtools.IDMap) err if output, err := cmd.CombinedOutput(); err != nil { logrus.Errorf("running `%s`: %s", strings.Join(args, " "), output) - errorStr := fmt.Sprintf("cannot setup namespace using %q", path) + errorStr := fmt.Sprintf("cannot set up namespace using %q", path) if isSet, err := unshare.IsSetID(cmd.Path, mode, cap); err != nil { logrus.Errorf("Failed to check for %s on %s: %v", idtype, path, err) } else if !isSet { @@ -182,7 +182,7 @@ func joinUserAndMountNS(pid uint, pausePid string) (bool, int, error) { pidC := C.reexec_userns_join(C.int(pid), cPausePid) if int(pidC) < 0 { - return false, -1, errors.Errorf("cannot re-exec process") + return false, -1, errors.Errorf("cannot re-exec process to join the existing user namespace") } ret := C.reexec_in_user_namespace_wait(pidC, 0) @@ -303,7 +303,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo if retErr != nil && pid > 0 { if err := unix.Kill(pid, unix.SIGKILL); err != nil { if err != unix.ESRCH { - logrus.Errorf("Failed to cleanup process %d: %v", pid, err) + logrus.Errorf("Failed to clean up process %d: %v", pid, err) } } C.reexec_in_user_namespace_wait(C.int(pid), 0) @@ -461,13 +461,8 @@ func BecomeRootInUserNS(pausePid string) (bool, int, error) { // different uidmap and the unprivileged user has no way to read the // file owned by the root in the container. func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []string) (bool, int, error) { - if len(paths) == 0 { - return BecomeRootInUserNS(pausePidPath) - } - var lastErr error var pausePid int - foundProcess := false for _, path := range paths { if !needNewNamespace { @@ -479,12 +474,9 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st pausePid, err = strconv.Atoi(string(data)) if err != nil { - lastErr = errors.Wrapf(err, "cannot parse file %s", path) + lastErr = errors.Wrapf(err, "cannot parse file %q", path) continue } - - lastErr = nil - break } else { r, w, err := os.Pipe() if err != nil { @@ -511,26 +503,29 @@ func TryJoinFromFilePaths(pausePidPath string, needNewNamespace bool, paths []st n, err := r.Read(b) if err != nil { - lastErr = errors.Wrapf(err, "cannot read %s\n", path) + lastErr = errors.Wrapf(err, "cannot read %q", path) continue } pausePid, err = strconv.Atoi(string(b[:n])) - if err == nil && unix.Kill(pausePid, 0) == nil { - foundProcess = true - lastErr = nil - break + if err != nil { + lastErr = err + continue } } - } - if !foundProcess && pausePidPath != "" { - return BecomeRootInUserNS(pausePidPath) + + if pausePid > 0 && unix.Kill(pausePid, 0) == nil { + joined, pid, err := joinUserAndMountNS(uint(pausePid), pausePidPath) + if err == nil { + return joined, pid, nil + } + lastErr = err + } } if lastErr != nil { return false, 0, lastErr } - - return joinUserAndMountNS(uint(pausePid), pausePidPath) + return false, 0, errors.Wrapf(unix.ESRCH, "could not find any running process") } // ReadMappingsProc parses and returns the ID mappings at the specified path. diff --git a/pkg/signal/signal_common.go b/pkg/signal/signal_common.go index fe5a76dae..fc1ecc04d 100644 --- a/pkg/signal/signal_common.go +++ b/pkg/signal/signal_common.go @@ -2,6 +2,8 @@ package signal import ( "fmt" + "os" + "os/signal" "strconv" "strings" "syscall" @@ -39,3 +41,18 @@ func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) { } return -1, fmt.Errorf("invalid signal: %s", basename) } + +// CatchAll catches all signals and relays them to the specified channel. +func CatchAll(sigc chan os.Signal) { + handledSigs := make([]os.Signal, 0, len(SignalMap)) + for _, s := range SignalMap { + handledSigs = append(handledSigs, s) + } + signal.Notify(sigc, handledSigs...) +} + +// StopCatch stops catching the signals and closes the specified channel. +func StopCatch(sigc chan os.Signal) { + signal.Stop(sigc) + close(sigc) +} diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go index a114ea019..5103b6033 100644 --- a/pkg/signal/signal_linux.go +++ b/pkg/signal/signal_linux.go @@ -9,8 +9,6 @@ package signal // NOTE: this package has originally been copied from github.com/docker/docker. import ( - "os" - "os/signal" "syscall" "golang.org/x/sys/unix" @@ -91,18 +89,3 @@ var SignalMap = map[string]syscall.Signal{ "RTMAX-1": sigrtmax - 1, "RTMAX": sigrtmax, } - -// CatchAll catches all signals and relays them to the specified channel. -func CatchAll(sigc chan os.Signal) { - handledSigs := make([]os.Signal, 0, len(SignalMap)) - for _, s := range SignalMap { - handledSigs = append(handledSigs, s) - } - signal.Notify(sigc, handledSigs...) -} - -// StopCatch stops catching the signals and closes the specified channel. -func StopCatch(sigc chan os.Signal) { - signal.Stop(sigc) - close(sigc) -} diff --git a/pkg/signal/signal_linux_mipsx.go b/pkg/signal/signal_linux_mipsx.go index 9021a10e7..cdf9ad4c5 100644 --- a/pkg/signal/signal_linux_mipsx.go +++ b/pkg/signal/signal_linux_mipsx.go @@ -10,8 +10,6 @@ package signal // NOTE: this package has originally been copied from github.com/docker/docker. import ( - "os" - "os/signal" "syscall" "golang.org/x/sys/unix" @@ -92,18 +90,3 @@ var SignalMap = map[string]syscall.Signal{ "RTMAX-1": sigrtmax - 1, "RTMAX": sigrtmax, } - -// CatchAll catches all signals and relays them to the specified channel. -func CatchAll(sigc chan os.Signal) { - handledSigs := make([]os.Signal, 0, len(SignalMap)) - for _, s := range SignalMap { - handledSigs = append(handledSigs, s) - } - signal.Notify(sigc, handledSigs...) -} - -// StopCatch stops catching the signals and closes the specified channel. -func StopCatch(sigc chan os.Signal) { - signal.Stop(sigc) - close(sigc) -} diff --git a/pkg/signal/signal_unix.go b/pkg/signal/signal_unix.go index 0f43e21b7..7919e3670 100644 --- a/pkg/signal/signal_unix.go +++ b/pkg/signal/signal_unix.go @@ -5,7 +5,6 @@ package signal import ( - "os" "syscall" ) @@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{ "RTMAX-1": sigrtmax - 1, "RTMAX": sigrtmax, } - -// CatchAll catches all signals and relays them to the specified channel. -func CatchAll(sigc chan os.Signal) { - panic("Unsupported on non-linux platforms") -} - -// StopCatch stops catching the signals and closes the specified channel. -func StopCatch(sigc chan os.Signal) { - panic("Unsupported on non-linux platforms") -} diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go index 9d0cee317..19ae93a61 100644 --- a/pkg/signal/signal_unsupported.go +++ b/pkg/signal/signal_unsupported.go @@ -5,7 +5,6 @@ package signal import ( - "os" "syscall" ) @@ -88,13 +87,3 @@ var SignalMap = map[string]syscall.Signal{ "RTMAX-1": sigrtmax - 1, "RTMAX": sigrtmax, } - -// CatchAll catches all signals and relays them to the specified channel. -func CatchAll(sigc chan os.Signal) { - panic("Unsupported on non-linux platforms") -} - -// StopCatch stops catching the signals and closes the specified channel. -func StopCatch(sigc chan os.Signal) { - panic("Unsupported on non-linux platforms") -} diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go index 5616a4511..e09757d1d 100644 --- a/pkg/specgen/container_validate.go +++ b/pkg/specgen/container_validate.go @@ -59,6 +59,7 @@ func (s *SpecGenerator) Validate() error { if s.ContainerBasicConfig.UtsNS.IsPod() { return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when joining the pod UTS namespace") } + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when running in the host UTS namespace") } // systemd values must be true, false, or always diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index 0ed3c79ef..8fdd87adf 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -38,10 +38,19 @@ func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGen } // Need to look up image. - image, resolvedName, err := r.LibimageRuntime().LookupImage(s.Image, nil) + lookupOptions := &libimage.LookupImageOptions{ManifestList: true} + image, resolvedName, err := r.LibimageRuntime().LookupImage(s.Image, lookupOptions) if err != nil { return nil, "", nil, err } + manifestList, err := image.ToManifestList() + // only process if manifest list found otherwise expect it to be regular image + if err == nil { + image, err = manifestList.LookupInstance(ctx, s.ImageArch, s.ImageOS, s.ImageVariant) + if err != nil { + return nil, "", nil, err + } + } s.SetImage(image, resolvedName) inspectData, err := image.Inspect(ctx, nil) if err != nil { @@ -506,6 +515,7 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s specg.Mounts = mounts specg.HostDeviceList = conf.DeviceHostSrc specg.Networks = conf.Networks + specg.ShmSize = &conf.ShmSize mapSecurityConfig(conf, specg) diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 7faf13465..f31e46090 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -133,6 +133,12 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener options = append(options, libpod.WithRootFSFromImage(newImage.ID(), resolvedImageName, s.RawImageName)) } + + _, err = rt.LookupPod(s.Hostname) + if len(s.Hostname) > 0 && !s.UtsNS.IsPrivate() && err == nil { + // ok, we are incorrectly setting the pod as the hostname, lets undo that before validation + s.Hostname = "" + } if err := s.Validate(); err != nil { return nil, nil, nil, errors.Wrap(err, "invalid config provided") } @@ -180,10 +186,23 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, nil, nil, err } + resources := runtimeSpec.Linux.Resources + + // resources get overwrritten similarly to pod inheritance, manually assign here if there is a new value + marshalRes, err := json.Marshal(resources) + if err != nil { + return nil, nil, nil, err + } + err = json.Unmarshal(out, runtimeSpec.Linux) if err != nil { return nil, nil, nil, err } + + err = json.Unmarshal(marshalRes, runtimeSpec.Linux.Resources) + if err != nil { + return nil, nil, nil, err + } } if s.ResourceLimits != nil { switch { @@ -564,5 +583,10 @@ func Inherit(infra libpod.Container, s *specgen.SpecGenerator, rt *libpod.Runtim if err != nil { return nil, nil, nil, err } + + // this causes errors when shmSize is the default value, it will still get passed down unless we manually override. + if s.IpcNS.NSMode == specgen.Host && (compatibleOptions.ShmSize != nil && compatibleOptions.IsDefaultShmSize()) { + s.ShmSize = nil + } return options, infraSpec, compatibleOptions, nil } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index 689c740f0..c254b8192 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -511,12 +511,12 @@ func makeHealthCheck(inCmd string, interval int32, retries int32, timeout int32, cmd := []string{} if inCmd == "none" { - cmd = []string{"NONE"} + cmd = []string{define.HealthConfigTestNone} } else { err := json.Unmarshal([]byte(inCmd), &cmd) if err != nil { // ...otherwise pass it to "/bin/sh -c" inside the container - cmd = []string{"CMD-SHELL"} + cmd = []string{define.HealthConfigTestCmdShell} cmd = append(cmd, strings.Split(inCmd, " ")...) } } @@ -810,8 +810,8 @@ func envVarValueResourceFieldRef(env v1.EnvVar, opts *CtrSpecGenOptions) (*strin } // k8s rounds up the result to the nearest integer - intValue := int(math.Ceil(value.AsApproximateFloat64() / divisor.AsApproximateFloat64())) - stringValue := strconv.Itoa(intValue) + intValue := int64(math.Ceil(value.AsApproximateFloat64() / divisor.AsApproximateFloat64())) + stringValue := strconv.FormatInt(intValue, 10) return &stringValue, nil } diff --git a/pkg/specgen/generate/kube/play_test.go b/pkg/specgen/generate/kube/play_test.go index e01d62b08..466dab610 100644 --- a/pkg/specgen/generate/kube/play_test.go +++ b/pkg/specgen/generate/kube/play_test.go @@ -2,7 +2,6 @@ package kube import ( "encoding/json" - "fmt" "math" "runtime" "strconv" @@ -777,8 +776,7 @@ func TestEnvVarValue(t *testing.T) { if test.expected == nilString { assert.Nil(t, result) } else { - fmt.Println(*result, test.expected) - assert.Equal(t, &(test.expected), result) + assert.Equal(t, test.expected, *result) } }) } diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 4224d16ce..a3719d58e 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -176,7 +176,14 @@ func namespaceOptions(s *specgen.SpecGenerator, rt *libpod.Runtime, pod *libpod. if pod == nil || infraCtr == nil { return nil, errNoInfra } - toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr)) + if pod.NamespaceMode(spec.UTSNamespace) == host { + // adding infra as a nsCtr is not what we want to do when uts == host + // this leads the new ctr to try to add an ns path which is should not in this mode + logrus.Debug("pod has host uts, not adding infra as a nsCtr") + s.UtsNS = specgen.Namespace{NSMode: specgen.Host} + } else { + toReturn = append(toReturn, libpod.WithUTSNSFrom(infraCtr)) + } case specgen.FromContainer: utsCtr, err := rt.LookupContainer(s.UtsNS.Value) if err != nil { diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index 19f55c9d8..1044854f4 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -298,8 +298,7 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt g.AddAnnotation(key, val) } - switch { - case compatibleOptions.InfraResources == nil && s.ResourceLimits != nil: + if s.ResourceLimits != nil { out, err := json.Marshal(s.ResourceLimits) if err != nil { return nil, err @@ -308,29 +307,9 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt if err != nil { return nil, err } - case s.ResourceLimits != nil: // if we have predefined resource limits we need to make sure we keep the infra and container limits - originalResources, err := json.Marshal(s.ResourceLimits) - if err != nil { - return nil, err - } - infraResources, err := json.Marshal(compatibleOptions.InfraResources) - if err != nil { - return nil, err - } - err = json.Unmarshal(infraResources, s.ResourceLimits) // put infra's resource limits in the container - if err != nil { - return nil, err - } - err = json.Unmarshal(originalResources, s.ResourceLimits) // make sure we did not override anything - if err != nil { - return nil, err - } g.Config.Linux.Resources = s.ResourceLimits - default: - g.Config.Linux.Resources = compatibleOptions.InfraResources } // Devices - // set the default rule at the beginning of device configuration if !inUserNS && !s.Privileged { g.AddLinuxResourcesDevice(false, "", nil, nil, "rwm") diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index 4ac8a0aa2..59936c7a8 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -60,6 +60,7 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { if err != nil { return nil, err } + spec.Pod = pod.ID() opts = append(opts, rt.WithPod(pod)) spec.CgroupParent = pod.CgroupParent() diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 777097ac5..02ba06be1 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -183,6 +183,10 @@ type PodStorageConfig struct { // comma-separated options. Valid options are 'ro', 'rw', and 'z'. // Options will be used for all volumes sourced from the container. VolumesFrom []string `json:"volumes_from,omitempty"` + // ShmSize is the size of the tmpfs to mount in at /dev/shm, in bytes. + // Conflicts with ShmSize if IpcNS is not private. + // Optional. + ShmSize *int64 `json:"shm_size,omitempty"` } // PodCgroupConfig contains configuration options about a pod's cgroups. diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 79e20667b..42b89ece1 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -103,6 +103,12 @@ type ContainerBasicConfig struct { // RawImageName is the user-specified and unprocessed input referring // to a local or a remote image. RawImageName string `json:"raw_image_name,omitempty"` + // ImageOS is the user-specified image OS + ImageOS string `json:"image_os,omitempty"` + // ImageArch is the user-specified image architecture + ImageArch string `json:"image_arch,omitempty"` + // ImageVariant is the user-specified image variant + ImageVariant string `json:"image_variant,omitempty"` // RestartPolicy is the container's restart policy - an action which // will be taken when the container exits. // If not given, the default policy, which does nothing, will be used. diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index a7a1022b0..c9f944abf 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -1,6 +1,7 @@ package specgen import ( + "path/filepath" "strings" "github.com/containers/common/pkg/parse" @@ -36,7 +37,7 @@ type OverlayVolume struct { // ImageVolume is a volume based on a container image. The container image is // first mounted on the host and is then bind-mounted into the container. An -// ImageVolume is always mounted read only. +// ImageVolume is always mounted read-only. type ImageVolume struct { // Source is the source of the image volume. The image can be referred // to by name and by ID. @@ -56,7 +57,6 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na overlayVolumes := make(map[string]*OverlayVolume) volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") - for _, vol := range volumeFlag { var ( options []string @@ -71,6 +71,20 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na } src = splitVol[0] + + // Support relative paths beginning with ./ + if strings.HasPrefix(src, "./") { + path, err := filepath.EvalSymlinks(src) + if err != nil { + return nil, nil, nil, err + } + src, err = filepath.Abs(path) + if err != nil { + return nil, nil, nil, err + } + splitVol[0] = src + } + if len(splitVol) == 1 { // This is an anonymous named volume. Only thing given // is destination. @@ -125,7 +139,13 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na // This is a overlay volume newOverlayVol := new(OverlayVolume) newOverlayVol.Destination = dest - newOverlayVol.Source = src + // convert src to absolute path so we don't end up passing + // relative values as lowerdir for overlay mounts + source, err := filepath.Abs(src) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed while resolving absolute path for source %v for overlay mount", src) + } + newOverlayVol.Source = source newOverlayVol.Options = options if _, ok := overlayVolumes[newOverlayVol.Destination]; ok { diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index ab45a8d47..34350579d 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -873,23 +873,23 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start } var concat string - if cmdArr[0] == "CMD" || cmdArr[0] == "none" { // this is for compat, we are already split properly for most compat cases + if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestCmd || strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // this is for compat, we are already split properly for most compat cases cmdArr = strings.Fields(inCmd) - } else if cmdArr[0] != "CMD-SHELL" { // this is for podman side of things, won't contain the keywords + } else if strings.ToUpper(cmdArr[0]) != define.HealthConfigTestCmdShell { // this is for podman side of things, won't contain the keywords if isArr && len(cmdArr) > 1 { // an array of consecutive commands - cmdArr = append([]string{"CMD"}, cmdArr...) + cmdArr = append([]string{define.HealthConfigTestCmd}, cmdArr...) } else { // one singular command if len(cmdArr) == 1 { concat = cmdArr[0] } else { concat = strings.Join(cmdArr[0:], " ") } - cmdArr = append([]string{"CMD-SHELL"}, concat) + cmdArr = append([]string{define.HealthConfigTestCmdShell}, concat) } } - if cmdArr[0] == "none" { // if specified to remove healtcheck - cmdArr = []string{"NONE"} + if strings.ToUpper(cmdArr[0]) == define.HealthConfigTestNone { // if specified to remove healtcheck + cmdArr = []string{define.HealthConfigTestNone} } // healthcheck is by default an array, so we simply pass the user input @@ -1134,17 +1134,21 @@ func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, er } number := strings.SplitN(value[1], ":", 2) - i, err := strconv.ParseInt(number[0], 10, 64) - if err != nil { - return specs.LinuxDeviceCgroup{}, err + if number[0] != "*" { + i, err := strconv.ParseUint(number[0], 10, 64) + if err != nil { + return specs.LinuxDeviceCgroup{}, err + } + m := int64(i) + major = &m } - major = &i if len(number) == 2 && number[1] != "*" { - i, err := strconv.ParseInt(number[1], 10, 64) + i, err := strconv.ParseUint(number[1], 10, 64) if err != nil { return specs.LinuxDeviceCgroup{}, err } - minor = &i + m := int64(i) + minor = &m } access = value[2] for _, c := range strings.Split(access, "") { diff --git a/pkg/specgenutil/specgenutil_test.go b/pkg/specgenutil/specgenutil_test.go index 5867b0ae0..fb2743f17 100644 --- a/pkg/specgenutil/specgenutil_test.go +++ b/pkg/specgenutil/specgenutil_test.go @@ -75,3 +75,82 @@ func TestWinPath(t *testing.T) { } } } + +func TestParseLinuxResourcesDeviceAccess(t *testing.T) { + d, err := parseLinuxResourcesDeviceAccess("a *:* rwm") + assert.Nil(t, err, "err is nil") + assert.True(t, d.Allow, "allow is true") + assert.Equal(t, d.Type, "a", "type is 'a'") + assert.Nil(t, d.Minor, "minor is nil") + assert.Nil(t, d.Major, "major is nil") + + d, err = parseLinuxResourcesDeviceAccess("b 3:* rwm") + assert.Nil(t, err, "err is nil") + assert.True(t, d.Allow, "allow is true") + assert.Equal(t, d.Type, "b", "type is 'b'") + assert.Nil(t, d.Minor, "minor is nil") + assert.NotNil(t, d.Major, "major is not nil") + assert.Equal(t, *d.Major, int64(3), "major is 3") + + d, err = parseLinuxResourcesDeviceAccess("a *:3 rwm") + assert.Nil(t, err, "err is nil") + assert.True(t, d.Allow, "allow is true") + assert.Equal(t, d.Type, "a", "type is 'a'") + assert.Nil(t, d.Major, "major is nil") + assert.NotNil(t, d.Minor, "minor is not nil") + assert.Equal(t, *d.Minor, int64(3), "minor is 3") + + d, err = parseLinuxResourcesDeviceAccess("c 1:2 rwm") + assert.Nil(t, err, "err is nil") + assert.True(t, d.Allow, "allow is true") + assert.Equal(t, d.Type, "c", "type is 'c'") + assert.NotNil(t, d.Major, "minor is not nil") + assert.Equal(t, *d.Major, int64(1), "minor is 1") + assert.NotNil(t, d.Minor, "minor is not nil") + assert.Equal(t, *d.Minor, int64(2), "minor is 2") + + _, err = parseLinuxResourcesDeviceAccess("q *:* rwm") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a a:* rwm") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a *:a rwm") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a *:* abc") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("* *:* *") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("* *:a2 *") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("*") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("*:*") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a *:*") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a *:*") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a 12a:* r") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a a12:* r") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a 0x1:* r") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a -2:* r") + assert.NotNil(t, err, "err is not nil") + + _, err = parseLinuxResourcesDeviceAccess("a *:-3 r") + assert.NotNil(t, err, "err is not nil") +} diff --git a/pkg/specgenutil/volumes.go b/pkg/specgenutil/volumes.go index 50d745380..016166a20 100644 --- a/pkg/specgenutil/volumes.go +++ b/pkg/specgenutil/volumes.go @@ -605,7 +605,7 @@ func getNamedVolume(args []string) (*specgen.NamedVolume, error) { // Parse the arguments into an image volume. An image volume is a volume based // on a container image. The container image is first mounted on the host and // is then bind-mounted into the container. An ImageVolume is always mounted -// read only. +// read-only. func getImageVolume(args []string) (*specgen.ImageVolume, error) { newVolume := new(specgen.ImageVolume) diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index d552e21ed..e953a1f1f 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -204,7 +204,7 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste } else { runRoot = ctr.Runtime().RunRoot() if runRoot == "" { - return nil, errors.Errorf("could not lookup container's runroot: got empty string") + return nil, errors.Errorf("could not look up container's runroot: got empty string") } } diff --git a/pkg/util/mountOpts.go b/pkg/util/mountOpts.go index e37394619..d1dd75a82 100644 --- a/pkg/util/mountOpts.go +++ b/pkg/util/mountOpts.go @@ -25,7 +25,7 @@ type defaultMountOptions struct { // The sourcePath variable, if not empty, contains a bind mount source. func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, error) { var ( - foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap bool + foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy bool ) newOptions := make([]string, 0, len(options)) @@ -55,6 +55,11 @@ func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string } switch splitOpt[0] { + case "copy", "nocopy": + if foundCopy { + return nil, errors.Wrapf(ErrDupeMntOption, "only one of 'nocopy' and 'copy' can be used") + } + foundCopy = true case "O": foundOverlay = true case "volume-opt": |