diff options
Diffstat (limited to 'pkg')
39 files changed, 690 insertions, 179 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 1ae6a990b..b1ef08cda 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strings" + "syscall" "github.com/containers/podman/v2/libpod" "github.com/containers/podman/v2/libpod/define" @@ -169,16 +170,16 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { return } - err = con.Kill(uint(sig)) + signal := uint(sig) + + err = con.Kill(signal) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) } - if utils.IsLibpodRequest(r) { - // the kill behavior for docker differs from podman in that they appear to wait - // for the Container to croak so the exit code is accurate immediately after the - // kill is sent. libpod does not. but we can add a wait here only for the docker - // side of things and mimic that behavior + // Docker waits for the container to stop if the signal is 0 or + // SIGKILL. + if !utils.IsLibpodRequest(r) && (signal == 0 || syscall.Signal(signal) == syscall.SIGKILL) { if _, err = con.Wait(); err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "failed to wait for Container %s", con.ID())) return diff --git a/pkg/api/handlers/compat/images.go b/pkg/api/handlers/compat/images.go index 6872dd780..8765e20ca 100644 --- a/pkg/api/handlers/compat/images.go +++ b/pkg/api/handlers/compat/images.go @@ -365,7 +365,6 @@ func LoadImages(w http.ResponseWriter, r *http.Request) { return } id, err := runtime.LoadImage(r.Context(), "", f.Name(), writer, "") - //id, err := runtime.Import(r.Context()) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to load image")) return diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index 80b7505df..87b947549 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -5,6 +5,7 @@ import ( "net" "net/http" "os" + "strings" "syscall" "time" @@ -177,9 +178,11 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } + + filterNames, nameFilterExists := query.Filters["name"] // TODO remove when filters are implemented - if len(query.Filters) > 0 { - utils.InternalServerError(w, errors.New("filters for listing networks is not implemented")) + if (!nameFilterExists && len(query.Filters) > 0) || len(query.Filters) > 1 { + utils.InternalServerError(w, errors.New("only the name filter for listing networks is implemented")) return } netNames, err := network.GetNetworkNamesFromFileSystem(config) @@ -187,6 +190,21 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } + + // filter by name + if nameFilterExists { + names := []string{} + for _, name := range netNames { + for _, filter := range filterNames { + if strings.Contains(name, filter) { + names = append(names, name) + break + } + } + } + netNames = names + } + reports := make([]*types.NetworkResource, 0, len(netNames)) for _, name := range netNames { report, err := getNetworkResourceByName(name, runtime) diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index 966874a2b..33bb75391 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -7,10 +7,55 @@ import ( "github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/infra/abi" + "github.com/containers/podman/v2/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" ) +func GenerateSystemd(w http.ResponseWriter, r *http.Request) { + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Name bool `schema:"useName"` + New bool `schema:"new"` + RestartPolicy string `schema:"restartPolicy"` + StopTimeout uint `schema:"stopTimeout"` + ContainerPrefix string `schema:"containerPrefix"` + PodPrefix string `schema:"podPrefix"` + Separator string `schema:"separator"` + }{ + RestartPolicy: "on-failure", + StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout, + ContainerPrefix: "container", + PodPrefix: "pod", + Separator: "-", + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) + return + } + + containerEngine := abi.ContainerEngine{Libpod: runtime} + options := entities.GenerateSystemdOptions{ + Name: query.Name, + New: query.New, + RestartPolicy: query.RestartPolicy, + StopTimeout: &query.StopTimeout, + ContainerPrefix: query.ContainerPrefix, + PodPrefix: query.PodPrefix, + Separator: query.Separator, + } + report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating systemd units")) + return + } + + utils.WriteResponse(w, http.StatusOK, report.Units) +} + func GenerateKube(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) diff --git a/pkg/api/handlers/libpod/images.go b/pkg/api/handlers/libpod/images.go index 8d3fc4e00..85f7903dc 100644 --- a/pkg/api/handlers/libpod/images.go +++ b/pkg/api/handlers/libpod/images.go @@ -234,6 +234,76 @@ func ExportImage(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, rdr) } +func ExportImages(w http.ResponseWriter, r *http.Request) { + var ( + output string + ) + runtime := r.Context().Value("runtime").(*libpod.Runtime) + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Compress bool `schema:"compress"` + Format string `schema:"format"` + References []string `schema:"references"` + }{ + Format: define.OCIArchive, + } + + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + // References are mandatory! + if len(query.References) == 0 { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.New("No references")) + return + } + + // Format is mandatory! Currently, we only support multi-image docker + // archives. + switch query.Format { + case define.V2s2Archive: + tmpfile, err := ioutil.TempFile("", "api.tar") + if err != nil { + utils.Error(w, "unable to create tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to create tempfile")) + return + } + output = tmpfile.Name() + if err := tmpfile.Close(); err != nil { + utils.Error(w, "unable to close tmpfile", http.StatusInternalServerError, errors.Wrap(err, "unable to close tempfile")) + return + } + default: + utils.Error(w, "unsupported format", http.StatusInternalServerError, errors.Errorf("unsupported format %q", query.Format)) + return + } + defer os.RemoveAll(output) + + // Use the ABI image engine to share as much code as possible. + opts := entities.ImageSaveOptions{ + Compress: query.Compress, + Format: query.Format, + MultiImageArchive: true, + Output: output, + } + + imageEngine := abi.ImageEngine{Libpod: runtime} + if err := imageEngine.Save(r.Context(), query.References[0], query.References[1:], opts); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, err) + return + } + + rdr, err := os.Open(output) + if err != nil { + utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "failed to read the exported tarfile")) + return + } + defer rdr.Close() + utils.WriteResponse(w, http.StatusOK, rdr) +} + func ImagesLoad(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) decoder := r.Context().Value("decoder").(*schema.Decoder) diff --git a/pkg/api/handlers/libpod/networks.go b/pkg/api/handlers/libpod/networks.go index 475522664..dfece2a4e 100644 --- a/pkg/api/handlers/libpod/networks.go +++ b/pkg/api/handlers/libpod/networks.go @@ -42,7 +42,21 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) { } func ListNetworks(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) - options := entities.NetworkListOptions{} + decoder := r.Context().Value("decoder").(*schema.Decoder) + query := struct { + Filter string `schema:"filter"` + }{ + // override any golang type defaults + } + if err := decoder.Decode(&query, r.URL.Query()); err != nil { + utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest, + errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String())) + return + } + + options := entities.NetworkListOptions{ + Filter: query.Filter, + } ic := abi.ContainerEngine{Libpod: runtime} reports, err := ic.NetworkList(r.Context(), options) if err != nil { diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 8f8292567..82a7299b2 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -327,7 +327,7 @@ func PodTop(w http.ResponseWriter, r *http.Request) { name := utils.GetName(r) pod, err := runtime.LookupPod(name) if err != nil { - utils.ContainerNotFound(w, name, err) + utils.PodNotFound(w, name, err) return } diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 7db8ee387..60e5b03f7 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -8,6 +8,68 @@ import ( ) func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { + // swagger:operation GET /libpod/generate/{name:.*}/systemd libpod libpodGenerateSystemd + // --- + // tags: + // - containers + // - pods + // summary: Generate Systemd Units + // description: Generate Systemd Units based on a pod or container. + // parameters: + // - in: path + // name: name:.* + // type: string + // required: true + // description: Name or ID of the container or pod. + // - in: query + // name: useName + // type: boolean + // default: false + // description: Use container/pod names instead of IDs. + // - in: query + // name: new + // type: boolean + // default: false + // description: Create a new container instead of starting an existing one. + // - in: query + // name: time + // type: integer + // default: 10 + // description: Stop timeout override. + // - in: query + // name: restartPolicy + // default: on-failure + // type: string + // enum: ["no", on-success, on-failure, on-abnormal, on-watchdog, on-abort, always] + // description: Systemd restart-policy. + // - in: query + // name: containerPrefix + // type: string + // default: container + // description: Systemd unit name prefix for containers. + // - in: query + // name: podPrefix + // type: string + // default: pod + // description: Systemd unit name prefix for pods. + // - in: query + // name: separator + // type: string + // default: "-" + // description: Systemd unit name separator between name/id and prefix. + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: object + // additionalProperties: + // type: string + // 500: + // $ref: "#/responses/InternalError" + r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/systemd"), s.APIHandler(libpod.GenerateSystemd)).Methods(http.MethodGet) + // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube // --- // tags: diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go index 64258a073..b1007fe09 100644 --- a/pkg/api/server/register_images.go +++ b/pkg/api/server/register_images.go @@ -1028,6 +1028,40 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error { // 500: // $ref: '#/responses/InternalError' r.Handle(VersionedPath("/libpod/images/{name:.*}/get"), s.APIHandler(libpod.ExportImage)).Methods(http.MethodGet) + // swagger:operation GET /libpod/images/export libpod libpodExportImages + // --- + // tags: + // - images + // summary: Export multiple images + // description: Export multiple images into a single object. Only `docker-archive` is currently supported. + // parameters: + // - in: query + // name: format + // type: string + // description: format for exported image (only docker-archive is supported) + // - in: query + // name: references + // description: references to images to export + // type: array + // items: + // type: string + // - in: query + // name: compress + // type: boolean + // description: use compression on image + // produces: + // - application/json + // responses: + // 200: + // description: no error + // schema: + // type: string + // format: binary + // 404: + // $ref: '#/responses/NoSuchImage' + // 500: + // $ref: '#/responses/InternalError' + r.Handle(VersionedPath("/libpod/images/export"), s.APIHandler(libpod.ExportImages)).Methods(http.MethodGet) // swagger:operation GET /libpod/images/{name:.*}/json libpod libpodInspectImage // --- // tags: diff --git a/pkg/api/server/register_networks.go b/pkg/api/server/register_networks.go index 7918ad4a2..61916eedf 100644 --- a/pkg/api/server/register_networks.go +++ b/pkg/api/server/register_networks.go @@ -61,6 +61,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // - networks (compat) // summary: List networks // description: Display summary of network configurations + // parameters: + // - in: query + // name: filters + // type: string + // description: JSON encoded value of the filters (a map[string][]string) to process on the networks list. Only the name filter is supported. // produces: // - application/json // responses: @@ -106,7 +111,7 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // required: true // description: the name of the network // - in: query - // name: Force + // name: force // type: boolean // description: remove containers associated with network // produces: @@ -152,6 +157,11 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error { // - networks // summary: List networks // description: Display summary of network configurations + // parameters: + // - in: query + // name: filter + // type: string + // description: Provide filter values (e.g. 'name=podman') // produces: // - application/json // responses: diff --git a/pkg/bindings/generate/generate.go b/pkg/bindings/generate/generate.go index b02221765..dde1cc29c 100644 --- a/pkg/bindings/generate/generate.go +++ b/pkg/bindings/generate/generate.go @@ -10,6 +10,33 @@ import ( "github.com/containers/podman/v2/pkg/domain/entities" ) +func Systemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + params := url.Values{} + + params.Set("useName", strconv.FormatBool(options.Name)) + params.Set("new", strconv.FormatBool(options.New)) + if options.RestartPolicy != "" { + params.Set("restartPolicy", options.RestartPolicy) + } + if options.StopTimeout != nil { + params.Set("stopTimeout", strconv.FormatUint(uint64(*options.StopTimeout), 10)) + } + params.Set("containerPrefix", options.ContainerPrefix) + params.Set("podPrefix", options.PodPrefix) + params.Set("separator", options.Separator) + + response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID) + if err != nil { + return nil, err + } + report := &entities.GenerateSystemdReport{} + return report, response.Process(&report.Units) +} + func Kube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { conn, err := bindings.GetClient(ctx) if err != nil { diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 9f6e78b79..a80c94025 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -128,6 +128,34 @@ func Load(ctx context.Context, r io.Reader, name *string) (*entities.ImageLoadRe return &report, response.Process(&report) } +func MultiExport(ctx context.Context, namesOrIds []string, w io.Writer, format *string, compress *bool) error { + conn, err := bindings.GetClient(ctx) + if err != nil { + return err + } + params := url.Values{} + if format != nil { + params.Set("format", *format) + } + if compress != nil { + params.Set("compress", strconv.FormatBool(*compress)) + } + for _, ref := range namesOrIds { + params.Add("references", ref) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/images/export", params, nil) + if err != nil { + return err + } + + if response.StatusCode/100 == 2 || response.StatusCode/100 == 3 { + _, err = io.Copy(w, response.Body) + return err + } + return response.Process(nil) + +} + // Export saves an image from local storage as a tarball or image archive. The optional format // parameter is used to change the format of the output. func Export(ctx context.Context, nameOrID string, w io.Writer, format *string, compress *bool) error { diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index fd1111282..d8dc7e352 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -70,7 +70,7 @@ func Remove(ctx context.Context, nameOrID string, force *bool) ([]*entities.Netw } // List returns a summary of all CNI network configurations -func List(ctx context.Context) ([]*entities.NetworkListReport, error) { +func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { var ( netList []*entities.NetworkListReport ) @@ -78,7 +78,11 @@ func List(ctx context.Context) ([]*entities.NetworkListReport, error) { if err != nil { return nil, err } - response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", nil, nil) + params := url.Values{} + if options.Filter != "" { + params.Set("filter", options.Filter) + } + response, err := conn.DoRequest(nil, http.MethodGet, "/networks/json", params, nil) if err != nil { return netList, err } diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index c8894300b..16997cdd1 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -282,6 +282,7 @@ type ContainerListOptions struct { Quiet bool Size bool Sort string + Storage bool Sync bool Watch uint } diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index a8ad13705..4a0d7537e 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -4,8 +4,6 @@ import "io" // GenerateSystemdOptions control the generation of systemd unit files. type GenerateSystemdOptions struct { - // Files - generate files instead of printing to stdout. - Files bool // Name - use container/pod name instead of its ID. Name bool // New - create a new container instead of starting a new one. @@ -24,9 +22,8 @@ type GenerateSystemdOptions struct { // GenerateSystemdReport type GenerateSystemdReport struct { - // Output of the generate process. Either the generated files or their - // entire content. - Output string + // Units of the generate process. key = unit name -> value = unit content + Units map[string]string } // GenerateKubeOptions control the generation of Kubernetes YAML files. diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 3a12a4e22..2a8133680 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -271,11 +271,22 @@ type ImageImportReport struct { Id string //nolint } +// ImageSaveOptions provide options for saving images. type ImageSaveOptions struct { + // Compress layers when saving to a directory. Compress bool - Format string - Output string - Quiet bool + // Format of saving the image: oci-archive, oci-dir (directory with oci + // manifest type), docker-archive, docker-dir (directory with v2s2 + // manifest type). + Format string + // MultiImageArchive denotes if the created archive shall include more + // than one image. Additional tags will be interpreted as references + // to images which are added to the archive. + MultiImageArchive bool + // Output - write image to the specified path. + Output string + // Quiet - suppress output when copying images + Quiet bool } // ImageTreeOptions provides options for ImageEngine.Tree() diff --git a/pkg/domain/entities/manifest.go b/pkg/domain/entities/manifest.go index 853619b19..01180951a 100644 --- a/pkg/domain/entities/manifest.go +++ b/pkg/domain/entities/manifest.go @@ -9,14 +9,19 @@ type ManifestCreateOptions struct { } type ManifestAddOptions struct { - All bool `json:"all" schema:"all"` - Annotation []string `json:"annotation" schema:"annotation"` - Arch string `json:"arch" schema:"arch"` - Features []string `json:"features" schema:"features"` - Images []string `json:"images" schema:"images"` - OS string `json:"os" schema:"os"` - OSVersion string `json:"os_version" schema:"os_version"` - Variant string `json:"variant" schema:"variant"` + All bool `json:"all" schema:"all"` + Annotation []string `json:"annotation" schema:"annotation"` + Arch string `json:"arch" schema:"arch"` + Authfile string `json:"-" schema:"-"` + CertDir string `json:"-" schema:"-"` + Features []string `json:"features" schema:"features"` + Images []string `json:"images" schema:"images"` + OS string `json:"os" schema:"os"` + OSVersion string `json:"os_version" schema:"os_version"` + Password string `json:"-" schema:"-"` + SkipTLSVerify types.OptionalBool `json:"-" schema:"-"` + Username string `json:"-" schema:"-"` + Variant string `json:"variant" schema:"variant"` } type ManifestAnnotateOptions struct { diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index af355b0af..bde2b6ef2 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -75,9 +75,10 @@ type SystemDfContainerReport struct { // SystemDfVolumeReport describes a volume and its size type SystemDfVolumeReport struct { - VolumeName string - Links int - Size int64 + VolumeName string + Links int + Size int64 + ReclaimableSize int64 } // SystemResetOptions describes the options for resetting your diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 3fee5d394..21618f555 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -174,6 +174,12 @@ func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []strin return err } } + if c.AutoRemove() { + // Issue #7384: if the container is configured for + // auto-removal, it might already have been removed at + // this point. + return nil + } return c.Cleanup(ctx) }) if err != nil { @@ -792,6 +798,9 @@ func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []stri } func (ic *ContainerEngine) ContainerList(ctx context.Context, options entities.ContainerListOptions) ([]entities.ListContainer, error) { + if options.Latest { + options.Last = 1 + } return ps.GetContainerLists(ic.Libpod, options) } diff --git a/pkg/domain/infra/abi/containers_runlabel.go b/pkg/domain/infra/abi/containers_runlabel.go index ab2316d47..30a5a55b8 100644 --- a/pkg/domain/infra/abi/containers_runlabel.go +++ b/pkg/domain/infra/abi/containers_runlabel.go @@ -7,12 +7,10 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v5/types" "github.com/containers/podman/v2/libpod/define" "github.com/containers/podman/v2/libpod/image" "github.com/containers/podman/v2/pkg/domain/entities" envLib "github.com/containers/podman/v2/pkg/env" - "github.com/containers/podman/v2/pkg/util" "github.com/containers/podman/v2/utils" "github.com/google/shlex" "github.com/pkg/errors" @@ -36,6 +34,11 @@ func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, return err } + if options.Display { + fmt.Printf("command: %s\n", strings.Join(append([]string{os.Args[0]}, cmd[1:]...), " ")) + return nil + } + stdErr := os.Stderr stdOut := os.Stdout stdIn := os.Stdin @@ -84,29 +87,17 @@ func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imag // Fallthrough and pull! } - // Parse credentials if specified. - var credentials *types.DockerAuthConfig - if options.Credentials != "" { - credentials, err = util.ParseRegistryCreds(options.Credentials) - if err != nil { - return nil, err - } - } - - // Suppress pull progress bars if requested. - pullOutput := os.Stdout - if options.Quiet { - pullOutput = nil // c/image/copy takes care of the rest + pullOptions := entities.ImagePullOptions{ + Quiet: options.Quiet, + CertDir: options.CertDir, + SkipTLSVerify: options.SkipTLSVerify, + SignaturePolicy: options.SignaturePolicy, + Authfile: options.Authfile, } - - // Pull the image. - dockerRegistryOptions := image.DockerRegistryOptions{ - DockerCertPath: options.CertDir, - DockerInsecureSkipTLSVerify: options.SkipTLSVerify, - DockerRegistryCreds: credentials, + if _, err := pull(ctx, ic.Libpod.ImageRuntime(), imageRef, pullOptions, &label); err != nil { + return nil, err } - - return ic.Libpod.ImageRuntime().New(ctx, imageRef, options.SignaturePolicy, options.Authfile, pullOutput, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing) + return ic.Libpod.ImageRuntime().NewFromLocal(imageRef) } // generateRunlabelCommand generates the to-be-executed command as a string diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go index 0b73ddd7e..79bf2291e 100644 --- a/pkg/domain/infra/abi/generate.go +++ b/pkg/domain/infra/abi/generate.go @@ -19,11 +19,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, ctr, ctrErr := ic.Libpod.LookupContainer(nameOrID) if ctrErr == nil { // Generate the unit for the container. - s, err := generate.ContainerUnit(ctr, options) + name, content, err := generate.ContainerUnit(ctr, options) if err != nil { return nil, err } - return &entities.GenerateSystemdReport{Output: s}, nil + return &entities.GenerateSystemdReport{Units: map[string]string{name: content}}, nil } // If it's not a container, we either have a pod or garbage. @@ -34,11 +34,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, } // Generate the units for the pod and all its containers. - s, err := generate.PodUnits(pod, options) + units, err := generate.PodUnits(pod, options) if err != nil { return nil, err } - return &entities.GenerateSystemdReport{Output: s}, nil + return &entities.GenerateSystemdReport{Units: units}, nil } func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 6b94ca9c0..23aef9573 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -14,7 +14,6 @@ import ( "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker" - dockerarchive "github.com/containers/image/v5/docker/archive" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/signature" @@ -215,7 +214,7 @@ func ToDomainHistoryLayer(layer *libpodImage.History) entities.ImageHistoryLayer return l } -func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { +func pull(ctx context.Context, runtime *image.Runtime, rawImage string, options entities.ImagePullOptions, label *string) (*entities.ImagePullReport, error) { var writer io.Writer if !options.Quiet { writer = os.Stderr @@ -230,15 +229,6 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } } - // Special-case for docker-archive which allows multiple tags. - if imageRef.Transport().Name() == dockerarchive.Transport.Name() { - newImage, err := ir.Libpod.ImageRuntime().LoadFromArchiveReference(ctx, imageRef, options.SignaturePolicy, writer) - if err != nil { - return nil, err - } - return &entities.ImagePullReport{Images: []string{newImage[0].ID()}}, nil - } - var registryCreds *types.DockerAuthConfig if len(options.Username) > 0 && len(options.Password) > 0 { registryCreds = &types.DockerAuthConfig{ @@ -256,7 +246,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti } if !options.AllTags { - newImage, err := ir.Libpod.ImageRuntime().New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) + newImage, err := runtime.New(ctx, rawImage, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, label, util.PullImageAlways) if err != nil { return nil, err } @@ -290,7 +280,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti foundIDs := []string{} for _, tag := range tags { name := rawImage + ":" + tag - newImage, err := ir.Libpod.ImageRuntime().New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) + newImage, err := runtime.New(ctx, name, options.SignaturePolicy, options.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageAlways) if err != nil { logrus.Errorf("error pulling image %q", name) continue @@ -304,6 +294,10 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti return &entities.ImagePullReport{Images: foundIDs}, nil } +func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entities.ImagePullOptions) (*entities.ImagePullReport, error) { + return pull(ctx, ir.Libpod.ImageRuntime(), rawImage, options, nil) +} + func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, []error, error) { reports := []*entities.ImageInspectReport{} errs := []error{} @@ -481,6 +475,10 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti } func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, options entities.ImageSaveOptions) error { + if options.MultiImageArchive { + nameOrIDs := append([]string{nameOrID}, tags...) + return ir.Libpod.ImageRuntime().SaveImages(ctx, nameOrIDs, options.Format, options.Output, options.Quiet) + } newImage, err := ir.Libpod.ImageRuntime().NewFromLocal(nameOrID) if err != nil { return err diff --git a/pkg/domain/infra/abi/manifest.go b/pkg/domain/infra/abi/manifest.go index 6f3c6b902..55f73bf65 100644 --- a/pkg/domain/infra/abi/manifest.go +++ b/pkg/domain/infra/abi/manifest.go @@ -102,7 +102,24 @@ func (ir *ImageEngine) ManifestAdd(ctx context.Context, opts entities.ManifestAd } manifestAddOpts.Annotation = annotations } - listID, err := listImage.AddManifest(*ir.Libpod.SystemContext(), manifestAddOpts) + + // Set the system context. + sys := ir.Libpod.SystemContext() + if sys != nil { + sys = &types.SystemContext{} + } + sys.AuthFilePath = opts.Authfile + sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify + sys.DockerCertPath = opts.CertDir + + if opts.Username != "" && opts.Password != "" { + sys.DockerAuthConfig = &types.DockerAuthConfig{ + Username: opts.Username, + Password: opts.Password, + } + } + + listID, err := listImage.AddManifest(*sys, manifestAddOpts) if err != nil { return listID, err } @@ -191,6 +208,7 @@ func (ir *ImageEngine) ManifestPush(ctx context.Context, names []string, opts en } sys.AuthFilePath = opts.Authfile sys.DockerInsecureSkipTLSVerify = opts.SkipTLSVerify + sys.DockerCertPath = opts.CertDir if opts.Username != "" && opts.Password != "" { sys.DockerAuthConfig = &types.DockerAuthConfig{ diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index c06714cbb..807e4b272 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "os" "path/filepath" "strings" @@ -216,6 +217,9 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate if err != nil { return "", err } + if err := os.MkdirAll(network.GetCNIConfDir(runtimeConfig), 0755); err != nil { + return "", err + } cniPathName := filepath.Join(network.GetCNIConfDir(runtimeConfig), fmt.Sprintf("%s.conflist", name)) err = ioutil.WriteFile(cniPathName, b, 0644) return cniPathName, err diff --git a/pkg/domain/infra/abi/play.go b/pkg/domain/infra/abi/play.go index 31ad51672..6dfb52c63 100644 --- a/pkg/domain/infra/abi/play.go +++ b/pkg/domain/infra/abi/play.go @@ -132,7 +132,11 @@ func (ic *ContainerEngine) playKubePod(ctx context.Context, podName string, podY libpod.WithInfraContainer(), libpod.WithPodName(podName), } - // TODO for now we just used the default kernel namespaces; we need to add/subtract this from yaml + // TODO we only configure Process namespace. We also need to account for Host{IPC,Network,PID} + // which is not currently possible with pod create + if podYAML.Spec.ShareProcessNamespace != nil && *podYAML.Spec.ShareProcessNamespace { + podOptions = append(podOptions, libpod.WithPodPID()) + } hostname := podYAML.Spec.Hostname if hostname == "" { @@ -556,6 +560,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container containerConfig.Env = envs for _, volume := range containerYAML.VolumeMounts { + var readonly string hostPath, exists := volumes[volume.Name] if !exists { return nil, errors.Errorf("Volume mount %s specified for container but not configured in volumes", volume.Name) @@ -563,7 +568,10 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container if err := parse.ValidateVolumeCtrDir(volume.MountPath); err != nil { return nil, errors.Wrapf(err, "error in parsing MountPath") } - containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s", hostPath, volume.MountPath)) + if volume.ReadOnly { + readonly = ":ro" + } + containerConfig.Volumes = append(containerConfig.Volumes, fmt.Sprintf("%s:%s%s", hostPath, volume.MountPath, readonly)) } return &containerConfig, nil } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index ff1052d86..914a7681d 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -313,6 +313,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System } dfVolumes := make([]*entities.SystemDfVolumeReport, 0, len(vols)) + var reclaimableSize int64 for _, v := range vols { var consInUse int volSize, err := sizeOfPath(v.MountPoint()) @@ -323,15 +324,19 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System if err != nil { return nil, err } + if len(inUse) == 0 { + reclaimableSize += volSize + } for _, viu := range inUse { if util.StringInSlice(viu, runningContainers) { consInUse++ } } report := entities.SystemDfVolumeReport{ - VolumeName: v.Name(), - Links: consInUse, - Size: volSize, + VolumeName: v.Name(), + Links: consInUse, + Size: volSize, + ReclaimableSize: reclaimableSize, } dfVolumes = append(dfVolumes, &report) } diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index cc919561f..35550b9be 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -8,15 +8,18 @@ import ( "os" "strconv" "strings" + "sync" "time" "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/podman/v2/libpod/define" + "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/api/handlers" "github.com/containers/podman/v2/pkg/bindings" "github.com/containers/podman/v2/pkg/bindings/containers" "github.com/containers/podman/v2/pkg/domain/entities" + "github.com/containers/podman/v2/pkg/errorhandling" "github.com/containers/podman/v2/pkg/specgen" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -507,33 +510,90 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta for _, w := range con.Warnings { fmt.Fprintf(os.Stderr, "%s\n", w) } + report := entities.ContainerRunReport{Id: con.ID} - // Attach - if !opts.Detach { - err = startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream) - if err == nil { - exitCode, err := containers.Wait(ic.ClientCxt, con.ID, nil) - if err == nil { - report.ExitCode = int(exitCode) - } + + if opts.Detach { + // Detach and return early + err := containers.Start(ic.ClientCxt, con.ID, nil) + if err != nil { + report.ExitCode = define.ExitCode(err) } - } else { - err = containers.Start(ic.ClientCxt, con.ID, nil) + return &report, err } - if err != nil { + + // Attach + if err := startAndAttach(ic, con.ID, &opts.DetachKeys, opts.InputStream, opts.OutputStream, opts.ErrorStream); err != nil { report.ExitCode = define.ExitCode(err) + if opts.Rm { + if rmErr := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); rmErr != nil { + logrus.Debugf("unable to remove container %s after failing to start and attach to it", con.ID) + } + } + return &report, err } + if opts.Rm { - if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr || - errors.Cause(err) == define.ErrCtrRemoved { - logrus.Warnf("Container %s does not exist: %v", con.ID, err) - } else { - logrus.Errorf("Error removing container %s: %v", con.ID, err) + // Defer the removal, so we can return early if needed and + // de-spaghetti the code. + defer func() { + if err := containers.Remove(ic.ClientCxt, con.ID, bindings.PFalse, bindings.PTrue); err != nil { + if errorhandling.Contains(err, define.ErrNoSuchCtr) || + errorhandling.Contains(err, define.ErrCtrRemoved) { + logrus.Warnf("Container %s does not exist: %v", con.ID, err) + } else { + logrus.Errorf("Error removing container %s: %v", con.ID, err) + } } + }() + } + + // Wait + exitCode, waitErr := containers.Wait(ic.ClientCxt, con.ID, nil) + if waitErr == nil { + report.ExitCode = int(exitCode) + return &report, nil + } + + // Determine why the wait failed. If the container doesn't exist, + // consult the events. + if !errorhandling.Contains(waitErr, define.ErrNoSuchCtr) { + return &report, waitErr + } + + // Events + eventsChannel := make(chan *events.Event) + eventOptions := entities.EventsOptions{ + EventChan: eventsChannel, + Filter: []string{ + "type=container", + fmt.Sprintf("container=%s", con.ID), + fmt.Sprintf("event=%s", events.Exited), + }, + } + + var lastEvent *events.Event + var mutex sync.Mutex + mutex.Lock() + // Read the events. + go func() { + for e := range eventsChannel { + lastEvent = e } + mutex.Unlock() + }() + + eventsErr := ic.Events(ctx, eventOptions) + + // Wait for all events to be read + mutex.Lock() + if eventsErr != nil || lastEvent == nil { + logrus.Errorf("Cannot get exit code: %v", err) + report.ExitCode = define.ExecErrorCodeNotFound + return &report, nil // compat with local client } + report.ExitCode = lastEvent.ContainerExitCode return &report, err } diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index c7d5cd9e2..966f707b1 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -5,11 +5,10 @@ import ( "github.com/containers/podman/v2/pkg/bindings/generate" "github.com/containers/podman/v2/pkg/domain/entities" - "github.com/pkg/errors" ) func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { - return nil, errors.New("not implemented for tunnel") + return generate.Systemd(ic.ClientCxt, nameOrID, options) } func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index b255c5da4..185cc2f9a 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -251,12 +251,23 @@ func (ir *ImageEngine) Save(ctx context.Context, nameOrID string, tags []string, return err } - exErr := images.Export(ir.ClientCxt, nameOrID, f, &options.Format, &options.Compress) - if err := f.Close(); err != nil { - return err - } - if exErr != nil { - return exErr + if options.MultiImageArchive { + exErr := images.MultiExport(ir.ClientCxt, append([]string{nameOrID}, tags...), f, &options.Format, &options.Compress) + if err := f.Close(); err != nil { + return err + } + if exErr != nil { + return exErr + } + } else { + // FIXME: tags are entirely ignored here but shouldn't. + exErr := images.Export(ir.ClientCxt, nameOrID, f, &options.Format, &options.Compress) + if err := f.Close(); err != nil { + return err + } + if exErr != nil { + return exErr + } } if options.Format != "oci-dir" && options.Format != "docker-dir" { diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index 2b197cac0..074425087 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -8,7 +8,7 @@ import ( ) func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.NetworkListOptions) ([]*entities.NetworkListReport, error) { - return network.List(ic.ClientCxt) + return network.List(ic.ClientCxt, options) } func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.NetworkInspectOptions) ([]entities.NetworkInspectReport, error) { diff --git a/pkg/errorhandling/errorhandling.go b/pkg/errorhandling/errorhandling.go index 3117b0ca4..ca6b60bc5 100644 --- a/pkg/errorhandling/errorhandling.go +++ b/pkg/errorhandling/errorhandling.go @@ -57,3 +57,11 @@ func CloseQuiet(f *os.File) { logrus.Errorf("unable to close file %s: %q", f.Name(), err) } } + +// Contains checks if err's message contains sub's message. Contains should be +// used iff either err or sub has lost type information (e.g., due to +// marshaling). For typed errors, please use `errors.Contains(...)` or `Is()` +// in recent version of Go. +func Contains(err error, sub error) bool { + return strings.Contains(err.Error(), sub.Error()) +} diff --git a/pkg/network/files.go b/pkg/network/files.go index 38ce38b97..a2090491f 100644 --- a/pkg/network/files.go +++ b/pkg/network/files.go @@ -14,11 +14,16 @@ import ( "github.com/pkg/errors" ) -func GetCNIConfDir(config *config.Config) string { - if len(config.Network.NetworkConfigDir) < 1 { - return CNIConfigDir +func GetCNIConfDir(configArg *config.Config) string { + if len(configArg.Network.NetworkConfigDir) < 1 { + dc, err := config.DefaultConfig() + if err != nil { + // Fallback to hard-coded dir + return CNIConfigDir + } + return dc.Network.NetworkConfigDir } - return config.Network.NetworkConfigDir + return configArg.Network.NetworkConfigDir } // LoadCNIConfsFromDir loads all the CNI configurations from a dir diff --git a/pkg/ps/ps.go b/pkg/ps/ps.go index 4c5f60844..8087507e2 100644 --- a/pkg/ps/ps.go +++ b/pkg/ps/ps.go @@ -14,6 +14,7 @@ import ( lpfilters "github.com/containers/podman/v2/libpod/filters" "github.com/containers/podman/v2/pkg/domain/entities" psdefine "github.com/containers/podman/v2/pkg/ps/define" + "github.com/containers/storage" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -54,12 +55,12 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp return nil, err } if options.Last > 0 { - // Sort the containers we got + // Sort the libpod containers sort.Sort(SortCreateTime{SortContainers: cons}) // we should perform the lopping before we start getting // the expensive information on containers if options.Last < len(cons) { - cons = cons[len(cons)-options.Last:] + cons = cons[:options.Last] } } for _, con := range cons { @@ -68,7 +69,31 @@ func GetContainerLists(runtime *libpod.Runtime, options entities.ContainerListOp return nil, err } pss = append(pss, listCon) + } + + if options.All && options.Storage { + externCons, err := runtime.StorageContainers() + if err != nil { + return nil, err + } + + for _, con := range externCons { + listCon, err := ListStorageContainer(runtime, con, options) + if err != nil { + return nil, err + } + pss = append(pss, listCon) + } + } + + // Sort the containers we got + sort.Sort(SortPSCreateTime{SortPSContainers: pss}) + if options.Last > 0 { + // only return the "last" containers caller requested + if options.Last < len(pss) { + pss = pss[:options.Last] + } } return pss, nil } @@ -199,6 +224,48 @@ func ListContainerBatch(rt *libpod.Runtime, ctr *libpod.Container, opts entities return ps, nil } +func ListStorageContainer(rt *libpod.Runtime, ctr storage.Container, opts entities.ContainerListOptions) (entities.ListContainer, error) { + name := "unknown" + if len(ctr.Names) > 0 { + name = ctr.Names[0] + } + + ps := entities.ListContainer{ + ID: ctr.ID, + Created: ctr.Created.Unix(), + ImageID: ctr.ImageID, + State: "storage", + Names: []string{name}, + } + + buildahCtr, err := rt.IsBuildahContainer(ctr.ID) + if err != nil { + return ps, errors.Wrapf(err, "error determining buildah container for container %s", ctr.ID) + } + + if buildahCtr { + ps.Command = []string{"buildah"} + } else { + ps.Command = []string{"storage"} + } + + imageName := "" + if ctr.ImageID != "" { + names, err := rt.ImageRuntime().ImageNames(ctr.ImageID) + if err != nil { + return ps, err + } + if len(names) > 0 { + imageName = names[0] + } + } else if buildahCtr { + imageName = "scratch" + } + + ps.Image = imageName + return ps, nil +} + func getNamespaceInfo(path string) (string, error) { val, err := os.Readlink(path) if err != nil { @@ -223,5 +290,17 @@ func (a SortContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type SortCreateTime struct{ SortContainers } func (a SortCreateTime) Less(i, j int) bool { - return a.SortContainers[i].CreatedTime().Before(a.SortContainers[j].CreatedTime()) + return a.SortContainers[i].CreatedTime().After(a.SortContainers[j].CreatedTime()) +} + +// SortPSContainers helps us set-up ability to sort by createTime +type SortPSContainers []entities.ListContainer + +func (a SortPSContainers) Len() int { return len(a) } +func (a SortPSContainers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +type SortPSCreateTime struct{ SortPSContainers } + +func (a SortPSCreateTime) Less(i, j int) bool { + return a.SortPSContainers[i].Created > a.SortPSContainers[j].Created } diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go index fd324c6e1..b57ddf1aa 100644 --- a/pkg/specgen/generate/oci.go +++ b/pkg/specgen/generate/oci.go @@ -353,6 +353,9 @@ func SpecGenToOCI(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runt configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse } + if s.OOMScoreAdj != nil { + g.SetProcessOOMScoreAdj(*s.OOMScoreAdj) + } setProcOpts(s, &g) return configSpec, nil diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index d3e3d9278..87e8029a7 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -60,7 +60,7 @@ func setLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig s func setupApparmor(s *specgen.SpecGenerator, rtc *config.Config, g *generate.Generator) error { hasProfile := len(s.ApparmorProfile) > 0 if !apparmor.IsEnabled() { - if hasProfile { + if hasProfile && s.ApparmorProfile != "unconfined" { return errors.Errorf("Apparmor profile %q specified, but Apparmor is not enabled on this system", s.ApparmorProfile) } return nil diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 5f6376977..a4fdae46e 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -3,9 +3,7 @@ package generate import ( "bytes" "fmt" - "io/ioutil" "os" - "path/filepath" "sort" "strings" "text/template" @@ -87,17 +85,22 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` // ContainerUnit generates a systemd unit for the specified container. Based // on the options, the return value might be the entire unit or a file it has // been written to. -func ContainerUnit(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, error) { +func ContainerUnit(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, string, error) { info, err := generateContainerInfo(ctr, options) if err != nil { - return "", err + return "", "", err + } + content, err := executeContainerTemplate(info, options) + if err != nil { + return "", "", err } - return executeContainerTemplate(info, options) + return info.ServiceName, content, nil } func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSystemdOptions) (*containerInfo, error) { @@ -217,6 +220,9 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst case "--replace": hasReplaceParam = true } + if strings.HasPrefix(p, "--name=") { + hasNameParam = true + } } if !hasDetachParam { @@ -288,18 +294,5 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst return "", err } - if !options.Files { - return buf.String(), nil - } - - buf.WriteByte('\n') - cwd, err := os.Getwd() - if err != nil { - return "", errors.Wrap(err, "error getting current working directory") - } - path := filepath.Join(cwd, fmt.Sprintf("%s.service", info.ServiceName)) - if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil { - return "", errors.Wrap(err, "error generating systemd unit") - } - return path, nil + return buf.String(), nil } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index b5c736c5a..d27062ef3 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -56,7 +56,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodName := `# container-foobar.service # autogenerated by Podman CI @@ -78,7 +79,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodNameBoundTo := `# container-foobar.service # autogenerated by Podman CI @@ -102,7 +104,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodWithNameAndGeneric := `# jadda-jadda.service # autogenerated by Podman CI @@ -125,7 +128,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodWithExplicitShortDetachParam := `# jadda-jadda.service # autogenerated by Podman CI @@ -148,7 +152,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodNameNewWithPodFile := `# jadda-jadda.service # autogenerated by Podman CI @@ -171,7 +176,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodNameNewDetach := `# jadda-jadda.service # autogenerated by Podman CI @@ -194,7 +200,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` goodIDNew := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service # autogenerated by Podman CI @@ -217,7 +224,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` tests := []struct { name string @@ -375,8 +383,7 @@ WantedBy=multi-user.target default.target` test := tt t.Run(tt.name, func(t *testing.T) { opts := entities.GenerateSystemdOptions{ - Files: false, - New: test.new, + New: test.new, } got, err := executeContainerTemplate(&test.info, opts) if (err != nil) != test.wantErr { diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index dec9587d9..c41eedd17 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -3,9 +3,7 @@ package generate import ( "bytes" "fmt" - "io/ioutil" "os" - "path/filepath" "sort" "strings" "text/template" @@ -88,39 +86,40 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` // PodUnits generates systemd units for the specified pod and its containers. // Based on the options, the return value might be the content of all units or // the files they been written to. -func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, error) { +func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) { // Error out if the pod has no infra container, which we require to be the // main service. if !pod.HasInfraContainer() { - return "", errors.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name()) + return nil, errors.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name()) } podInfo, err := generatePodInfo(pod, options) if err != nil { - return "", err + return nil, err } infraID, err := pod.InfraContainerID() if err != nil { - return "", err + return nil, err } // Compute the container-dependency graph for the Pod. containers, err := pod.AllContainers() if err != nil { - return "", err + return nil, err } if len(containers) == 0 { - return "", errors.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name()) + return nil, errors.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name()) } graph, err := libpod.BuildContainerGraph(containers) if err != nil { - return "", err + return nil, err } // Traverse the dependency graph and create systemdgen.containerInfo's for @@ -133,7 +132,7 @@ func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, } ctrInfo, err := generateContainerInfo(ctr, options) if err != nil { - return "", err + return nil, err } // Now add the container's dependencies and at the container as a // required service of the infra container. @@ -149,24 +148,23 @@ func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, containerInfos = append(containerInfos, ctrInfo) } + units := map[string]string{} // Now generate the systemd service for all containers. - builder := strings.Builder{} out, err := executePodTemplate(podInfo, options) if err != nil { - return "", err + return nil, err } - builder.WriteString(out) + units[podInfo.ServiceName] = out for _, info := range containerInfos { info.pod = podInfo - builder.WriteByte('\n') out, err := executeContainerTemplate(info, options) if err != nil { - return "", err + return nil, err } - builder.WriteString(out) + units[info.ServiceName] = out } - return builder.String(), nil + return units, nil } func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) (*podInfo, error) { @@ -339,18 +337,5 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions) return "", err } - if !options.Files { - return buf.String(), nil - } - - buf.WriteByte('\n') - cwd, err := os.Getwd() - if err != nil { - return "", errors.Wrap(err, "error getting current working directory") - } - path := filepath.Join(cwd, fmt.Sprintf("%s.service", info.ServiceName)) - if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil { - return "", errors.Wrap(err, "error generating systemd unit") - } - return path, nil + return buf.String(), nil } diff --git a/pkg/systemd/generate/pods_test.go b/pkg/systemd/generate/pods_test.go index 8bf4705a7..7f1f63b7e 100644 --- a/pkg/systemd/generate/pods_test.go +++ b/pkg/systemd/generate/pods_test.go @@ -58,7 +58,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` podGoodNamedNew := `# pod-123abc.service # autogenerated by Podman CI @@ -84,7 +85,8 @@ KillMode=none Type=forking [Install] -WantedBy=multi-user.target default.target` +WantedBy=multi-user.target default.target +` tests := []struct { name string @@ -130,8 +132,7 @@ WantedBy=multi-user.target default.target` test := tt t.Run(tt.name, func(t *testing.T) { opts := entities.GenerateSystemdOptions{ - Files: false, - New: test.new, + New: test.new, } got, err := executePodTemplate(&test.info, opts) if (err != nil) != test.wantErr { |