diff options
-rw-r--r-- | cmd/podman/common/inspect.go | 18 | ||||
-rw-r--r-- | cmd/podman/containers/inspect.go | 53 | ||||
-rw-r--r-- | cmd/podman/generate/generate.go | 27 | ||||
-rw-r--r-- | cmd/podman/generate/systemd.go | 57 | ||||
-rw-r--r-- | cmd/podman/images/inspect.go | 92 | ||||
-rw-r--r-- | cmd/podman/inspect.go | 42 | ||||
-rw-r--r-- | cmd/podman/inspect/inspect.go | 159 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | pkg/bindings/images/images.go | 4 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 1 | ||||
-rw-r--r-- | pkg/domain/entities/engine_image.go | 2 | ||||
-rw-r--r-- | pkg/domain/entities/generate.go | 22 | ||||
-rw-r--r-- | pkg/domain/entities/images.go | 8 | ||||
-rw-r--r-- | pkg/domain/entities/types.go | 10 | ||||
-rw-r--r-- | pkg/domain/infra/abi/generate.go | 174 | ||||
-rw-r--r-- | pkg/domain/infra/abi/images.go | 31 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/generate.go | 12 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/images.go | 14 | ||||
-rw-r--r-- | pkg/specgen/generate/container_create.go | 1 | ||||
-rw-r--r-- | test/e2e/generate_systemd_test.go | 1 | ||||
-rw-r--r-- | test/e2e/inspect_test.go | 1 | ||||
-rw-r--r-- | test/system/250-generate-systemd.bats | 2 |
22 files changed, 511 insertions, 221 deletions
diff --git a/cmd/podman/common/inspect.go b/cmd/podman/common/inspect.go deleted file mode 100644 index dfc6fe679..000000000 --- a/cmd/podman/common/inspect.go +++ /dev/null @@ -1,18 +0,0 @@ -package common - -import ( - "github.com/containers/libpod/pkg/domain/entities" - "github.com/spf13/cobra" -) - -// AddInspectFlagSet takes a command and adds the inspect flags and returns an InspectOptions object -// Since this cannot live in `package main` it lives here until a better home is found -func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { - opts := entities.InspectOptions{} - - flags := cmd.Flags() - flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") - flags.StringVarP(&opts.Format, "format", "f", "", "Change the output format to a Go template") - - return &opts -} diff --git a/cmd/podman/containers/inspect.go b/cmd/podman/containers/inspect.go index f9ef1ddbd..4549a4ef6 100644 --- a/cmd/podman/containers/inspect.go +++ b/cmd/podman/containers/inspect.go @@ -1,15 +1,8 @@ package containers import ( - "context" - "fmt" - "os" - "strings" - "text/template" - - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" - "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) @@ -20,7 +13,7 @@ var ( Use: "inspect [flags] CONTAINER", Short: "Display the configuration of a container", Long: `Displays the low-level information on a container identified by name or ID.`, - RunE: inspect, + RunE: inspectExec, Example: `podman container inspect myCtr podman container inspect -l --format '{{.Id}} {{.Config.Labels}}'`, } @@ -33,45 +26,9 @@ func init() { Command: inspectCmd, Parent: containerCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of") - } - -} - -func inspect(cmd *cobra.Command, args []string) error { - responses, err := registry.ContainerEngine().ContainerInspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - if inspectOpts.Format == "" { - b, err := json.MarshalIndent(responses, "", " ") - if err != nil { - return err - } - fmt.Println(string(b)) - return nil - } - format := inspectOpts.Format - if !strings.HasSuffix(format, "\n") { - format += "\n" - } - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - for _, i := range responses { - if err := tmpl.Execute(os.Stdout, i); err != nil { - return err - } - } - return nil + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/generate/generate.go b/cmd/podman/generate/generate.go new file mode 100644 index 000000000..f04ef58a5 --- /dev/null +++ b/cmd/podman/generate/generate.go @@ -0,0 +1,27 @@ +package pods + +import ( + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/spf13/cobra" +) + +var ( + // Command: podman _generate_ + generateCmd = &cobra.Command{ + Use: "generate", + Short: "Generate structured data based on containers and pods.", + Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.", + TraverseChildren: true, + RunE: registry.SubCommandExists, + } + containerConfig = util.DefaultContainerConfig() +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: generateCmd, + }) +} diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go new file mode 100644 index 000000000..55d770249 --- /dev/null +++ b/cmd/podman/generate/systemd.go @@ -0,0 +1,57 @@ +package pods + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/cmd/podman/utils" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/spf13/cobra" +) + +var ( + systemdTimeout uint + systemdOptions = entities.GenerateSystemdOptions{} + systemdDescription = `Generate systemd units for a pod or container. + The generated units can later be controlled via systemctl(1).` + + systemdCmd = &cobra.Command{ + Use: "systemd [flags] CTR|POD", + Short: "Generate systemd units.", + Long: systemdDescription, + RunE: systemd, + Args: cobra.MinimumNArgs(1), + Example: `podman generate systemd CTR + podman generate systemd --new --time 10 CTR + podman generate systemd --files --name POD`, + } +) + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, + Command: systemdCmd, + Parent: generateCmd, + }) + flags := systemdCmd.Flags() + flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs") + flags.BoolVarP(&systemdOptions.Files, "files", "f", false, "Generate .service files instead of printing to stdout") + flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override") + flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy") + flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one") + flags.SetNormalizeFunc(utils.AliasFlags) +} + +func systemd(cmd *cobra.Command, args []string) error { + if cmd.Flags().Changed("time") { + systemdOptions.StopTimeout = &systemdTimeout + } + + report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) + if err != nil { + return err + } + + fmt.Println(report.Output) + return nil +} diff --git a/cmd/podman/images/inspect.go b/cmd/podman/images/inspect.go index 91c9445eb..8c727eb07 100644 --- a/cmd/podman/images/inspect.go +++ b/cmd/podman/images/inspect.go @@ -1,18 +1,9 @@ package images import ( - "context" - "fmt" - "os" - "strings" - "text/tabwriter" - "text/template" - - "github.com/containers/buildah/pkg/formats" - "github.com/containers/libpod/cmd/podman/common" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" - "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -21,8 +12,8 @@ var ( inspectCmd = &cobra.Command{ Use: "inspect [flags] IMAGE", Short: "Display the configuration of an image", - Long: `Displays the low-level information on an image identified by name or ID.`, - RunE: inspect, + Long: `Displays the low-level information of an image identified by name or ID.`, + RunE: inspectExec, Example: `podman inspect alpine podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine podman inspect --format "image: {{.ImageName}} driver: {{.Driver}}" myctr`, @@ -36,78 +27,11 @@ func init() { Command: inspectCmd, Parent: imageCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) -} - -func inspect(cmd *cobra.Command, args []string) error { - if inspectOpts.Size { - return fmt.Errorf("--size can only be used for containers") - } - if inspectOpts.Latest { - return fmt.Errorf("--latest can only be used for containers") - } - if len(args) == 0 { - return errors.Errorf("image name must be specified: podman image inspect [options [...]] name") - } - - results, err := registry.ImageEngine().Inspect(context.Background(), args, *inspectOpts) - if err != nil { - return err - } - - if len(results.Images) > 0 { - if inspectOpts.Format == "" { - buf, err := json.MarshalIndent(results.Images, "", " ") - if err != nil { - return err - } - fmt.Println(string(buf)) - - for id, e := range results.Errors { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, e.Error()) - } - return nil - } - row := inspectFormat(inspectOpts.Format) - format := "{{range . }}" + row + "{{end}}" - tmpl, err := template.New("inspect").Parse(format) - if err != nil { - return err - } - - w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0) - defer func() { _ = w.Flush() }() - err = tmpl.Execute(w, results.Images) - if err != nil { - return err - } - } - - var lastErr error - for id, e := range results.Errors { - if lastErr != nil { - fmt.Fprintf(os.Stderr, "%s: %s\n", id, lastErr.Error()) - } - lastErr = e - } - return lastErr -} - -func inspectFormat(row string) string { - r := strings.NewReplacer("{{.Id}}", formats.IDString, - ".Src", ".Source", - ".Dst", ".Destination", - ".ImageID", ".Image", - ) - row = r.Replace(row) - - if !strings.HasSuffix(row, "\n") { - row += "\n" - } - return row + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) + flags := inspectCmd.Flags() + _ = flags.MarkHidden("latest") // Shared with container-inspect but not wanted here. } -func Inspect(cmd *cobra.Command, args []string, options *entities.InspectOptions) error { - inspectOpts = options - return inspect(cmd, args) +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/inspect.go b/cmd/podman/inspect.go index 93bf58bdd..a5fdaedc2 100644 --- a/cmd/podman/inspect.go +++ b/cmd/podman/inspect.go @@ -1,31 +1,26 @@ package main import ( - "fmt" - - "github.com/containers/libpod/cmd/podman/common" - "github.com/containers/libpod/cmd/podman/containers" - "github.com/containers/libpod/cmd/podman/images" + "github.com/containers/libpod/cmd/podman/inspect" "github.com/containers/libpod/cmd/podman/registry" "github.com/containers/libpod/pkg/domain/entities" "github.com/spf13/cobra" ) -// Inspect is one of the outlier commands in that it operates on images/containers/... - var ( - inspectOpts *entities.InspectOptions - // Command: podman _inspect_ Object_ID inspectCmd = &cobra.Command{ Use: "inspect [flags] {CONTAINER_ID | IMAGE_ID}", Short: "Display the configuration of object denoted by ID", Long: "Displays the low-level information on an object identified by name or ID", TraverseChildren: true, - RunE: inspect, - Example: `podman inspect alpine - podman inspect --format "imageId: {{.Id}} size: {{.Size}}" alpine`, + RunE: inspectExec, + Example: `podman inspect fedora + podman inspect --type image fedora + podman inspect CtrID ImgID + podman inspect --format "imageId: {{.Id}} size: {{.Size}}" fedora`, } + inspectOpts *entities.InspectOptions ) func init() { @@ -33,26 +28,9 @@ func init() { Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode}, Command: inspectCmd, }) - inspectOpts = common.AddInspectFlagSet(inspectCmd) - flags := inspectCmd.Flags() - flags.StringVarP(&inspectOpts.Type, "type", "t", "", "Return JSON for specified type, (image or container) (default \"all\")") - if !registry.IsRemote() { - flags.BoolVarP(&inspectOpts.Latest, "latest", "l", false, "Act on the latest container podman is aware of (containers only)") - } + inspectOpts = inspect.AddInspectFlagSet(inspectCmd) } -func inspect(cmd *cobra.Command, args []string) error { - switch inspectOpts.Type { - case "image": - return images.Inspect(cmd, args, inspectOpts) - case "container": - return containers.Inspect(cmd, args, inspectOpts) - case "": - if err := images.Inspect(cmd, args, inspectOpts); err == nil { - return nil - } - return containers.Inspect(cmd, args, inspectOpts) - default: - return fmt.Errorf("invalid type %q is must be 'container' or 'image'", inspectOpts.Type) - } +func inspectExec(cmd *cobra.Command, args []string) error { + return inspect.Inspect(args, *inspectOpts) } diff --git a/cmd/podman/inspect/inspect.go b/cmd/podman/inspect/inspect.go new file mode 100644 index 000000000..223ce00f0 --- /dev/null +++ b/cmd/podman/inspect/inspect.go @@ -0,0 +1,159 @@ +package inspect + +import ( + "context" + "fmt" + "strings" + + "github.com/containers/buildah/pkg/formats" + "github.com/containers/libpod/cmd/podman/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +const ( + // ImageType is the image type. + ImageType = "image" + // ContainerType is the container type. + ContainerType = "container" + // AllType can be of type ImageType or ContainerType. + AllType = "all" +) + +// AddInspectFlagSet takes a command and adds the inspect flags and returns an +// InspectOptions object. +func AddInspectFlagSet(cmd *cobra.Command) *entities.InspectOptions { + opts := entities.InspectOptions{} + + flags := cmd.Flags() + flags.BoolVarP(&opts.Size, "size", "s", false, "Display total file size") + flags.StringVarP(&opts.Format, "format", "f", "json", "Format the output to a Go template or json") + flags.StringVarP(&opts.Type, "type", "t", AllType, fmt.Sprintf("Specify inspect-oject type (%q, %q or %q)", ImageType, ContainerType, AllType)) + flags.BoolVarP(&opts.Latest, "latest", "l", false, "Act on the latest container Podman is aware of") + + return &opts +} + +// Inspect inspects the specified container/image names or IDs. +func Inspect(namesOrIDs []string, options entities.InspectOptions) error { + inspector, err := newInspector(options) + if err != nil { + return err + } + return inspector.inspect(namesOrIDs) +} + +// inspector allows for inspecting images and containers. +type inspector struct { + containerEngine entities.ContainerEngine + imageEngine entities.ImageEngine + options entities.InspectOptions +} + +// newInspector creates a new inspector based on the specified options. +func newInspector(options entities.InspectOptions) (*inspector, error) { + switch options.Type { + case ImageType, ContainerType, AllType: + // Valid types. + default: + return nil, errors.Errorf("invalid type %q: must be %q, %q or %q", options.Type, ImageType, ContainerType, AllType) + } + if options.Type == ImageType { + if options.Latest { + return nil, errors.Errorf("latest is not supported for type %q", ImageType) + } + if options.Size { + return nil, errors.Errorf("size is not supported for type %q", ImageType) + } + } + return &inspector{ + containerEngine: registry.ContainerEngine(), + imageEngine: registry.ImageEngine(), + options: options, + }, nil +} + +// inspect inspects the specified container/image names or IDs. +func (i *inspector) inspect(namesOrIDs []string) error { + // data - dumping place for inspection results. + var data []interface{} + ctx := context.Background() + + if len(namesOrIDs) == 0 { + if !i.options.Latest { + return errors.New("no containers or images specified") + } + } + + tmpType := i.options.Type + if i.options.Latest { + if len(namesOrIDs) > 0 { + return errors.New("latest and containers are not allowed") + } + tmpType = ContainerType // -l works with --type=all + } + + // Inspect - note that AllType requires us to expensively query one-by-one. + switch tmpType { + case AllType: + all, err := i.inspectAll(ctx, namesOrIDs) + if err != nil { + return err + } + data = all + case ImageType: + imgData, err := i.imageEngine.Inspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range imgData { + data = append(data, imgData[i]) + } + case ContainerType: + ctrData, err := i.containerEngine.ContainerInspect(ctx, namesOrIDs, i.options) + if err != nil { + return err + } + for i := range ctrData { + data = append(data, ctrData[i]) + } + default: + return errors.Errorf("invalid type %q: must be %q, %q or %q", i.options.Type, ImageType, ContainerType, AllType) + } + + var out formats.Writer + if i.options.Format == "json" || i.options.Format == "" { // "" for backwards compat + out = formats.JSONStructArray{Output: data} + } else { + out = formats.StdoutTemplateArray{Output: data, Template: inspectFormat(i.options.Format)} + } + return out.Out() +} + +func (i *inspector) inspectAll(ctx context.Context, namesOrIDs []string) ([]interface{}, error) { + var data []interface{} + for _, name := range namesOrIDs { + imgData, err := i.imageEngine.Inspect(ctx, []string{name}, i.options) + if err == nil { + data = append(data, imgData[0]) + continue + } + ctrData, err := i.containerEngine.ContainerInspect(ctx, []string{name}, i.options) + if err != nil { + return nil, err + } + data = append(data, ctrData[0]) + } + return data, nil +} + +func inspectFormat(row string) string { + r := strings.NewReplacer( + "{{.Id}}", formats.IDString, + ".Src", ".Source", + ".Dst", ".Destination", + ".ImageID", ".Image", + ) + return r.Replace(row) +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 4cd0beee8..481214a38 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -4,6 +4,7 @@ import ( "os" _ "github.com/containers/libpod/cmd/podman/containers" + _ "github.com/containers/libpod/cmd/podman/generate" _ "github.com/containers/libpod/cmd/podman/healthcheck" _ "github.com/containers/libpod/cmd/podman/images" _ "github.com/containers/libpod/cmd/podman/manifest" diff --git a/pkg/bindings/images/images.go b/pkg/bindings/images/images.go index 06f01c7a0..2305e0101 100644 --- a/pkg/bindings/images/images.go +++ b/pkg/bindings/images/images.go @@ -57,7 +57,7 @@ func List(ctx context.Context, all *bool, filters map[string][]string) ([]*entit // Get performs an image inspect. To have the on-disk size of the image calculated, you can // use the optional size parameter. -func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageData, error) { +func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.ImageInspectReport, error) { conn, err := bindings.GetClient(ctx) if err != nil { return nil, err @@ -66,7 +66,7 @@ func GetImage(ctx context.Context, nameOrID string, size *bool) (*entities.Image if size != nil { params.Set("size", strconv.FormatBool(*size)) } - inspectedData := entities.ImageData{} + inspectedData := entities.ImageInspectReport{} response, err := conn.DoRequest(nil, http.MethodGet, "/images/%s/json", params, nameOrID) if err != nil { return &inspectedData, err diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 502279bcf..eebf4c033 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -41,6 +41,7 @@ type ContainerEngine interface { ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) Events(ctx context.Context, opts EventsOptions) error + GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error) HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error) Info(ctx context.Context) (*define.Info, error) PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) diff --git a/pkg/domain/entities/engine_image.go b/pkg/domain/entities/engine_image.go index b118a4104..46a96ca20 100644 --- a/pkg/domain/entities/engine_image.go +++ b/pkg/domain/entities/engine_image.go @@ -13,7 +13,7 @@ type ImageEngine interface { Exists(ctx context.Context, nameOrId string) (*BoolReport, error) History(ctx context.Context, nameOrId string, opts ImageHistoryOptions) (*ImageHistoryReport, error) Import(ctx context.Context, opts ImageImportOptions) (*ImageImportReport, error) - Inspect(ctx context.Context, names []string, opts InspectOptions) (*ImageInspectReport, error) + Inspect(ctx context.Context, namesOrIDs []string, opts InspectOptions) ([]*ImageInspectReport, error) List(ctx context.Context, opts ImageListOptions) ([]*ImageSummary, error) Load(ctx context.Context, opts ImageLoadOptions) (*ImageLoadReport, error) Prune(ctx context.Context, opts ImagePruneOptions) (*ImagePruneReport, error) diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go new file mode 100644 index 000000000..6d65b52f8 --- /dev/null +++ b/pkg/domain/entities/generate.go @@ -0,0 +1,22 @@ +package entities + +// 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. + New bool + // RestartPolicy - systemd restart policy. + RestartPolicy string + // StopTimeout - time when stopping the container. + StopTimeout *uint +} + +// GenerateSystemdReport +type GenerateSystemdReport struct { + // Output of the generate process. Either the generated files or their + // entire content. + Output string +} diff --git a/pkg/domain/entities/images.go b/pkg/domain/entities/images.go index 460965b34..707937a44 100644 --- a/pkg/domain/entities/images.go +++ b/pkg/domain/entities/images.go @@ -238,13 +238,9 @@ type ImagePruneReport struct { type ImageTagOptions struct{} type ImageUntagOptions struct{} -type ImageData struct { - *inspect.ImageData -} - +// ImageInspectReport is the data when inspecting an image. type ImageInspectReport struct { - Images []*ImageData - Errors map[string]error + *inspect.ImageData } type ImageLoadOptions struct { diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index d742cc53d..9fbe04c9a 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -47,10 +47,14 @@ type NetOptions struct { // All CLI inspect commands and inspect sub-commands use the same options type InspectOptions struct { + // Format - change the output to JSON or a Go template. Format string `json:",omitempty"` - Latest bool `json:",omitempty"` - Size bool `json:",omitempty"` - Type string `json:",omitempty"` + // Latest - inspect the latest container Podman is aware of. + Latest bool `json:",omitempty"` + // Size (containers only) - display total file size. + Size bool `json:",omitempty"` + // Type -- return JSON for specified type. + Type string `json:",omitempty"` } // All API and CLI diff commands and diff sub-commands use the same options diff --git a/pkg/domain/infra/abi/generate.go b/pkg/domain/infra/abi/generate.go new file mode 100644 index 000000000..f69ba560e --- /dev/null +++ b/pkg/domain/infra/abi/generate.go @@ -0,0 +1,174 @@ +package abi + +import ( + "context" + "fmt" + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/systemd/generate" + "github.com/pkg/errors" +) + +func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { + opts := generate.Options{ + Files: options.Files, + New: options.New, + } + + // First assume it's a container. + if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil { + return nil, err + } else if found && err == nil { + output, err := generate.CreateContainerSystemdUnit(info, opts) + if err != nil { + return nil, err + } + return &entities.GenerateSystemdReport{Output: output}, nil + } + + // --new does not support pods. + if options.New { + return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod") + } + + // We're either having a pod or garbage. + pod, err := ic.Libpod.LookupPod(nameOrID) + if err != nil { + return nil, err + } + + // Error out if the pod has no infra container, which we require to be the + // main service. + if !pod.HasInfraContainer() { + return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name()) + } + + // Generate a systemdgen.ContainerInfo for the infra container. This + // ContainerInfo acts as the main service of the pod. + infraID, err := pod.InfraContainerID() + if err != nil { + return nil, nil + } + podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options) + if err != nil { + return nil, err + } + + // Compute the container-dependency graph for the Pod. + containers, err := pod.AllContainers() + if err != nil { + return nil, err + } + if len(containers) == 0 { + return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name()) + } + graph, err := libpod.BuildContainerGraph(containers) + if err != nil { + return nil, err + } + + // Traverse the dependency graph and create systemdgen.ContainerInfo's for + // each container. + containerInfos := []*generate.ContainerInfo{podInfo} + for ctr, dependencies := range graph.DependencyMap() { + // Skip the infra container as we already generated it. + if ctr.ID() == infraID { + continue + } + ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options) + if err != nil { + return nil, err + } + // Now add the container's dependencies and at the container as a + // required service of the infra container. + for _, dep := range dependencies { + if dep.ID() == infraID { + ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName) + } else { + _, serviceName := generateServiceName(dep, nil, options) + ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName) + } + } + podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName) + containerInfos = append(containerInfos, ctrInfo) + } + + // Now generate the systemd service for all containers. + builder := strings.Builder{} + for i, info := range containerInfos { + if i > 0 { + builder.WriteByte('\n') + } + out, err := generate.CreateContainerSystemdUnit(info, opts) + if err != nil { + return nil, err + } + builder.WriteString(out) + } + + return &entities.GenerateSystemdReport{Output: builder.String()}, nil +} + +// generateSystemdgenContainerInfo is a helper to generate a +// systemdgen.ContainerInfo for `GenerateSystemd`. +func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) { + ctr, err := ic.Libpod.LookupContainer(nameOrID) + if err != nil { + return nil, false, err + } + + timeout := ctr.StopTimeout() + if options.StopTimeout != nil { + timeout = *options.StopTimeout + } + + config := ctr.Config() + conmonPidFile := config.ConmonPidFile + if conmonPidFile == "" { + return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag") + } + + createCommand := []string{} + if config.CreateCommand != nil { + createCommand = config.CreateCommand + } else if options.New { + return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID) + } + + name, serviceName := generateServiceName(ctr, pod, options) + info := &generate.ContainerInfo{ + ServiceName: serviceName, + ContainerName: name, + RestartPolicy: options.RestartPolicy, + PIDFile: conmonPidFile, + StopTimeout: timeout, + GenerateTimestamp: true, + CreateCommand: createCommand, + } + + return info, true, nil +} + +// generateServiceName generates the container name and the service name for systemd service. +func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) { + var kind, name, ctrName string + if pod == nil { + kind = "container" + name = ctr.ID() + if options.Name { + name = ctr.Name() + } + ctrName = name + } else { + kind = "pod" + name = pod.ID() + ctrName = ctr.ID() + if options.Name { + name = pod.Name() + ctrName = ctr.Name() + } + } + return ctrName, fmt.Sprintf("%s-%s", kind, name) +} diff --git a/pkg/domain/infra/abi/images.go b/pkg/domain/infra/abi/images.go index 7ac111745..7c63276e5 100644 --- a/pkg/domain/infra/abi/images.go +++ b/pkg/domain/infra/abi/images.go @@ -170,29 +170,24 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti return &entities.ImagePullReport{Images: foundIDs}, nil } -func (ir *ImageEngine) Inspect(ctx context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { - report := entities.ImageInspectReport{ - Errors: make(map[string]error), - } - - for _, id := range names { - img, err := ir.Libpod.ImageRuntime().NewFromLocal(id) +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { + reports := []*entities.ImageInspectReport{} + for _, i := range namesOrIDs { + img, err := ir.Libpod.ImageRuntime().NewFromLocal(i) if err != nil { - report.Errors[id] = err - continue + return nil, err } - - results, err := img.Inspect(ctx) + result, err := img.Inspect(ctx) if err != nil { - report.Errors[id] = err - continue + return nil, err } - - cookedResults := entities.ImageData{} - _ = domainUtils.DeepCopy(&cookedResults, results) - report.Images = append(report.Images, &cookedResults) + report := entities.ImageInspectReport{} + if err := domainUtils.DeepCopy(&report, result); err != nil { + return nil, err + } + reports = append(reports, &report) } - return &report, nil + return reports, nil } func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, options entities.ImagePushOptions) error { diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go new file mode 100644 index 000000000..3cd483053 --- /dev/null +++ b/pkg/domain/infra/tunnel/generate.go @@ -0,0 +1,12 @@ +package tunnel + +import ( + "context" + + "github.com/containers/libpod/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") +} diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 66e4e6e3f..dcc5fc3e7 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -143,16 +143,16 @@ func (ir *ImageEngine) Untag(ctx context.Context, nameOrId string, tags []string return nil } -func (ir *ImageEngine) Inspect(_ context.Context, names []string, opts entities.InspectOptions) (*entities.ImageInspectReport, error) { - report := entities.ImageInspectReport{} - for _, id := range names { - r, err := images.GetImage(ir.ClientCxt, id, &opts.Size) +func (ir *ImageEngine) Inspect(ctx context.Context, namesOrIDs []string, opts entities.InspectOptions) ([]*entities.ImageInspectReport, error) { + reports := []*entities.ImageInspectReport{} + for _, i := range namesOrIDs { + r, err := images.GetImage(ir.ClientCxt, i, &opts.Size) if err != nil { - report.Errors[id] = err + return nil, err } - report.Images = append(report.Images, r) + reports = append(reports, r) } - return &report, nil + return reports, nil } func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) (*entities.ImageLoadReport, error) { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index bb84f0618..01ddcf9c8 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -76,6 +76,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } options := []libpod.CtrCreateOption{} + options = append(options, libpod.WithCreateCommand()) var newImage *image.Image if s.Rootfs != "" { diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 2901e7ac6..abfca4db9 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -18,7 +18,6 @@ var _ = Describe("Podman generate systemd", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 5ec1b51bb..ebac087ac 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -17,7 +17,6 @@ var _ = Describe("Podman inspect", func() { ) BeforeEach(func() { - Skip(v2fail) tempdir, err = CreateTempDirInTempDir() if err != nil { os.Exit(1) diff --git a/test/system/250-generate-systemd.bats b/test/system/250-generate-systemd.bats index 80199af5f..6155d6ace 100644 --- a/test/system/250-generate-systemd.bats +++ b/test/system/250-generate-systemd.bats @@ -10,6 +10,8 @@ SERVICE_NAME="podman_test_$(random_string)" UNIT_DIR="$HOME/.config/systemd/user" UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service" +# FIXME: the must run as root (because of CI). It's also broken... + function setup() { skip_if_not_systemd skip_if_remote |