summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/inspect.go18
-rw-r--r--cmd/podman/containers/inspect.go53
-rw-r--r--cmd/podman/generate/generate.go27
-rw-r--r--cmd/podman/generate/systemd.go57
-rw-r--r--cmd/podman/images/inspect.go92
-rw-r--r--cmd/podman/inspect.go42
-rw-r--r--cmd/podman/inspect/inspect.go159
-rw-r--r--cmd/podman/main.go1
-rw-r--r--pkg/bindings/images/images.go4
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/engine_image.go2
-rw-r--r--pkg/domain/entities/generate.go22
-rw-r--r--pkg/domain/entities/images.go8
-rw-r--r--pkg/domain/entities/types.go10
-rw-r--r--pkg/domain/infra/abi/generate.go174
-rw-r--r--pkg/domain/infra/abi/images.go31
-rw-r--r--pkg/domain/infra/tunnel/generate.go12
-rw-r--r--pkg/domain/infra/tunnel/images.go14
-rw-r--r--pkg/specgen/generate/container_create.go1
-rw-r--r--test/e2e/generate_systemd_test.go1
-rw-r--r--test/e2e/inspect_test.go1
-rw-r--r--test/system/250-generate-systemd.bats2
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