diff options
Diffstat (limited to 'pkg')
79 files changed, 1017 insertions, 324 deletions
diff --git a/pkg/api/handlers/compat/containers_logs.go b/pkg/api/handlers/compat/containers_logs.go index 50cdb1e65..a7cfe09ea 100644 --- a/pkg/api/handlers/compat/containers_logs.go +++ b/pkg/api/handlers/compat/containers_logs.go @@ -152,9 +152,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { } frame.WriteString(line.Msg) - // Log lines in the compat layer require adding EOL - // https://github.com/containers/podman/issues/8058 - if !utils.IsLibpodRequest(r) { + if !line.Partial() { frame.WriteString("\n") } diff --git a/pkg/api/handlers/compat/images_build.go b/pkg/api/handlers/compat/images_build.go index 08d1df4b8..0fcca1821 100644 --- a/pkg/api/handlers/compat/images_build.go +++ b/pkg/api/handlers/compat/images_build.go @@ -34,13 +34,16 @@ func BuildImage(w http.ResponseWriter, r *http.Request) { contentType := hdr[0] switch contentType { case "application/tar": - logrus.Warnf("tar file content type is %s, should use \"application/x-tar\" content type", contentType) + logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType) case "application/x-tar": break default: - utils.BadRequest(w, "Content-Type", hdr[0], - fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) - return + if utils.IsLibpodRequest(r) { + utils.BadRequest(w, "Content-Type", hdr[0], + fmt.Errorf("Content-Type: %s is not supported. Should be \"application/x-tar\"", hdr[0])) + return + } + logrus.Infof("tar file content type is %s, should use \"application/x-tar\" content type", contentType) } } diff --git a/pkg/api/handlers/compat/info.go b/pkg/api/handlers/compat/info.go index d7cefd516..2c26c7bf8 100644 --- a/pkg/api/handlers/compat/info.go +++ b/pkg/api/handlers/compat/info.go @@ -102,14 +102,18 @@ func GetInfo(w http.ResponseWriter, r *http.Request) { OomKillDisable: sysInfo.OomKillDisable, OperatingSystem: infoData.Host.Distribution.Distribution, PidsLimit: sysInfo.PidsLimit, - Plugins: docker.PluginsInfo{}, - ProductLicense: "Apache-2.0", - RegistryConfig: new(registry.ServiceConfig), - RuncCommit: docker.Commit{}, - Runtimes: getRuntimes(configInfo), - SecurityOptions: getSecOpts(sysInfo), - ServerVersion: versionInfo.Version, - SwapLimit: sysInfo.SwapLimit, + Plugins: docker.PluginsInfo{ + Volume: infoData.Plugins.Volume, + Network: infoData.Plugins.Network, + Log: infoData.Plugins.Log, + }, + ProductLicense: "Apache-2.0", + RegistryConfig: new(registry.ServiceConfig), + RuncCommit: docker.Commit{}, + Runtimes: getRuntimes(configInfo), + SecurityOptions: getSecOpts(sysInfo), + ServerVersion: versionInfo.Version, + SwapLimit: sysInfo.SwapLimit, Swarm: swarm.Info{ LocalNodeState: swarm.LocalNodeStateInactive, }, diff --git a/pkg/api/handlers/compat/swagger.go b/pkg/api/handlers/compat/swagger.go index b773799ef..cfbdd1154 100644 --- a/pkg/api/handlers/compat/swagger.go +++ b/pkg/api/handlers/compat/swagger.go @@ -2,7 +2,6 @@ package compat import ( "github.com/containers/podman/v3/pkg/domain/entities" - "github.com/containers/storage/pkg/archive" "github.com/docker/docker/api/types" ) @@ -28,15 +27,6 @@ type swagCtrWaitResponse struct { } } -// Object Changes -// swagger:response Changes -type swagChangesResponse struct { - // in:body - Body struct { - Changes []archive.Change - } -} - // Network inspect // swagger:response CompatNetworkInspect type swagCompatNetworkInspect struct { diff --git a/pkg/api/handlers/compat/version.go b/pkg/api/handlers/compat/version.go index f1cd77a9a..a115cc885 100644 --- a/pkg/api/handlers/compat/version.go +++ b/pkg/api/handlers/compat/version.go @@ -13,20 +13,19 @@ import ( "github.com/containers/podman/v3/pkg/domain/entities/types" "github.com/containers/podman/v3/version" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func VersionHandler(w http.ResponseWriter, r *http.Request) { - // 200 ok - // 500 internal runtime := r.Context().Value("runtime").(*libpod.Runtime) - versionInfo, err := define.GetVersion() + running, err := define.GetVersion() if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) return } - infoData, err := runtime.Info() + info, err := runtime.Info() if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to obtain system memory info")) return @@ -34,20 +33,40 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { components := []types.ComponentVersion{{ Name: "Podman Engine", - Version: versionInfo.Version, + Version: running.Version, Details: map[string]string{ "APIVersion": version.APIVersion[version.Libpod][version.CurrentAPI].String(), "Arch": goRuntime.GOARCH, - "BuildTime": time.Unix(versionInfo.Built, 0).Format(time.RFC3339), - "Experimental": "true", - "GitCommit": versionInfo.GitCommit, - "GoVersion": versionInfo.GoVersion, - "KernelVersion": infoData.Host.Kernel, + "BuildTime": time.Unix(running.Built, 0).Format(time.RFC3339), + "Experimental": "false", + "GitCommit": running.GitCommit, + "GoVersion": running.GoVersion, + "KernelVersion": info.Host.Kernel, "MinAPIVersion": version.APIVersion[version.Libpod][version.MinimalAPI].String(), "Os": goRuntime.GOOS, }, }} + if conmon, oci, err := runtime.DefaultOCIRuntime().RuntimeInfo(); err != nil { + logrus.Warnf("Failed to retrieve Conmon and OCI Information: %q", err.Error()) + } else { + additional := []types.ComponentVersion{ + { + Name: "Conmon", + Version: conmon.Version, + Details: map[string]string{ + "Package": conmon.Package, + }}, + { + Name: fmt.Sprintf("OCI Runtime (%s)", oci.Name), + Version: oci.Version, + Details: map[string]string{ + "Package": oci.Package, + }}, + } + components = append(components, additional...) + } + apiVersion := version.APIVersion[version.Compat][version.CurrentAPI] minVersion := version.APIVersion[version.Compat][version.MinimalAPI] @@ -56,13 +75,13 @@ func VersionHandler(w http.ResponseWriter, r *http.Request) { Platform: struct { Name string }{ - Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, infoData.Host.Distribution.Distribution, infoData.Host.Distribution.Version), + Name: fmt.Sprintf("%s/%s/%s-%s", goRuntime.GOOS, goRuntime.GOARCH, info.Host.Distribution.Distribution, info.Host.Distribution.Version), }, APIVersion: fmt.Sprintf("%d.%d", apiVersion.Major, apiVersion.Minor), Arch: components[0].Details["Arch"], BuildTime: components[0].Details["BuildTime"], Components: components, - Experimental: true, + Experimental: false, GitCommit: components[0].Details["GitCommit"], GoVersion: components[0].Details["GoVersion"], KernelVersion: components[0].Details["KernelVersion"], diff --git a/pkg/api/handlers/libpod/images_pull.go b/pkg/api/handlers/libpod/images_pull.go index 04b415638..3c13c6e20 100644 --- a/pkg/api/handlers/libpod/images_pull.go +++ b/pkg/api/handlers/libpod/images_pull.go @@ -33,6 +33,7 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { TLSVerify bool `schema:"tlsVerify"` AllTags bool `schema:"allTags"` PullPolicy string `schema:"policy"` + Quiet bool `schema:"quiet"` }{ TLSVerify: true, PullPolicy: "always", @@ -116,8 +117,10 @@ func ImagesPull(w http.ResponseWriter, r *http.Request) { select { case s := <-writer.Chan(): report.Stream = string(s) - if err := enc.Encode(report); err != nil { - logrus.Warnf("Failed to encode json: %v", err) + if !query.Quiet { + if err := enc.Encode(report); err != nil { + logrus.Warnf("Failed to encode json: %v", err) + } } flush() case <-runCtx.Done(): diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 4dc8740e2..ff105bc48 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -30,6 +30,12 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { utils.Error(w, "failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } + // parse userns so we get the valid default value of userns + psg.Userns, err = specgen.ParseUserNamespace(psg.Userns.String()) + if err != nil { + utils.Error(w, "failed to parse userns", http.StatusInternalServerError, errors.Wrap(err, "failed to parse userns")) + return + } pod, err := generate.MakePod(&psg, runtime) if err != nil { httpCode := http.StatusInternalServerError diff --git a/pkg/api/handlers/swagger/swagger.go b/pkg/api/handlers/swagger/swagger.go index 83ff5914e..2296eea3a 100644 --- a/pkg/api/handlers/swagger/swagger.go +++ b/pkg/api/handlers/swagger/swagger.go @@ -152,13 +152,6 @@ type swagPodTopResponse struct { } } -// List processes in pod -// swagger:response DocsPodStatsResponse -type swagPodStatsResponse struct { - // in:body - Body []*entities.PodStatsReport -} - // Inspect container // swagger:response LibpodInspectContainerResponse type swagLibpodInspectContainerResponse struct { @@ -183,12 +176,3 @@ type swagInspectPodResponse struct { define.InspectPodData } } - -// Inspect volume -// swagger:response InspectVolumeResponse -type swagInspectVolumeResponse struct { - // in:body - Body struct { - define.InspectVolumeData - } -} diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index af5878798..b82c586ea 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -104,6 +104,7 @@ type ContainerWaitOKBody struct { } // CreateContainerConfig used when compatible endpoint creates a container +// swagger:model CreateContainerConfig type CreateContainerConfig struct { Name string // container name dockerContainer.Config // desired container configuration diff --git a/pkg/api/handlers/utils/images.go b/pkg/api/handlers/utils/images.go index 1e8edb6dd..1e3647a3e 100644 --- a/pkg/api/handlers/utils/images.go +++ b/pkg/api/handlers/utils/images.go @@ -27,7 +27,7 @@ func IsRegistryReference(name string) error { if imageRef.Transport().Name() == docker.Transport.Name() { return nil } - return errors.Errorf("unsupport transport %s in %q: only docker transport is supported", imageRef.Transport().Name(), name) + return errors.Errorf("unsupported transport %s in %q: only docker transport is supported", imageRef.Transport().Name(), name) } // ParseStorageReference parses the specified image name to a diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 0ec4f95d9..b36cb75f1 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -21,6 +21,12 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // name: name // type: string // description: container name + // - in: body + // name: body + // description: Container to create + // schema: + // $ref: "#/definitions/CreateContainerConfig" + // required: true // responses: // 201: // $ref: "#/responses/ContainerCreateResponse" diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 2103c093c..2630acac2 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -25,6 +25,10 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // produces: // - application/json // parameters: + // - in: header + // name: X-Registry-Auth + // type: string + // description: A base64-encoded auth configuration. // - in: query // name: fromImage // type: string @@ -49,13 +53,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // name: platform // type: string // description: Platform in the format os[/arch[/variant]] - // default: "" - // - in: header - // name: X-Registry-Auth - // type: string - // description: A base64-encoded auth configuration. // - in: body - // name: request + // name: inputImage // schema: // type: string // format: binary @@ -472,6 +471,14 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // summary: Create image // description: Build an image from the given Dockerfile(s) // parameters: + // - in: header + // name: Content-Type + // type: string + // default: application/x-tar + // enum: ["application/x-tar"] + // - in: header + // name: X-Registry-Config + // type: string // - in: query // name: dockerfile // type: string @@ -653,6 +660,14 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: | // output configuration TBD // (As of version 1.xx) + // - in: body + // name: inputStream + // description: | + // A tar archive compressed with one of the following algorithms: + // identity (no compression), gzip, bzip2, xz. + // schema: + // type: string + // format: binary // produces: // - application/json // responses: @@ -852,6 +867,11 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // summary: Import image // description: Import a previously exported tarball as an image. // parameters: + // - in: header + // name: Content-Type + // type: string + // default: application/x-tar + // enum: ["application/x-tar"] // - in: query // name: changes // description: "Apply the following possible instructions to the created image: CMD | ENTRYPOINT | ENV | EXPOSE | LABEL | STOPSIGNAL | USER | VOLUME | WORKDIR. JSON encoded string" @@ -875,7 +895,8 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // required: true // description: tarball for imported image // schema: - // type: "string" + // type: string + // format: binary // produces: // - application/json // consumes: @@ -962,6 +983,15 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // description: "Mandatory reference to the image (e.g., quay.io/image/name:tag)" // type: string // - in: query + // name: quiet + // description: "silences extra stream data on pull" + // type: boolean + // default: false + // - in: query + // name: credentials + // description: "username:password for the registry" + // type: string + // - in: query // name: Arch // description: Pull image for the specified architecture. // type: string diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 3bcc50ba4..de3669a0a 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -17,7 +17,18 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // - in: query // name: filters // type: string - // description: needs description and plumbing for filters + // description: | + // JSON encoded value of the filters (a map[string][]string) to process on the pods list. Available filters: + // - `id=<pod-id>` Matches all of pod id. + // - `label=<key>` or `label=<key>:<value>` Matches pods based on the presence of a label alone or a label and a value. + // - `name=<pod-name>` Matches all of pod name. + // - `until=<timestamp>` List pods created before this timestamp. The `<timestamp>` can be Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`) computed relative to the daemon machine’s time. + // - `status=<pod-status>` Pod's status: `stopped`, `running`, `paused`, `exited`, `dead`, `created`, `degraded`. + // - `network=<pod-network>` Name or full ID of network. + // - `ctr-names=<pod-ctr-names>` Container name within the pod. + // - `ctr-ids=<pod-ctr-ids>` Container ID within the pod. + // - `ctr-status=<pod-ctr-status>` Container status within the pod. + // - `ctr-number=<pod-ctr-number>` Number of containers in the pod. // responses: // 200: // $ref: "#/responses/ListPodsResponse" @@ -40,7 +51,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // responses: // 201: // schema: - // $ref: "#/definitions/IdResponse" + // $ref: "#/definitions/IDResponse" // 400: // $ref: "#/responses/BadParamError" // 409: diff --git a/pkg/api/server/swagger.go b/pkg/api/server/swagger.go index d282edf23..0fd66652e 100644 --- a/pkg/api/server/swagger.go +++ b/pkg/api/server/swagger.go @@ -141,13 +141,6 @@ type swagImageSummary struct { Body []entities.ImageSummary } -// Registries summary -// swagger:response DocsRegistriesList -type swagRegistriesList struct { - // in:body - Body entities.ListRegistriesReport -} - // List Containers // swagger:response DocsListContainer type swagListContainers struct { diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go index 62b1655ac..4127ad2f0 100644 --- a/pkg/bindings/connection.go +++ b/pkg/bindings/connection.go @@ -56,7 +56,7 @@ func NewConnection(ctx context.Context, uri string) (context.Context, error) { return NewConnectionWithIdentity(ctx, uri, "") } -// NewConnection takes a URI as a string and returns a context with the +// NewConnectionWithIdentity takes a URI as a string and returns a context with the // Connection embedded as a value. This context needs to be passed to each // endpoint to work correctly. // @@ -117,7 +117,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, identity string) ctx = context.WithValue(ctx, clientKey, &connection) if err := pingNewConnection(ctx); err != nil { - return nil, errors.Wrap(err, "cannot connect to the Podman socket, please verify that Podman REST API service is running") + return nil, errors.Wrap(err, "cannot connect to the Podman socket, please verify the connection to the Linux system, or use `podman machine` to create/start a Linux VM.") } return ctx, nil } @@ -149,6 +149,7 @@ func pingNewConnection(ctx context.Context) error { if err != nil { return err } + defer response.Body.Close() if response.StatusCode == http.StatusOK { versionHdr := response.Header.Get("Libpod-API-Version") @@ -338,7 +339,7 @@ func (c *Connection) DoRequest(httpBody io.Reader, httpMethod, endpoint string, req.Header.Set(key, val) } // Give the Do three chances in the case of a comm/service hiccup - for i := 0; i < 3; i++ { + for i := 1; i <= 3; i++ { response, err = c.Client.Do(req) // nolint if err == nil { break @@ -358,7 +359,7 @@ func FiltersToString(filters map[string][]string) (string, error) { return jsoniter.MarshalToString(lowerCaseKeys) } -// IsInformation returns true if the response code is 1xx +// IsInformational returns true if the response code is 1xx func (h *APIResponse) IsInformational() bool { return h.Response.StatusCode/100 == 1 } diff --git a/pkg/bindings/containers/archive.go b/pkg/bindings/containers/archive.go index 52b73662b..876f5340b 100644 --- a/pkg/bindings/containers/archive.go +++ b/pkg/bindings/containers/archive.go @@ -27,6 +27,7 @@ func Stat(ctx context.Context, nameOrID string, path string) (*entities.Containe if err != nil { return nil, err } + defer response.Body.Close() var finalErr error if response.StatusCode == http.StatusNotFound { @@ -53,7 +54,9 @@ func CopyFromArchive(ctx context.Context, nameOrID string, path string, reader i return CopyFromArchiveWithOptions(ctx, nameOrID, path, reader, nil) } -// CopyFromArchiveWithOptions FIXME: remove this function and make CopyFromArchive accept the option as the last parameter in podman 4.0 +// CopyFromArchiveWithOptions copy files into container +// +// FIXME: remove this function and make CopyFromArchive accept the option as the last parameter in podman 4.0 func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path string, reader io.Reader, options *CopyOptions) (entities.ContainerCopyFunc, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -72,6 +75,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin if err != nil { return err } + if response.StatusCode != http.StatusOK { return errors.New(response.Status) } @@ -79,6 +83,7 @@ func CopyFromArchiveWithOptions(ctx context.Context, nameOrID string, path strin }, nil } +// CopyToArchive copy files from container func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io.Writer) (entities.ContainerCopyFunc, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -91,11 +96,14 @@ func CopyToArchive(ctx context.Context, nameOrID string, path string, writer io. if err != nil { return nil, err } + if response.StatusCode != http.StatusOK { + defer response.Body.Close() return nil, response.Process(nil) } return func() error { + defer response.Body.Close() _, err := io.Copy(writer, response.Body) return err }, nil diff --git a/pkg/bindings/containers/attach.go b/pkg/bindings/containers/attach.go index 01c14d350..6efbcb57b 100644 --- a/pkg/bindings/containers/attach.go +++ b/pkg/bindings/containers/attach.go @@ -134,7 +134,9 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri if err != nil { return err } + if !(response.IsSuccess() || response.IsInformational()) { + defer response.Body.Close() return response.Process(nil) } @@ -207,7 +209,7 @@ func Attach(ctx context.Context, nameOrID string, stdin io.Reader, stdout io.Wri } } } else { - logrus.Debugf("Copying standard streams of container in non-terminal mode") + logrus.Debugf("Copying standard streams of container %q in non-terminal mode", ctnr.ID) for { // Read multiplexed channels and write to appropriate stream fd, l, err := DemuxHeader(socket, buffer) @@ -324,6 +326,8 @@ func resizeTTY(ctx context.Context, endpoint string, height *int, width *int) er if err != nil { return err } + defer rsp.Body.Close() + return rsp.Process(nil) } @@ -407,6 +411,7 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if err != nil { return err } + defer resp.Body.Close() respStruct := new(define.InspectExecSession) if err := resp.Process(respStruct); err != nil { @@ -477,6 +482,8 @@ func ExecStartAndAttach(ctx context.Context, sessionID string, options *ExecStar if err != nil { return err } + defer response.Body.Close() + if !(response.IsSuccess() || response.IsInformational()) { return response.Process(nil) } diff --git a/pkg/bindings/containers/checkpoint.go b/pkg/bindings/containers/checkpoint.go index 440bf707d..7f7080f13 100644 --- a/pkg/bindings/containers/checkpoint.go +++ b/pkg/bindings/containers/checkpoint.go @@ -27,6 +27,8 @@ func Checkpoint(ctx context.Context, nameOrID string, options *CheckpointOptions if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -54,5 +56,7 @@ func Restore(ctx context.Context, nameOrID string, options *RestoreOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } diff --git a/pkg/bindings/containers/commit.go b/pkg/bindings/containers/commit.go index 8ae61322e..a4adebb1f 100644 --- a/pkg/bindings/containers/commit.go +++ b/pkg/bindings/containers/commit.go @@ -28,5 +28,7 @@ func Commit(ctx context.Context, nameOrID string, options *CommitOptions) (handl if err != nil { return id, err } + defer response.Body.Close() + return id, response.Process(&id) } diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go index bc7b0c8c9..aafb83f65 100644 --- a/pkg/bindings/containers/containers.go +++ b/pkg/bindings/containers/containers.go @@ -42,6 +42,8 @@ func List(ctx context.Context, options *ListOptions) ([]entities.ListContainer, if err != nil { return containers, err } + defer response.Body.Close() + return containers, response.Process(&containers) } @@ -66,6 +68,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -90,6 +94,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -113,6 +119,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*de if err != nil { return nil, err } + defer response.Body.Close() + inspect := define.InspectContainerData{} return &inspect, response.Process(&inspect) } @@ -136,6 +144,8 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -154,6 +164,8 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -176,6 +188,8 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -199,6 +213,8 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -231,6 +247,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha go func() { defer close(statsChan) + defer response.Body.Close() dec := json.NewDecoder(response.Body) doStream := true @@ -245,6 +262,7 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha default: // fall through and do some work } + var report entities.ContainerStatsReport if err := dec.Decode(&report); err != nil { report = entities.ContainerStatsReport{Error: err} @@ -279,6 +297,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e if err != nil { return nil, err } + defer response.Body.Close() body := handlers.ContainerTopOKBody{} if err = response.Process(&body); err != nil { @@ -311,6 +330,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -334,6 +355,8 @@ func Wait(ctx context.Context, nameOrID string, options *WaitOptions) (int32, er if err != nil { return exitCode, err } + defer response.Body.Close() + return exitCode, response.Process(&exitCode) } @@ -353,6 +376,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -374,6 +399,8 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -393,6 +420,8 @@ func Export(ctx context.Context, nameOrID string, w io.Writer, options *ExportOp if err != nil { return err } + defer response.Body.Close() + if response.StatusCode/100 == 2 { _, err = io.Copy(w, response.Body) return err @@ -416,6 +445,8 @@ func ContainerInit(ctx context.Context, nameOrID string, options *InitOptions) e if err != nil { return err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { return errors.Wrapf(define.ErrCtrStateInvalid, "container %s has already been created in runtime", nameOrID) } @@ -435,5 +466,7 @@ func ShouldRestart(ctx context.Context, nameOrID string, options *ShouldRestartO if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } diff --git a/pkg/bindings/containers/create.go b/pkg/bindings/containers/create.go index 3efa9643d..c0b9538a6 100644 --- a/pkg/bindings/containers/create.go +++ b/pkg/bindings/containers/create.go @@ -30,5 +30,7 @@ func CreateWithSpec(ctx context.Context, s *specgen.SpecGenerator, options *Crea if err != nil { return ccr, err } + defer response.Body.Close() + return ccr, response.Process(&ccr) } diff --git a/pkg/bindings/containers/diff.go b/pkg/bindings/containers/diff.go index 7d20ae530..e4ec49809 100644 --- a/pkg/bindings/containers/diff.go +++ b/pkg/bindings/containers/diff.go @@ -26,6 +26,8 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive if err != nil { return nil, err } + defer response.Body.Close() + var changes []archive.Change return changes, response.Process(&changes) } diff --git a/pkg/bindings/containers/exec.go b/pkg/bindings/containers/exec.go index 5ae6d1d71..12b31aba3 100644 --- a/pkg/bindings/containers/exec.go +++ b/pkg/bindings/containers/exec.go @@ -39,6 +39,7 @@ func ExecCreate(ctx context.Context, nameOrID string, config *handlers.ExecCreat if err != nil { return "", err } + defer resp.Body.Close() respStruct := new(handlers.ExecCreateResponse) if err := resp.Process(respStruct); err != nil { @@ -66,6 +67,7 @@ func ExecInspect(ctx context.Context, sessionID string, options *ExecInspectOpti if err != nil { return nil, err } + defer resp.Body.Close() respStruct := new(define.InspectExecSession) if err := resp.Process(respStruct); err != nil { @@ -103,6 +105,7 @@ func ExecStart(ctx context.Context, sessionID string, options *ExecStartOptions) if err != nil { return err } + defer resp.Body.Close() return resp.Process(nil) } diff --git a/pkg/bindings/containers/healthcheck.go b/pkg/bindings/containers/healthcheck.go index d6b721615..0e65a5a46 100644 --- a/pkg/bindings/containers/healthcheck.go +++ b/pkg/bindings/containers/healthcheck.go @@ -26,5 +26,7 @@ func RunHealthCheck(ctx context.Context, nameOrID string, options *HealthCheckOp if err != nil { return nil, err } + defer response.Body.Close() + return &status, response.Process(&status) } diff --git a/pkg/bindings/containers/logs.go b/pkg/bindings/containers/logs.go index a3100c697..67db94487 100644 --- a/pkg/bindings/containers/logs.go +++ b/pkg/bindings/containers/logs.go @@ -33,6 +33,7 @@ func Logs(ctx context.Context, nameOrID string, options *LogOptions, stdoutChan, if err != nil { return err } + defer response.Body.Close() buffer := make([]byte, 1024) for { diff --git a/pkg/bindings/containers/mount.go b/pkg/bindings/containers/mount.go index bb5c3bd67..c07998fd3 100644 --- a/pkg/bindings/containers/mount.go +++ b/pkg/bindings/containers/mount.go @@ -25,6 +25,8 @@ func Mount(ctx context.Context, nameOrID string, options *MountOptions) (string, if err != nil { return path, err } + defer response.Body.Close() + return path, response.Process(&path) } @@ -43,6 +45,8 @@ func Unmount(ctx context.Context, nameOrID string, options *UnmountOptions) erro if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -61,5 +65,7 @@ func GetMountedContainerPaths(ctx context.Context, options *MountedContainerPath if err != nil { return mounts, err } + defer response.Body.Close() + return mounts, response.Process(&mounts) } diff --git a/pkg/bindings/containers/rename.go b/pkg/bindings/containers/rename.go index 60d7fda73..172d7838a 100644 --- a/pkg/bindings/containers/rename.go +++ b/pkg/bindings/containers/rename.go @@ -24,5 +24,7 @@ func Rename(ctx context.Context, nameOrID string, options *RenameOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } diff --git a/pkg/bindings/errors.go b/pkg/bindings/errors.go index 3339062a5..9c311d912 100644 --- a/pkg/bindings/errors.go +++ b/pkg/bindings/errors.go @@ -20,6 +20,8 @@ func handleError(data []byte) error { return e } +// Process drains the response body, and processes the HTTP status code +// Note: Closing the response.Body is left to the caller func (h APIResponse) Process(unmarshalInto interface{}) error { data, err := ioutil.ReadAll(h.Response.Body) if err != nil { diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index 7c904a6a8..742956515 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -26,10 +26,15 @@ func Systemd(ctx context.Context, nameOrID string, options *SystemdOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + report := &entities.GenerateSystemdReport{} return report, response.Process(&report.Units) } +// Kube generate Kubernetes YAML (v1 specification) +// +// Note: Caller is responsible for closing returned reader func Kube(ctx context.Context, nameOrIDs []string, options *KubeOptions) (*entities.GenerateKubeReport, error) { if options == nil { options = new(KubeOptions) diff --git a/pkg/bindings/images/build.go b/pkg/bindings/images/build.go index e1aeae244..39e0fc5df 100644 --- a/pkg/bindings/images/build.go +++ b/pkg/bindings/images/build.go @@ -481,9 +481,9 @@ func nTar(excludes []string, sources ...string) (io.ReadCloser, error) { return nil // skip root dir } - name := strings.TrimPrefix(path, s+string(filepath.Separator)) + name := filepath.ToSlash(strings.TrimPrefix(path, s+string(filepath.Separator))) - excluded, err := pm.Matches(filepath.ToSlash(name)) // nolint:staticcheck + excluded, err := pm.Matches(name) // nolint:staticcheck if err != nil { return errors.Wrapf(err, "error checking if %q is excluded", name) } diff --git a/pkg/bindings/images/diff.go b/pkg/bindings/images/diff.go index 79b0df8c9..671b73089 100644 --- a/pkg/bindings/images/diff.go +++ b/pkg/bindings/images/diff.go @@ -23,6 +23,8 @@ func Diff(ctx context.Context, nameOrID string, options *DiffOptions) ([]archive if err != nil { return nil, err } + defer response.Body.Close() + var changes []archive.Change return changes, response.Process(&changes) } diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 8680d6baa..959481e0d 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -27,6 +27,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -49,6 +51,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ImageSummary, if err != nil { return imageSummary, err } + defer response.Body.Close() + return imageSummary, response.Process(&imageSummary) } @@ -71,6 +75,8 @@ func GetImage(ctx context.Context, nameOrID string, options *GetOptions) (*entit if err != nil { return &inspectedData, err } + defer response.Body.Close() + return &inspectedData, response.Process(&inspectedData) } @@ -92,6 +98,8 @@ func Tree(ctx context.Context, nameOrID string, options *TreeOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -110,6 +118,8 @@ func History(ctx context.Context, nameOrID string, options *HistoryOptions) ([]* if err != nil { return history, err } + defer response.Body.Close() + return history, response.Process(&history) } @@ -123,6 +133,8 @@ func Load(ctx context.Context, r io.Reader) (*entities.ImageLoadReport, error) { if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -147,6 +159,7 @@ func Export(ctx context.Context, nameOrIDs []string, w io.Writer, options *Expor if err != nil { return err } + defer response.Body.Close() if response.StatusCode/100 == 2 || response.StatusCode/100 == 3 { _, err = io.Copy(w, response.Body) @@ -176,8 +189,9 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return deleted, err } - err = response.Process(&deleted) - return deleted, err + defer response.Body.Close() + + return deleted, response.Process(&deleted) } // Tag adds an additional name to locally-stored image. Both the tag and repo parameters are required. @@ -197,6 +211,8 @@ func Tag(ctx context.Context, nameOrID, tag, repo string, options *TagOptions) e if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -217,10 +233,12 @@ func Untag(ctx context.Context, nameOrID, tag, repo string, options *UntagOption if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } -// Imports adds the given image to the local image store. This can be done by file and the given reader +// Import adds the given image to the local image store. This can be done by file and the given reader // or via the url parameter. Additional metadata can be associated with the image by using the changes and // message parameters. The image can also be tagged given a reference. One of url OR r must be provided. func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities.ImageImportReport, error) { @@ -243,6 +261,8 @@ func Import(ctx context.Context, r io.Reader, options *ImportOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -269,8 +289,8 @@ func Push(ctx context.Context, source string, destination string, options *PushO if err != nil { return err } - //SkipTLSVerify is special. We need to delete the param added by - //toparams and change the key and flip the bool + // SkipTLSVerify is special. We need to delete the param added by + // toparams and change the key and flip the bool if options.SkipTLSVerify != nil { params.Del("SkipTLSVerify") params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) @@ -282,6 +302,7 @@ func Push(ctx context.Context, source string, destination string, options *PushO if err != nil { return err } + defer response.Body.Close() return response.Process(err) } @@ -317,6 +338,7 @@ func Search(ctx context.Context, term string, options *SearchOptions) ([]entitie if err != nil { return nil, err } + defer response.Body.Close() results := []entities.ImageSearchReport{} if err := response.Process(&results); err != nil { diff --git a/pkg/bindings/images/rm.go b/pkg/bindings/images/rm.go index e45e583f4..461eb7729 100644 --- a/pkg/bindings/images/rm.go +++ b/pkg/bindings/images/rm.go @@ -36,6 +36,8 @@ func Remove(ctx context.Context, images []string, options *RemoveOptions) (*enti if err != nil { return nil, []error{err} } + defer response.Body.Close() + if err := response.Process(&report); err != nil { return nil, []error{err} } diff --git a/pkg/bindings/manifests/manifests.go b/pkg/bindings/manifests/manifests.go index 268ce3b19..6aa4961f1 100644 --- a/pkg/bindings/manifests/manifests.go +++ b/pkg/bindings/manifests/manifests.go @@ -46,10 +46,12 @@ func Create(ctx context.Context, names, images []string, options *CreateOptions) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } -// Exists returns true if a given maifest list exists +// Exists returns true if a given manifest list exists func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, error) { conn, err := bindings.GetClient(ctx) if err != nil { @@ -59,6 +61,8 @@ func Exists(ctx context.Context, name string, options *ExistsOptions) (bool, err if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -77,6 +81,8 @@ func Inspect(ctx context.Context, name string, options *InspectOptions) (*manife if err != nil { return nil, err } + defer response.Body.Close() + return &list, response.Process(&list) } @@ -100,6 +106,8 @@ func Add(ctx context.Context, name string, options *AddOptions) (string, error) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } @@ -121,6 +129,8 @@ func Remove(ctx context.Context, name, digest string, options *RemoveOptions) (s if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, response.Process(&idr) } @@ -145,18 +155,20 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt if err != nil { return "", err } - //SkipTLSVerify is special. We need to delete the param added by - //toparams and change the key and flip the bool + // SkipTLSVerify is special. We need to delete the param added by + // toparams and change the key and flip the bool if options.SkipTLSVerify != nil { params.Del("SkipTLSVerify") params.Set("tlsVerify", strconv.FormatBool(!options.GetSkipTLSVerify())) } params.Set("image", name) params.Set("destination", destination) - _, err = conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) + response, err := conn.DoRequest(nil, http.MethodPost, "/manifests/%s/push", params, nil, name) if err != nil { return "", err } + defer response.Body.Close() + return idr.ID, err } @@ -179,5 +191,6 @@ func Push(ctx context.Context, name, destination string, options *images.PushOpt // if err != nil { // return "", err // } +// defer response.Body.Close() // return idr.ID, response.Process(&idr) //} diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 17451c273..59207aa8d 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -34,6 +34,8 @@ func Create(ctx context.Context, options *CreateOptions) (*entities.NetworkCreat if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -53,6 +55,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) ([]e if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports[0]) } @@ -76,6 +80,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) ([]*en if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -99,6 +105,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.NetworkListRep if err != nil { return netList, err } + defer response.Body.Close() + return netList, response.Process(&netList) } @@ -133,6 +141,8 @@ func Disconnect(ctx context.Context, networkName string, ContainerNameOrID strin if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -166,6 +176,8 @@ func Connect(ctx context.Context, networkName string, ContainerNameOrID string, if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -179,6 +191,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -203,5 +217,7 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.NetworkPrune if err != nil { return nil, err } + defer response.Body.Close() + return prunedNetworks, response.Process(&prunedNetworks) } diff --git a/pkg/bindings/play/play.go b/pkg/bindings/play/play.go index 4b735c821..8451cd533 100644 --- a/pkg/bindings/play/play.go +++ b/pkg/bindings/play/play.go @@ -48,6 +48,8 @@ func Kube(ctx context.Context, path string, options *KubeOptions) (*entities.Pla if err != nil { return nil, err } + defer response.Body.Close() + if err := response.Process(&report); err != nil { return nil, err } diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index eb7b273cf..9d3ff322e 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -34,6 +34,8 @@ func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator, options if err != nil { return nil, err } + defer response.Body.Close() + return &pcr, response.Process(&pcr) } @@ -47,6 +49,8 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } @@ -67,6 +71,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -91,6 +97,8 @@ func Kill(ctx context.Context, nameOrID string, options *KillOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -109,6 +117,8 @@ func Pause(ctx context.Context, nameOrID string, options *PauseOptions) (*entiti if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -128,6 +138,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*entities.PodPruneRepo if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } @@ -152,6 +164,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.ListPodsReport if err != nil { return podsReports, err } + defer response.Body.Close() + return podsReports, response.Process(&podsReports) } @@ -170,6 +184,8 @@ func Restart(ctx context.Context, nameOrID string, options *RestartOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -192,6 +208,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) (*enti if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -210,6 +228,8 @@ func Start(ctx context.Context, nameOrID string, options *StartOptions) (*entiti if err != nil { return nil, err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { report.Id = nameOrID return &report, nil @@ -236,6 +256,8 @@ func Stop(ctx context.Context, nameOrID string, options *StopOptions) (*entities if err != nil { return nil, err } + defer response.Body.Close() + if response.StatusCode == http.StatusNotModified { report.Id = nameOrID return &report, nil @@ -261,6 +283,7 @@ func Top(ctx context.Context, nameOrID string, options *TopOptions) ([]string, e if err != nil { return nil, err } + defer response.Body.Close() body := handlers.PodTopOKBody{} if err = response.Process(&body); err != nil { @@ -293,6 +316,8 @@ func Unpause(ctx context.Context, nameOrID string, options *UnpauseOptions) (*en if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -318,5 +343,7 @@ func Stats(ctx context.Context, namesOrIDs []string, options *StatsOptions) ([]* if err != nil { return nil, err } + defer response.Body.Close() + return reports, response.Process(&reports) } diff --git a/pkg/bindings/secrets/secrets.go b/pkg/bindings/secrets/secrets.go index 091d38e56..b741d3e5c 100644 --- a/pkg/bindings/secrets/secrets.go +++ b/pkg/bindings/secrets/secrets.go @@ -22,6 +22,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.SecretInfoRepo if err != nil { return secrs, err } + defer response.Body.Close() + return secrs, response.Process(&secrs) } @@ -38,6 +40,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return inspect, err } + defer response.Body.Close() + return inspect, response.Process(&inspect) } @@ -52,6 +56,8 @@ func Remove(ctx context.Context, nameOrID string) error { if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -74,5 +80,7 @@ func Create(ctx context.Context, reader io.Reader, options *CreateOptions) (*ent if err != nil { return nil, err } + defer response.Body.Close() + return create, response.Process(&create) } diff --git a/pkg/bindings/system/info.go b/pkg/bindings/system/info.go index 244f9643e..8a307a4ca 100644 --- a/pkg/bindings/system/info.go +++ b/pkg/bindings/system/info.go @@ -9,12 +9,7 @@ import ( ) // Info returns information about the libpod environment and its stores -func Info(ctx context.Context, options *InfoOptions) (*define.Info, error) { - if options == nil { - options = new(InfoOptions) - } - _ = options - info := define.Info{} +func Info(ctx context.Context, _ *InfoOptions) (*define.Info, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -23,5 +18,8 @@ func Info(ctx context.Context, options *InfoOptions) (*define.Info, error) { if err != nil { return nil, err } + defer response.Body.Close() + + info := define.Info{} return &info, response.Process(&info) } diff --git a/pkg/bindings/system/system.go b/pkg/bindings/system/system.go index 310bcef15..719cde52e 100644 --- a/pkg/bindings/system/system.go +++ b/pkg/bindings/system/system.go @@ -31,6 +31,8 @@ func Events(ctx context.Context, eventChan chan entities.Event, cancelChan chan if err != nil { return err } + defer response.Body.Close() + if cancelChan != nil { go func() { <-cancelChan @@ -75,6 +77,8 @@ func Prune(ctx context.Context, options *PruneOptions) (*entities.SystemPruneRep if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } @@ -101,6 +105,7 @@ func Version(ctx context.Context, options *VersionOptions) (*entities.SystemVers if err != nil { return nil, err } + defer response.Body.Close() if err = response.Process(&component); err != nil { return nil, err @@ -141,5 +146,7 @@ func DiskUsage(ctx context.Context, options *DiskOptions) (*entities.SystemDfRep if err != nil { return nil, err } + defer response.Body.Close() + return &report, response.Process(&report) } diff --git a/pkg/bindings/test/attach_test.go b/pkg/bindings/test/attach_test.go index fbdf18d44..5c3ec48e4 100644 --- a/pkg/bindings/test/attach_test.go +++ b/pkg/bindings/test/attach_test.go @@ -81,10 +81,9 @@ var _ = Describe("Podman containers attach", func() { tickTock := time.NewTimer(2 * time.Second) go func() { <-tickTock.C - timeout := uint(5) - err := containers.Stop(bt.conn, ctnr.ID, new(containers.StopOptions).WithTimeout(timeout)) + err := containers.Stop(bt.conn, ctnr.ID, new(containers.StopOptions).WithTimeout(uint(5))) if err != nil { - GinkgoWriter.Write([]byte(err.Error())) + fmt.Fprint(GinkgoWriter, err.Error()) } }() diff --git a/pkg/bindings/test/common_test.go b/pkg/bindings/test/common_test.go index 9bac4b620..91ebe21fc 100644 --- a/pkg/bindings/test/common_test.go +++ b/pkg/bindings/test/common_test.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "strings" + "time" "github.com/containers/podman/v3/libpod/define" . "github.com/containers/podman/v3/pkg/bindings" @@ -150,11 +151,21 @@ func createTempDirInTempDir() (string, error) { } func (b *bindingTest) startAPIService() *gexec.Session { - var ( - cmd []string - ) - cmd = append(cmd, "--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock) - return b.runPodman(cmd) + cmd := []string{"--log-level=debug", "--events-backend=file", "system", "service", "--timeout=0", b.sock} + session := b.runPodman(cmd) + + sock := strings.TrimPrefix(b.sock, "unix://") + for i := 0; i < 10; i++ { + if _, err := os.Stat(sock); err != nil { + if !os.IsNotExist(err) { + break + } + time.Sleep(time.Second) + continue + } + break + } + return session } func (b *bindingTest) cleanup() { diff --git a/pkg/bindings/test/resource_test.go b/pkg/bindings/test/resource_test.go new file mode 100644 index 000000000..b12d1ccd6 --- /dev/null +++ b/pkg/bindings/test/resource_test.go @@ -0,0 +1,116 @@ +package test_bindings + +import ( + "context" + "fmt" + "io/fs" + "os" + "os/exec" + "path/filepath" + "reflect" + "strconv" + "syscall" + + "github.com/containers/podman/v3/pkg/bindings" + "github.com/containers/podman/v3/pkg/bindings/containers" + "github.com/containers/podman/v3/pkg/bindings/images" + "github.com/containers/podman/v3/pkg/bindings/pods" + "github.com/containers/podman/v3/pkg/bindings/system" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Verify Podman resources", func() { + var ( + bt *bindingTest + s *Session + ) + + BeforeEach(func() { + bt = newBindingTest() + s = bt.startAPIService() + err := bt.NewConnection() + Expect(err).ShouldNot(HaveOccurred()) + }) + + AfterEach(func() { + s.Kill() + bt.cleanup() + }) + + It("no leaked connections", func() { + conn, err := bindings.NewConnection(context.Background(), bt.sock) + Expect(err).ShouldNot(HaveOccurred()) + + // Record details on open file descriptors before using API + buffer := lsof() + + // Record open fd from /proc + start, err := readProc() + Expect(err).ShouldNot(HaveOccurred()) + + // Run some operations + _, err = system.Info(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = images.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = containers.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + _, err = pods.List(conn, nil) + Expect(err).ShouldNot(HaveOccurred()) + + podman, _ := bindings.GetClient(conn) + podman.Client.CloseIdleConnections() + + // Record open fd from /proc + finished, err := readProc() + Expect(err).ShouldNot(HaveOccurred()) + if !reflect.DeepEqual(finished, start) { + fmt.Fprintf(GinkgoWriter, "Open FDs:\nlsof Before:\n%s\n", buffer) + + // Record details on open file descriptors after using API + buffer := lsof() + fmt.Fprintf(GinkgoWriter, "lsof After:\n%s\n", buffer) + + // We know test has failed. Easier to let ginkgo format output. + Expect(finished).Should(Equal(start)) + } + }) +}) + +func lsof() string { + lsof := exec.Command("lsof", "+E", "-p", strconv.Itoa(os.Getpid())) + buffer, err := lsof.Output() + Expect(err).ShouldNot(HaveOccurred()) + return string(buffer) +} + +func readProc() ([]string, error) { + syscall.Sync() + + names := make([]string, 0) + err := filepath.WalkDir(fmt.Sprintf("/proc/%d/fd", os.Getpid()), + func(path string, d fs.DirEntry, err error) error { + name := path + " -> " + + switch { + case d.IsDir(): + return nil + case err != nil: + name += err.Error() + case d.Type()&fs.ModeSymlink != 0: + n, err := os.Readlink(path) + if err != nil && !os.IsNotExist(err) { + return err + } + if n == "" { + n = d.Type().String() + } + name += n + } + names = append(names, name) + return nil + }) + return names, err +} diff --git a/pkg/bindings/volumes/volumes.go b/pkg/bindings/volumes/volumes.go index fb58a1d1f..56cf13ade 100644 --- a/pkg/bindings/volumes/volumes.go +++ b/pkg/bindings/volumes/volumes.go @@ -33,6 +33,8 @@ func Create(ctx context.Context, config entities.VolumeCreateOptions, options *C if err != nil { return nil, err } + defer response.Body.Close() + return &v, response.Process(&v) } @@ -53,6 +55,8 @@ func Inspect(ctx context.Context, nameOrID string, options *InspectOptions) (*en if err != nil { return &inspect, err } + defer response.Body.Close() + return &inspect, response.Process(&inspect) } @@ -74,6 +78,8 @@ func List(ctx context.Context, options *ListOptions) ([]*entities.VolumeListRepo if err != nil { return vols, err } + defer response.Body.Close() + return vols, response.Process(&vols) } @@ -94,6 +100,8 @@ func Prune(ctx context.Context, options *PruneOptions) ([]*reports.PruneReport, if err != nil { return nil, err } + defer response.Body.Close() + return pruned, response.Process(&pruned) } @@ -112,6 +120,8 @@ func Remove(ctx context.Context, nameOrID string, options *RemoveOptions) error if err != nil { return err } + defer response.Body.Close() + return response.Process(nil) } @@ -125,5 +135,7 @@ func Exists(ctx context.Context, nameOrID string, options *ExistsOptions) (bool, if err != nil { return false, err } + defer response.Body.Close() + return response.IsSuccess(), nil } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index d2a7505a8..607e68256 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -8,6 +8,7 @@ import ( "github.com/containers/image/v5/types" "github.com/containers/podman/v3/libpod/define" + nettypes "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/storage/pkg/archive" "github.com/cri-o/ocicni/pkg/ocicni" @@ -208,7 +209,7 @@ type RestoreOptions struct { Name string TCPEstablished bool ImportPrevious string - PublishPorts []specgen.PortMapping + PublishPorts []nettypes.PortMapping Pod string } diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go index af996ad1e..a8023f7cf 100644 --- a/pkg/domain/entities/engine.go +++ b/pkg/domain/entities/engine.go @@ -39,6 +39,7 @@ type PodmanConfig struct { EngineMode EngineMode // ABI or Tunneling mode Identity string // ssh identity for connecting to server MaxWorks int // maximum number of parallel threads + MemoryProfile string // Hidden: Should memory profile be taken RegistriesConf string // allows for specifying a custom registries.conf Remote bool // Connection to Podman API Service will use RESTful API RuntimePath string // --runtime flag will set Engine.RuntimePath diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index d573e4704..5d3c9480e 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -92,6 +92,7 @@ type ContainerEngine interface { Version(ctx context.Context) (*SystemVersionReport, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error) + VolumeMounted(ctx context.Context, namesOrID string) (*BoolReport, error) VolumeInspect(ctx context.Context, namesOrIds []string, opts InspectOptions) ([]*VolumeInspectReport, []error, error) VolumeList(ctx context.Context, opts VolumeListOptions) ([]*VolumeListReport, error) VolumePrune(ctx context.Context, options VolumePruneOptions) ([]*reports.PruneReport, error) diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 3ec713edf..8a437061f 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -35,6 +35,8 @@ type GenerateKubeOptions struct { } // GenerateKubeReport +// +// FIXME: Podman4.0 should change io.Reader to io.ReaderCloser type GenerateKubeReport struct { // Reader - the io.Reader to reader the generated YAML file. Reader io.Reader diff --git a/pkg/domain/entities/play.go b/pkg/domain/entities/play.go index 89dfc08e9..01de73ebe 100644 --- a/pkg/domain/entities/play.go +++ b/pkg/domain/entities/play.go @@ -10,6 +10,8 @@ import ( type PlayKubeOptions struct { // Authfile - path to an authentication file. Authfile string + // Indicator to build all images with Containerfile or Dockerfile + Build bool // CertDir - to a directory containing TLS certifications and keys. CertDir string // Username for authenticating against the registry. diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 68e335f8d..c66bf96fc 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -122,6 +122,7 @@ type PodCreateOptions struct { Pid string Cpus float64 CpusetCpus string + Userns specgen.Namespace } type PodCreateReport struct { @@ -217,6 +218,7 @@ func (p *PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) error { s.CPUQuota = *cpuDat.Quota } } + s.Userns = p.Userns return nil } diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index 9e25b7bf8..db4c6bb8a 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -6,6 +6,7 @@ import ( buildahDefine "github.com/containers/buildah/define" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/events" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/storage/pkg/archive" ) @@ -40,7 +41,7 @@ type NetOptions struct { DNSServers []net.IP Network specgen.Namespace NoHosts bool - PublishPorts []specgen.PortMapping + PublishPorts []types.PortMapping StaticIP *net.IP StaticMAC *net.HardwareAddr // NetworkOptions are additional options for each network diff --git a/pkg/domain/filters/containers.go b/pkg/domain/filters/containers.go index dc9fed2a4..269cd2d27 100644 --- a/pkg/domain/filters/containers.go +++ b/pkg/domain/filters/containers.go @@ -214,7 +214,7 @@ func GenerateContainerFilterFuncs(filter string, filterValues []string, r *libpo networkMode := c.NetworkMode() // support docker like `--filter network=container:<IDorName>` // check if networkMode is configured as `container:<ctr>` - // peform a match against filter `container:<IDorName>` + // perform a match against filter `container:<IDorName>` // networks is already going to be empty if `container:<ctr>` is configured as Mode if strings.HasPrefix(networkMode, "container:") { networkModeContainerPart := strings.SplitN(networkMode, ":", 2) diff --git a/pkg/domain/filters/pods.go b/pkg/domain/filters/pods.go index 9a1c7d19d..9a2f0a3ba 100644 --- a/pkg/domain/filters/pods.go +++ b/pkg/domain/filters/pods.go @@ -116,6 +116,17 @@ func GeneratePodFilterFunc(filter string, filterValues []string) ( labels := p.Labels() return util.MatchLabelFilters(filterValues, labels) }, nil + case "until": + return func(p *libpod.Pod) bool { + until, err := util.ComputeUntilTimestamp(filterValues) + if err != nil { + return false + } + if p.CreatedTime().Before(until) { + return true + } + return false + }, nil case "network": return func(p *libpod.Pod) bool { infra, err := p.InfraContainer() diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index d257bad18..6224feff5 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -7,9 +7,11 @@ import ( "io" "io/ioutil" "os" + "path/filepath" "strconv" "strings" + buildahDefine "github.com/containers/buildah/define" "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/types" @@ -266,39 +268,69 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY } containers := make([]*libpod.Container, 0, len(podYAML.Spec.Containers)) + cwd, err := os.Getwd() + if err != nil { + return nil, err + } for _, container := range podYAML.Spec.Containers { // Contains all labels obtained from kube labels := make(map[string]string) - - // NOTE: set the pull policy to "newer". This will cover cases - // where the "latest" tag requires a pull and will also - // transparently handle "localhost/" prefixed files which *may* - // refer to a locally built image OR an image running a - // registry on localhost. - pullPolicy := config.PullPolicyNewer - if len(container.ImagePullPolicy) > 0 { - // Make sure to lower the strings since K8s pull policy - // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). - rawPolicy := string(container.ImagePullPolicy) - pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) - if err != nil { - return nil, err - } + var pulledImage *libimage.Image + buildFile, err := getBuildFile(container.Image, cwd) + if err != nil { + return nil, err } - // This ensures the image is the image store - pullOptions := &libimage.PullOptions{} - pullOptions.AuthFilePath = options.Authfile - pullOptions.CertDirPath = options.CertDir - pullOptions.SignaturePolicyPath = options.SignaturePolicy - pullOptions.Writer = writer - pullOptions.Username = options.Username - pullOptions.Password = options.Password - pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify - - pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) + existsLocally, err := ic.Libpod.LibimageRuntime().Exists(container.Image) if err != nil { return nil, err } + if (len(buildFile) > 0 && !existsLocally) || (len(buildFile) > 0 && options.Build) { + buildOpts := new(buildahDefine.BuildOptions) + commonOpts := new(buildahDefine.CommonBuildOptions) + buildOpts.ConfigureNetwork = buildahDefine.NetworkDefault + buildOpts.Isolation = buildahDefine.IsolationChroot + buildOpts.CommonBuildOpts = commonOpts + buildOpts.Output = container.Image + if _, _, err := ic.Libpod.Build(ctx, *buildOpts, []string{buildFile}...); err != nil { + return nil, err + } + i, _, err := ic.Libpod.LibimageRuntime().LookupImage(container.Image, new(libimage.LookupImageOptions)) + if err != nil { + return nil, err + } + pulledImage = i + } else { + // NOTE: set the pull policy to "newer". This will cover cases + // where the "latest" tag requires a pull and will also + // transparently handle "localhost/" prefixed files which *may* + // refer to a locally built image OR an image running a + // registry on localhost. + pullPolicy := config.PullPolicyNewer + if len(container.ImagePullPolicy) > 0 { + // Make sure to lower the strings since K8s pull policy + // may be capitalized (see bugzilla.redhat.com/show_bug.cgi?id=1985905). + rawPolicy := string(container.ImagePullPolicy) + pullPolicy, err = config.ParsePullPolicy(strings.ToLower(rawPolicy)) + if err != nil { + return nil, err + } + } + // This ensures the image is the image store + pullOptions := &libimage.PullOptions{} + pullOptions.AuthFilePath = options.Authfile + pullOptions.CertDirPath = options.CertDir + pullOptions.SignaturePolicyPath = options.SignaturePolicy + pullOptions.Writer = writer + pullOptions.Username = options.Username + pullOptions.Password = options.Password + pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify + + pulledImages, err := ic.Libpod.LibimageRuntime().Pull(ctx, container.Image, pullPolicy, pullOptions) + if err != nil { + return nil, err + } + pulledImage = pulledImages[0] + } // Handle kube annotations for k, v := range annotations { @@ -318,7 +350,7 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY specgenOpts := kube.CtrSpecGenOptions{ Container: container, - Image: pulledImages[0], + Image: pulledImage, Volumes: volumes, PodID: pod.ID(), PodName: podName, @@ -509,3 +541,48 @@ func sortKubeKinds(documentList [][]byte) ([][]byte, error) { return sortedDocumentList, nil } +func imageNamePrefix(imageName string) string { + prefix := imageName + s := strings.Split(prefix, ":") + if len(s) > 0 { + prefix = s[0] + } + s = strings.Split(prefix, "/") + if len(s) > 0 { + prefix = s[len(s)-1] + } + s = strings.Split(prefix, "@") + if len(s) > 0 { + prefix = s[0] + } + return prefix +} + +func getBuildFile(imageName string, cwd string) (string, error) { + buildDirName := imageNamePrefix(imageName) + containerfilePath := filepath.Join(cwd, buildDirName, "Containerfile") + dockerfilePath := filepath.Join(cwd, buildDirName, "Dockerfile") + + _, err := os.Stat(filepath.Join(containerfilePath)) + if err == nil { + logrus.Debugf("building %s with %s", imageName, containerfilePath) + return containerfilePath, nil + } + // If the error is not because the file does not exist, take + // a mulligan and try Dockerfile. If that also fails, return that + // error + if err != nil && !os.IsNotExist(err) { + logrus.Errorf("%v: unable to check for %s", err, containerfilePath) + } + + _, err = os.Stat(filepath.Join(dockerfilePath)) + if err == nil { + logrus.Debugf("building %s with %s", imageName, dockerfilePath) + return dockerfilePath, nil + } + // Strike two + if os.IsNotExist(err) { + return "", nil + } + return "", err +} diff --git a/pkg/domain/infra/abi/terminal/sigproxy_linux.go b/pkg/domain/infra/abi/terminal/sigproxy_linux.go index 26e199aee..a9bd2d5fb 100644 --- a/pkg/domain/infra/abi/terminal/sigproxy_linux.go +++ b/pkg/domain/infra/abi/terminal/sigproxy_linux.go @@ -12,13 +12,17 @@ import ( "github.com/sirupsen/logrus" ) +// Make sure the signal buffer is sufficiently big. +// runc is using the same value. +const signalBufferSize = 2048 + // ProxySignals ... func ProxySignals(ctr *libpod.Container) { // Stop catching the shutdown signals (SIGINT, SIGTERM) - they're going // to the container now. shutdown.Stop() - sigBuffer := make(chan os.Signal, 128) + sigBuffer := make(chan os.Signal, signalBufferSize) signal.CatchAll(sigBuffer) logrus.Debugf("Enabling signal proxying") diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index e077b10ea..1610c0b48 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -162,3 +162,19 @@ func (ic *ContainerEngine) VolumeExists(ctx context.Context, nameOrID string) (* } return &entities.BoolReport{Value: exists}, nil } + +// Volumemounted check if a given volume using plugin or filesystem is mounted or not. +func (ic *ContainerEngine) VolumeMounted(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + vol, err := ic.Libpod.LookupVolume(nameOrID) + if err != nil { + return nil, err + } + mountCount, err := vol.MountCount() + if err != nil { + return &entities.BoolReport{Value: false}, nil + } + if mountCount > 0 { + return &entities.BoolReport{Value: true}, nil + } + return &entities.BoolReport{Value: false}, nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index b638bfe24..81ddce42f 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -404,11 +404,11 @@ func (ic *ContainerEngine) ContainerLogs(_ context.Context, nameOrIDs []string, return err case line := <-stdoutCh: if opts.StdoutWriter != nil { - _, _ = io.WriteString(opts.StdoutWriter, line+"\n") + _, _ = io.WriteString(opts.StdoutWriter, line) } case line := <-stderrCh: if opts.StderrWriter != nil { - _, _ = io.WriteString(opts.StderrWriter, line+"\n") + _, _ = io.WriteString(opts.StderrWriter, line) } } } diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 0e768b30b..3d3cd52be 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -16,6 +16,9 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, return generate.Systemd(ic.ClientCtx, nameOrID, options) } +// GenerateKube Kubernetes YAML (v1 specification) for nameOrIDs +// +// Note: Caller is responsible for closing returned Reader func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrIDs []string, opts entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { options := new(generate.KubeOptions).WithService(opts.Service) return generate.Kube(ic.ClientCtx, nameOrIDs, options) diff --git a/pkg/domain/infra/tunnel/volumes.go b/pkg/domain/infra/tunnel/volumes.go index 2d231bad6..2b2b2c2a1 100644 --- a/pkg/domain/infra/tunnel/volumes.go +++ b/pkg/domain/infra/tunnel/volumes.go @@ -91,3 +91,9 @@ func (ic *ContainerEngine) VolumeExists(ctx context.Context, nameOrID string) (* Value: exists, }, nil } + +// Volumemounted check if a given volume using plugin or filesystem is mounted or not. +// TODO: Not used and exposed to tunnel. Will be used by `export` command which is unavailable to `podman-remote` +func (ic *ContainerEngine) VolumeMounted(ctx context.Context, nameOrID string) (*entities.BoolReport, error) { + return nil, errors.New("not implemented") +} diff --git a/pkg/machine/fcos.go b/pkg/machine/fcos.go index 11936aee7..49ec01e67 100644 --- a/pkg/machine/fcos.go +++ b/pkg/machine/fcos.go @@ -3,14 +3,14 @@ package machine import ( - "crypto/sha256" - "io/ioutil" url2 "net/url" + "os" "path/filepath" "runtime" "strings" digest "github.com/opencontainers/go-digest" + "github.com/sirupsen/logrus" ) // These should eventually be moved into machine/qemu as @@ -91,24 +91,23 @@ func UpdateAvailable(d *Download) (bool, error) { // check the sha of the local image if it exists // get the sha of the remote image // == dont bother to pull - files, err := ioutil.ReadDir(filepath.Dir(d.LocalPath)) + if _, err := os.Stat(d.LocalPath); os.IsNotExist(err) { + return false, nil + } + fd, err := os.Open(d.LocalPath) if err != nil { return false, err } - for _, file := range files { - if filepath.Base(d.LocalPath) == file.Name() { - b, err := ioutil.ReadFile(d.LocalPath) - if err != nil { - return false, err - } - s := sha256.Sum256(b) - sum := digest.NewDigestFromBytes(digest.SHA256, s[:]) - if sum.Encoded() == d.Sha256sum { - return true, nil - } + defer func() { + if err := fd.Close(); err != nil { + logrus.Error(err) } + }() + sum, err := digest.SHA256.FromReader(fd) + if err != nil { + return false, err } - return false, nil + return sum.Encoded() == d.Sha256sum, nil } func getFcosArch() string { diff --git a/pkg/machine/qemu/machine.go b/pkg/machine/qemu/machine.go index 7b1ebcb03..a92892957 100644 --- a/pkg/machine/qemu/machine.go +++ b/pkg/machine/qemu/machine.go @@ -605,10 +605,12 @@ func CheckActiveVM() (bool, string, error) { // startHostNetworking runs a binary on the host system that allows users // to setup port forwarding to the podman virtual machine func (v *MachineVM) startHostNetworking() error { - binary := filepath.Join("/usr/lib/podman/", machine.ForwarderBinaryName) - if _, err := os.Stat(binary); os.IsNotExist(err) { - return errors.Errorf("unable to find %s", binary) + // TODO we may wish to configure the directory in containers common + binary := filepath.Join("/usr/libexec/podman/", machine.ForwarderBinaryName) + if _, err := os.Stat(binary); err != nil { + return err } + // Listen on all at port 7777 for setting up and tearing // down forwarding listenSocket := "tcp://0.0.0.0:7777" diff --git a/pkg/rootless/rootless_linux.go b/pkg/rootless/rootless_linux.go index 9ef56acb4..c046ecde7 100644 --- a/pkg/rootless/rootless_linux.go +++ b/pkg/rootless/rootless_linux.go @@ -397,8 +397,6 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo return false, -1, errors.Wrapf(err, "error setting up the process") } - c := make(chan os.Signal, 1) - signals := []os.Signal{} for sig := 0; sig < numSig; sig++ { if sig == int(unix.SIGTSTP) { @@ -407,6 +405,7 @@ func becomeRootInUserNS(pausePid, fileToRead string, fileOutput *os.File) (_ boo signals = append(signals, unix.Signal(sig)) } + c := make(chan os.Signal, len(signals)) gosignal.Notify(c, signals...) defer gosignal.Reset() go func() { diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go index ede216bfe..9a2f93f8e 100644 --- a/pkg/rootlessport/rootlessport_linux.go +++ b/pkg/rootlessport/rootlessport_linux.go @@ -20,7 +20,6 @@ import ( "net" "os" "os/exec" - "os/signal" "path/filepath" "github.com/containernetworking/plugins/pkg/ns" @@ -106,30 +105,6 @@ func parent() error { return err } - exitC := make(chan os.Signal, 1) - defer close(exitC) - - go func() { - sigC := make(chan os.Signal, 1) - signal.Notify(sigC, unix.SIGPIPE) - defer func() { - signal.Stop(sigC) - close(sigC) - }() - - select { - case s := <-sigC: - if s == unix.SIGPIPE { - if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { - unix.Dup2(int(f.Fd()), 1) // nolint:errcheck - unix.Dup2(int(f.Fd()), 2) // nolint:errcheck - f.Close() - } - } - case <-exitC: - } - }() - socketDir := filepath.Join(cfg.TmpDir, "rp") err = os.MkdirAll(socketDir, 0700) if err != nil { @@ -251,8 +226,16 @@ outer: go serve(socket, driver) } - // write and close ReadyFD (convention is same as slirp4netns --ready-fd) logrus.Info("ready") + + // https://github.com/containers/podman/issues/11248 + // Copy /dev/null to stdout and stderr to prevent SIGPIPE errors + if f, err := os.OpenFile("/dev/null", os.O_WRONLY, 0755); err == nil { + unix.Dup2(int(f.Fd()), 1) // nolint:errcheck + unix.Dup2(int(f.Fd()), 2) // nolint:errcheck + f.Close() + } + // write and close ReadyFD (convention is same as slirp4netns --ready-fd) if _, err := readyW.Write([]byte("1")); err != nil { return err } diff --git a/pkg/specgen/generate/kube/kube.go b/pkg/specgen/generate/kube/kube.go index fb7eb99a2..04b4e5ab3 100644 --- a/pkg/specgen/generate/kube/kube.go +++ b/pkg/specgen/generate/kube/kube.go @@ -12,6 +12,7 @@ import ( "github.com/containers/common/pkg/parse" "github.com/containers/common/pkg/secrets" "github.com/containers/image/v5/manifest" + "github.com/containers/podman/v3/libpod/network/types" ann "github.com/containers/podman/v3/pkg/annotations" "github.com/containers/podman/v3/pkg/specgen" "github.com/containers/podman/v3/pkg/specgen/generate" @@ -303,6 +304,8 @@ func ToSpecGen(ctx context.Context, opts *CtrSpecGenOptions) (*specgen.SpecGener if opts.NetNSIsHost { s.NetNS.NSMode = specgen.Host } + // Always set the userns to host since k8s doesn't have support for userns yet + s.UserNS.NSMode = specgen.Host // Add labels that come from kube if len(s.Labels) == 0 { @@ -586,8 +589,8 @@ func envVarValue(env v1.EnvVar, opts *CtrSpecGenOptions) (string, error) { // getPodPorts converts a slice of kube container descriptions to an // array of portmapping -func getPodPorts(containers []v1.Container) []specgen.PortMapping { - var infraPorts []specgen.PortMapping +func getPodPorts(containers []v1.Container) []types.PortMapping { + var infraPorts []types.PortMapping for _, container := range containers { for _, p := range container.Ports { if p.HostPort != 0 && p.ContainerPort == 0 { @@ -596,7 +599,7 @@ func getPodPorts(containers []v1.Container) []specgen.PortMapping { if p.Protocol == "" { p.Protocol = "tcp" } - portBinding := specgen.PortMapping{ + portBinding := types.PortMapping{ HostPort: uint16(p.HostPort), ContainerPort: uint16(p.ContainerPort), Protocol: strings.ToLower(string(p.Protocol)), diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index f41186ae4..80790dcc1 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -175,6 +175,11 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if pod == nil || infraCtr == nil { return nil, errNoInfra } + // Inherit the user from the infra container if it is set and --user has not + // been set explicitly + if infraCtr.User() != "" && s.User == "" { + toReturn = append(toReturn, libpod.WithUser(infraCtr.User())) + } toReturn = append(toReturn, libpod.WithUserNSFrom(infraCtr)) case specgen.FromContainer: userCtr, err := rt.LookupContainer(s.UserNS.Value) @@ -184,7 +189,10 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. toReturn = append(toReturn, libpod.WithUserNSFrom(userCtr)) } - if s.IDMappings != nil { + // This wipes the UserNS settings that get set from the infra container + // when we are inheritting from the pod. So only apply this if the container + // is not being created in a pod. + if s.IDMappings != nil && pod == nil { toReturn = append(toReturn, libpod.WithIDMappings(*s.IDMappings)) } if s.User != "" { @@ -234,7 +242,7 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. } toReturn = append(toReturn, libpod.WithNetNSFrom(netCtr)) case specgen.Slirp: - portMappings, err := createPortMappings(ctx, s, imageData) + portMappings, expose, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } @@ -242,15 +250,15 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if s.NetNS.Value != "" { val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value) } - toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil)) + toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, val, nil)) case specgen.Private: fallthrough case specgen.Bridge: - portMappings, err := createPortMappings(ctx, s, imageData) + portMappings, expose, err := createPortMappings(ctx, s, imageData) if err != nil { return nil, err } - toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "bridge", s.CNINetworks)) + toReturn = append(toReturn, libpod.WithNetNS(portMappings, expose, postConfigureNetNS, "bridge", s.CNINetworks)) } if s.UseImageHosts { @@ -379,46 +387,8 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt } // User - switch s.UserNS.NSMode { - case specgen.Path: - if _, err := os.Stat(s.UserNS.Value); err != nil { - return errors.Wrap(err, "cannot find specified user namespace path") - } - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); err != nil { - return err - } - // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping - g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) - g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) - case specgen.Host: - if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil { - return err - } - case specgen.KeepID: - var ( - err error - uid, gid int - ) - s.IDMappings, uid, gid, err = util.GetKeepIDMapping() - if err != nil { - return err - } - g.SetProcessUID(uint32(uid)) - g.SetProcessGID(uint32(gid)) - fallthrough - case specgen.Private: - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } - if s.IDMappings == nil || (len(s.IDMappings.UIDMap) == 0 && len(s.IDMappings.GIDMap) == 0) { - return errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") - } - for _, uidmap := range s.IDMappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) - } - for _, gidmap := range s.IDMappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) - } + if _, err := specgen.SetupUserNS(s.IDMappings, s.UserNS, g); err != nil { + return err } // Cgroup @@ -474,7 +444,7 @@ func specConfigureNamespaces(s *specgen.SpecGenerator, g *generate.Generator, rt // GetNamespaceOptions transforms a slice of kernel namespaces // into a slice of pod create options. Currently, not all // kernel namespaces are supported, and they will be returned in an error -func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { +func GetNamespaceOptions(ns []string, netnsIsHost bool) ([]libpod.PodCreateOption, error) { var options []libpod.PodCreateOption var erroredOptions []libpod.PodCreateOption if ns == nil { @@ -486,7 +456,10 @@ func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { case "cgroup": options = append(options, libpod.WithPodCgroups()) case "net": - options = append(options, libpod.WithPodNet()) + // share the netns setting with other containers in the pod only when it is not set to host + if !netnsIsHost { + options = append(options, libpod.WithPodNet()) + } case "mnt": return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") case "pid": diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index aab29499e..426cf1b6d 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -27,11 +27,16 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod ) if !p.NoInfra { options = append(options, libpod.WithInfraContainer()) - nsOptions, err := GetNamespaceOptions(p.SharedNamespaces) + nsOptions, err := GetNamespaceOptions(p.SharedNamespaces, p.NetNS.IsHost()) if err != nil { return nil, err } options = append(options, nsOptions...) + // Use pod user and infra userns only when --userns is not set to host + if !p.Userns.IsHost() { + options = append(options, libpod.WithPodUser()) + options = append(options, libpod.WithPodUserns(p.Userns)) + } // Make our exit command storageConfig := rt.StorageConfig() @@ -154,5 +159,6 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod if len(p.InfraConmonPidFile) > 0 { options = append(options, libpod.WithInfraConmonPidFile(p.InfraConmonPidFile)) } + return options, nil } diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index c00ad19fb..a300f8014 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/containers/common/libimage" + "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/podman/v3/utils" "github.com/containers/podman/v3/pkg/specgen" @@ -24,7 +25,7 @@ const ( // Parse port maps to OCICNI port mappings. // Returns a set of OCICNI port mappings, and maps of utilized container and // host ports. -func ParsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { +func ParsePortMapping(portMappings []types.PortMapping) ([]ocicni.PortMapping, map[string]map[string]map[uint16]uint16, map[string]map[string]map[uint16]uint16, error) { // First, we need to validate the ports passed in the specgen, and then // convert them into CNI port mappings. type tempMapping struct { @@ -253,17 +254,15 @@ func ParsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, } // Make final port mappings for the container -func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]ocicni.PortMapping, error) { +func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData *libimage.ImageData) ([]ocicni.PortMapping, map[uint16][]string, error) { finalMappings, containerPortValidate, hostPortValidate, err := ParsePortMapping(s.PortMappings) if err != nil { - return nil, err + return nil, nil, err } - // If not publishing exposed ports, or if we are publishing and there is - // nothing to publish - then just return the port mappings we've made so - // far. - if !s.PublishExposedPorts || (len(s.Expose) == 0 && imageData == nil) { - return finalMappings, nil + // No exposed ports so return the port mappings we've made so far. + if len(s.Expose) == 0 && imageData == nil { + return finalMappings, nil, nil } logrus.Debugf("Adding exposed ports") @@ -272,7 +271,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData if imageData != nil { expose, err = GenExposedPorts(imageData.Config.ExposedPorts) if err != nil { - return nil, err + return nil, nil, err } } @@ -288,11 +287,11 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData // Validate protocol first protocols, err := checkProtocol(proto, false) if err != nil { - return nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port) + return nil, nil, errors.Wrapf(err, "error validating protocols for exposed port %d", port) } if port == 0 { - return nil, errors.Errorf("cannot expose 0 as it is not a valid port number") + return nil, nil, errors.Errorf("cannot expose 0 as it is not a valid port number") } // Check to see if the port is already present in existing @@ -316,6 +315,11 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData } } + // If not publishing exposed ports return mappings and exposed ports. + if !s.PublishExposedPorts { + return finalMappings, toExpose, nil + } + // We now have a final list of ports that we want exposed. // Let's find empty, unallocated host ports for them. for port, protocols := range toExpose { @@ -331,7 +335,7 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData // unfortunate for the UDP case. candidate, err := utils.GetRandomPort() if err != nil { - return nil, err + return nil, nil, err } // Check if the host port is already bound @@ -362,12 +366,12 @@ func createPortMappings(ctx context.Context, s *specgen.SpecGenerator, imageData } if tries == 0 && hostPort == 0 { // We failed to find an open port. - return nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port) + return nil, nil, errors.Errorf("failed to find an open port to expose container port %d on the host", port) } } } - return finalMappings, nil + return finalMappings, nil, nil } // Check a string to ensure it is a comma-separated set of valid protocols diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go index 13f336594..de655ad7d 100644 --- a/pkg/specgen/generate/storage.go +++ b/pkg/specgen/generate/storage.go @@ -10,6 +10,7 @@ import ( "github.com/containers/common/libimage" "github.com/containers/common/pkg/config" + "github.com/containers/common/pkg/parse" "github.com/containers/podman/v3/libpod" "github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/pkg/specgen" @@ -59,6 +60,9 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru for _, m := range s.Mounts { // Ensure that mount dest is clean, so that it can be // compared against named volumes and avoid duplicate mounts. + if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil { + return nil, nil, nil, err + } cleanDestination := filepath.Clean(m.Destination) if _, ok := unifiedMounts[cleanDestination]; ok { return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified mounts - multiple mounts at %q", cleanDestination) @@ -67,34 +71,54 @@ func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Ru } for _, m := range commonMounts { - if _, ok := unifiedMounts[m.Destination]; !ok { - unifiedMounts[m.Destination] = m + if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil { + return nil, nil, nil, err + } + cleanDestination := filepath.Clean(m.Destination) + if _, ok := unifiedMounts[cleanDestination]; !ok { + unifiedMounts[cleanDestination] = m } } for _, v := range s.Volumes { - if _, ok := unifiedVolumes[v.Dest]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Dest) + if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil { + return nil, nil, nil, err } - unifiedVolumes[v.Dest] = v + cleanDestination := filepath.Clean(v.Dest) + if _, ok := unifiedVolumes[cleanDestination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", cleanDestination) + } + unifiedVolumes[cleanDestination] = v } for _, v := range commonVolumes { - if _, ok := unifiedVolumes[v.Dest]; !ok { - unifiedVolumes[v.Dest] = v + if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil { + return nil, nil, nil, err + } + cleanDestination := filepath.Clean(v.Dest) + if _, ok := unifiedVolumes[cleanDestination]; !ok { + unifiedVolumes[cleanDestination] = v } } for _, v := range s.OverlayVolumes { - if _, ok := unifiedOverlays[v.Destination]; ok { - return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", v.Destination) + if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil { + return nil, nil, nil, err } - unifiedOverlays[v.Destination] = v + cleanDestination := filepath.Clean(v.Destination) + if _, ok := unifiedOverlays[cleanDestination]; ok { + return nil, nil, nil, errors.Wrapf(errDuplicateDest, "conflict in specified volumes - multiple volumes at %q", cleanDestination) + } + unifiedOverlays[cleanDestination] = v } for _, v := range commonOverlayVolumes { - if _, ok := unifiedOverlays[v.Destination]; ok { - unifiedOverlays[v.Destination] = v + if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil { + return nil, nil, nil, err + } + cleanDestination := filepath.Clean(v.Destination) + if _, ok := unifiedOverlays[cleanDestination]; !ok { + unifiedOverlays[cleanDestination] = v } } @@ -190,6 +214,9 @@ func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGe } for volume := range inspect.Config.Volumes { logrus.Debugf("Image has volume at %q", volume) + if err = parse.ValidateVolumeCtrDir(volume); err != nil { + return nil, nil, err + } cleanDest := filepath.Clean(volume) switch mode { case "", "anonymous": @@ -304,9 +331,13 @@ func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]s if _, ok := finalMounts[namedVol.Dest]; ok { logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) } + if err = parse.ValidateVolumeCtrDir(namedVol.Dest); err != nil { + return nil, nil, err + } + cleanDest := filepath.Clean(namedVol.Dest) newVol := new(specgen.NamedVolume) - newVol.Dest = namedVol.Dest + newVol.Dest = cleanDest newVol.Options = namedVol.Options newVol.Name = namedVol.Name diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 76fa66bc7..2f4c48811 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,10 +1,16 @@ package specgen import ( + "fmt" + "os" "strings" "github.com/containers/podman/v3/pkg/cgroups" "github.com/containers/podman/v3/pkg/rootless" + "github.com/containers/podman/v3/pkg/util" + "github.com/containers/storage" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/runtime-tools/generate" "github.com/pkg/errors" ) @@ -103,6 +109,13 @@ func (n *Namespace) IsKeepID() bool { return n.NSMode == KeepID } +func (n *Namespace) String() string { + if n.Value != "" { + return fmt.Sprintf("%s:%s", n.NSMode, n.Value) + } + return string(n.NSMode) +} + func validateUserNS(n *Namespace) error { if n == nil { return nil @@ -323,3 +336,48 @@ func ParseNetworkString(network string) (Namespace, []string, map[string][]strin } return ns, cniNets, networkOptions, nil } + +func SetupUserNS(idmappings *storage.IDMappingOptions, userns Namespace, g *generate.Generator) (string, error) { + // User + var user string + switch userns.NSMode { + case Path: + if _, err := os.Stat(userns.Value); err != nil { + return user, errors.Wrap(err, "cannot find specified user namespace path") + } + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), userns.Value); err != nil { + return user, err + } + // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping + g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) + g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) + case Host: + if err := g.RemoveLinuxNamespace(string(spec.UserNamespace)); err != nil { + return user, err + } + case KeepID: + mappings, uid, gid, err := util.GetKeepIDMapping() + if err != nil { + return user, err + } + idmappings = mappings + g.SetProcessUID(uint32(uid)) + g.SetProcessGID(uint32(gid)) + user = fmt.Sprintf("%d:%d", uid, gid) + fallthrough + case Private: + if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { + return user, err + } + if idmappings == nil || (len(idmappings.UIDMap) == 0 && len(idmappings.GIDMap) == 0) { + return user, errors.Errorf("must provide at least one UID or GID mapping to configure a user namespace") + } + for _, uidmap := range idmappings.UIDMap { + g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) + } + for _, gidmap := range idmappings.GIDMap { + g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) + } + } + return user, nil +} diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go index 02237afe9..386571d11 100644 --- a/pkg/specgen/podspecgen.go +++ b/pkg/specgen/podspecgen.go @@ -3,6 +3,7 @@ package specgen import ( "net" + "github.com/containers/podman/v3/libpod/network/types" spec "github.com/opencontainers/runtime-spec/specs-go" ) @@ -67,6 +68,10 @@ type PodBasicConfig struct { // Optional (defaults to private if unset). This sets the PID namespace of the infra container // This configuration will then be shared with the entire pod if PID namespace sharing is enabled via --share Pid Namespace `json:"pid,omitempty:"` + // Userns is used to indicate which kind of Usernamespace to enter. + // Any containers created within the pod will inherit the pod's userns settings. + // Optional + Userns Namespace `json:"userns,omitempty"` } // PodNetworkConfig contains networking configuration for a pod. @@ -98,7 +103,7 @@ type PodNetworkConfig struct { // container, this will forward the ports to the entire pod. // Only available if NetNS is set to Bridge or Slirp. // Optional. - PortMappings []PortMapping `json:"portmappings,omitempty"` + PortMappings []types.PortMapping `json:"portmappings,omitempty"` // CNINetworks is a list of CNI networks that the infra container will // join. As, by default, containers share their network with the infra // container, these networks will effectively be joined by the diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index fc647227e..0c30c498a 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -5,6 +5,7 @@ import ( "syscall" "github.com/containers/image/v5/manifest" + nettypes "github.com/containers/podman/v3/libpod/network/types" "github.com/containers/storage/types" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -184,7 +185,7 @@ type ContainerBasicConfig struct { // Optional. EnvSecrets map[string]string `json:"secret_env,omitempty"` // InitContainerType describes if this container is an init container - // and if so, what type: always or oneshot + // and if so, what type: always or once InitContainerType string `json:"init_container_type"` // Personality allows users to configure different execution domains. // Execution domains tell Linux how to map signal numbers into signal actions. @@ -393,7 +394,7 @@ type ContainerNetworkConfig struct { // PortBindings is a set of ports to map into the container. // Only available if NetNS is set to bridge or slirp. // Optional. - PortMappings []PortMapping `json:"portmappings,omitempty"` + PortMappings []nettypes.PortMapping `json:"portmappings,omitempty"` // PublishExposedPorts will publish ports specified in the image to // random unused ports (guaranteed to be above 1024) on the host. // This is based on ports set in Expose below, and any ports specified @@ -506,36 +507,6 @@ type SpecGenerator struct { ContainerHealthCheckConfig } -// PortMapping is one or more ports that will be mapped into the container. -type PortMapping struct { - // HostIP is the IP that we will bind to on the host. - // If unset, assumed to be 0.0.0.0 (all interfaces). - HostIP string `json:"host_ip,omitempty"` - // ContainerPort is the port number that will be exposed from the - // container. - // Mandatory. - ContainerPort uint16 `json:"container_port"` - // HostPort is the port number that will be forwarded from the host into - // the container. - // If omitted, a random port on the host (guaranteed to be over 1024) - // will be assigned. - HostPort uint16 `json:"host_port,omitempty"` - // Range is the number of ports that will be forwarded, starting at - // HostPort and ContainerPort and counting up. - // This is 1-indexed, so 1 is assumed to be a single port (only the - // Hostport:Containerport mapping will be added), 2 is two ports (both - // Hostport:Containerport and Hostport+1:Containerport+1), etc. - // If unset, assumed to be 1 (a single port). - // Both hostport + range and containerport + range must be less than - // 65536. - Range uint16 `json:"range,omitempty"` - // Protocol is the protocol forward. - // Must be either "tcp", "udp", and "sctp", or some combination of these - // separated by commas. - // If unset, assumed to be TCP. - Protocol string `json:"protocol,omitempty"` -} - type Secret struct { Source string UID uint32 diff --git a/pkg/specgen/volumes.go b/pkg/specgen/volumes.go index d85d2bdd1..eca8c0c35 100644 --- a/pkg/specgen/volumes.go +++ b/pkg/specgen/volumes.go @@ -1,7 +1,6 @@ package specgen import ( - "path/filepath" "strings" "github.com/containers/common/pkg/parse" @@ -93,11 +92,6 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na return nil, nil, nil, errors.New("host directory cannot be empty") } } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, nil, err - } - - cleanDest := filepath.Clean(dest) if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { // This is not a named volume @@ -120,7 +114,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na if overlayFlag { // This is a overlay volume newOverlayVol := new(OverlayVolume) - newOverlayVol.Destination = cleanDest + newOverlayVol.Destination = dest newOverlayVol.Source = src newOverlayVol.Options = options @@ -130,7 +124,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na overlayVolumes[newOverlayVol.Destination] = newOverlayVol } else { newMount := spec.Mount{ - Destination: cleanDest, + Destination: dest, Type: "bind", Source: src, Options: options, @@ -144,7 +138,7 @@ func GenVolumeMounts(volumeFlag []string) (map[string]spec.Mount, map[string]*Na // This is a named volume newNamedVol := new(NamedVolume) newNamedVol.Name = src - newNamedVol.Dest = cleanDest + newNamedVol.Dest = dest newNamedVol.Options = options if _, ok := volumes[newNamedVol.Dest]; ok { diff --git a/pkg/systemd/dbus.go b/pkg/systemd/dbus.go index 718082526..c49f537b6 100644 --- a/pkg/systemd/dbus.go +++ b/pkg/systemd/dbus.go @@ -9,8 +9,106 @@ import ( "github.com/containers/podman/v3/pkg/rootless" "github.com/coreos/go-systemd/v22/dbus" godbus "github.com/godbus/dbus/v5" + "github.com/sirupsen/logrus" ) +// IsSystemdSessionValid checks if sessions is valid for provided rootless uid. +func IsSystemdSessionValid(uid int) bool { + var conn *godbus.Conn + var err error + var object godbus.BusObject + var seat0Path godbus.ObjectPath + dbusDest := "org.freedesktop.login1" + dbusInterface := "org.freedesktop.login1.Manager" + dbusPath := "/org/freedesktop/login1" + + if rootless.IsRootless() { + conn, err = GetLogindConnection(rootless.GetRootlessUID()) + object = conn.Object(dbusDest, godbus.ObjectPath(dbusPath)) + if err != nil { + //unable to fetch systemd object for logind + logrus.Debugf("systemd-logind: %s", err) + return false + } + object = conn.Object(dbusDest, godbus.ObjectPath(dbusPath)) + if err := object.Call(dbusInterface+".GetSeat", 0, "seat0").Store(&seat0Path); err != nil { + //unable to get seat0 path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + seat0Obj := conn.Object(dbusDest, seat0Path) + activeSession, err := seat0Obj.GetProperty(dbusDest + ".Seat.ActiveSession") + if err != nil { + //unable to get active sessions. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionMap, ok := activeSession.Value().([]interface{}) + if !ok || len(activeSessionMap) < 2 { + //unable to get active session map. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionPath, ok := activeSessionMap[1].(godbus.ObjectPath) + if !ok { + //unable to fetch active session path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + activeSessionObj := conn.Object(dbusDest, activeSessionPath) + sessionUser, err := activeSessionObj.GetProperty(dbusDest + ".Session.User") + if err != nil { + //unable to fetch session user from activeSession path. + logrus.Debugf("systemd-logind: %s", err) + return false + } + dbusUser, ok := sessionUser.Value().([]interface{}) + if !ok { + // not a valid user. + return false + } + if len(dbusUser) < 2 { + // not a valid session user. + return false + } + activeUID, ok := dbusUser[0].(uint32) + if !ok { + return false + } + //active session found which belongs to following rootless user + if activeUID == uint32(uid) { + return true + } + return false + } + return true +} + +// GetDbusConnection returns an user connection to D-BUS +func GetLogindConnection(uid int) (*godbus.Conn, error) { + return dbusAuthConnectionLogind(uid) +} + +func dbusAuthConnectionLogind(uid int) (*godbus.Conn, error) { + var conn *godbus.Conn + var err error + conn, err = godbus.SystemBusPrivate() + if err != nil { + return nil, err + } + methods := []godbus.Auth{godbus.AuthExternal(strconv.Itoa(uid))} + if err = conn.Auth(methods); err != nil { + conn.Close() + return nil, err + } + err = conn.Hello() + if err != nil { + conn.Close() + return nil, err + } + return conn, nil +} + func dbusAuthRootlessConnection(createBus func(opts ...godbus.ConnOption) (*godbus.Conn, error)) (*godbus.Conn, error) { conn, err := createBus() if err != nil { diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 45e12014a..49465fb30 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -71,11 +71,12 @@ func filterCommonContainerFlags(command []string, argCount int) []string { case s == "--rm": // Boolean flags support --flag and --flag={true,false}. continue - case s == "--sdnotify", s == "--cgroups": + case s == "--sdnotify", s == "--cgroups", s == "--cidfile": i++ continue case strings.HasPrefix(s, "--rm="), - strings.HasPrefix(s, "--cgroups="): + strings.HasPrefix(s, "--cgroups="), + strings.HasPrefix(s, "--cidfile="): continue } processed = append(processed, s) diff --git a/pkg/systemd/generate/common_test.go b/pkg/systemd/generate/common_test.go index 3e2ac015f..80abebb26 100644 --- a/pkg/systemd/generate/common_test.go +++ b/pkg/systemd/generate/common_test.go @@ -103,12 +103,12 @@ func TestFilterCommonContainerFlags(t *testing.T) { }, { []string{"podman", "run", "--cidfile", "foo", "alpine"}, - []string{"podman", "run", "--cidfile", "foo", "alpine"}, + []string{"podman", "run", "alpine"}, 1, }, { []string{"podman", "run", "--cidfile=foo", "alpine"}, - []string{"podman", "run", "--cidfile=foo", "alpine"}, + []string{"podman", "run", "alpine"}, 1, }, { diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 78b81b54b..931f13972 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -233,9 +233,10 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst info.Type = "notify" info.NotifyAccess = "all" info.PIDFile = "" - info.ContainerIDFile = "" - info.ExecStop = "" - info.ExecStopPost = "" + info.ContainerIDFile = "%t/%n.ctr-id" + info.ExecStartPre = "/bin/rm -f {{{{.ContainerIDFile}}}}" + info.ExecStop = "{{{{.Executable}}}} stop --ignore --cidfile={{{{.ContainerIDFile}}}}" + info.ExecStopPost = "{{{{.Executable}}}} rm -f --ignore --cidfile={{{{.ContainerIDFile}}}}" // The create command must at least have three arguments: // /usr/bin/podman run $IMAGE index := 0 @@ -258,6 +259,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst } startCommand = append(startCommand, "run", + "--cidfile={{{{.ContainerIDFile}}}}", "--cgroups=no-conmon", "--rm", ) diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index 6141950d0..c60c301cc 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -130,7 +130,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman container run --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman container run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -152,7 +155,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman container run --cgroups=no-conmon --rm -d --replace --sdnotify=container --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman container run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm -d --replace --sdnotify=container --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN "foo=arg \"with \" space" +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -174,7 +180,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -196,7 +205,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --pod-id-file %t/pod-foobar.pod-id-file --sdnotify=conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --pod-id-file %t/pod-foobar.pod-id-file --sdnotify=conmon --replace -d --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -218,7 +230,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon --replace --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon --replace --detach --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -240,7 +255,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -263,9 +281,12 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=102 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon ` + +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon ` + detachparam + ` awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -289,7 +310,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=102 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test -p 80:80 awesome-image:latest somecmd --detach=false +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test -p 80:80 awesome-image:latest somecmd --detach=false +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -311,7 +335,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=102 -ExecStart=/usr/bin/podman --events-backend none --runroot /root run --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman --events-backend none --runroot /root run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -333,7 +360,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman container run --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman container run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -355,7 +385,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test --log-driver=journald --log-opt=tag={{.Name}} awesome-image:latest +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test --log-driver=journald --log-opt=tag={{.Name}} awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -377,7 +410,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test awesome-image:latest sh -c "kill $$$$ && echo %%\\" +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --replace --name test awesome-image:latest sh -c "kill $$$$ && echo %%\\" +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -399,7 +435,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d --conmon-pidfile=foo --cidfile=foo awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo alpine +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --conmon-pidfile=foo awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo alpine +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -421,7 +460,10 @@ RequiresMountsFor=/var/run/containers/storage Environment=PODMAN_SYSTEMD_UNIT=%n Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --pod-id-file %t/pod-foobar.pod-id-file --sdnotify=conmon -d --conmon-pidfile=foo --cidfile=foo awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo --pod-id-file /tmp/pod-foobar.pod-id-file alpine +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --pod-id-file %t/pod-foobar.pod-id-file --sdnotify=conmon -d --conmon-pidfile=foo awesome-image:latest podman run --cgroups=foo --conmon-pidfile=foo --cidfile=foo --pod-id-file /tmp/pod-foobar.pod-id-file alpine +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all @@ -444,7 +486,10 @@ Environment=PODMAN_SYSTEMD_UNIT=%n Environment=FOO=abc "BAR=my test" USER=%%a Restart=always TimeoutStopSec=70 -ExecStart=/usr/bin/podman run --cgroups=no-conmon --rm --sdnotify=conmon -d --env FOO --env=BAR --env=MYENV=2 -e USER awesome-image:latest +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d --env FOO --env=BAR --env=MYENV=2 -e USER awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id Type=notify NotifyAccess=all diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 37a00c25c..208d815d9 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -621,6 +621,12 @@ func ValidateSysctls(strSlice []string) (map[string]string, error) { if len(arr) < 2 { return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val) } + + trimmed := fmt.Sprintf("%s=%s", strings.TrimSpace(arr[0]), strings.TrimSpace(arr[1])) + if trimmed != val { + return nil, errors.Errorf("'%s' is invalid, extra spaces found", val) + } + if validSysctlMap[arr[0]] { sysctl[arr[0]] = arr[1] continue diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index 35322c7ea..3d74d4c78 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -1,6 +1,7 @@ package util import ( + "fmt" "testing" "time" @@ -259,6 +260,28 @@ func TestValidateSysctlBadSysctl(t *testing.T) { assert.Error(t, err) } +func TestValidateSysctlBadSysctlWithExtraSpaces(t *testing.T) { + expectedError := "'%s' is invalid, extra spaces found" + + // should fail fast on first sysctl + strSlice1 := []string{ + "net.ipv4.ping_group_range = 0 0", + "net.ipv4.ping_group_range=0 0 ", + } + _, err := ValidateSysctls(strSlice1) + assert.Error(t, err) + assert.Equal(t, err.Error(), fmt.Sprintf(expectedError, strSlice1[0])) + + // should fail on second sysctl + strSlice2 := []string{ + "net.ipv4.ping_group_range=0 0", + "net.ipv4.ping_group_range=0 0 ", + } + _, err = ValidateSysctls(strSlice2) + assert.Error(t, err) + assert.Equal(t, err.Error(), fmt.Sprintf(expectedError, strSlice2[1])) +} + func TestCoresToPeriodAndQuota(t *testing.T) { cores := 1.0 expectedPeriod := DefaultCPUPeriod |