diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/api/handlers/libpod/generate.go | 34 | ||||
-rw-r--r-- | pkg/bindings/generate/types.go | 2 | ||||
-rw-r--r-- | pkg/bindings/generate/types_systemd_options.go | 15 | ||||
-rw-r--r-- | pkg/domain/entities/generate.go | 2 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/generate.go | 2 | ||||
-rw-r--r-- | pkg/systemd/generate/common.go | 4 | ||||
-rw-r--r-- | pkg/systemd/generate/containers.go | 52 | ||||
-rw-r--r-- | pkg/systemd/generate/containers_test.go | 119 | ||||
-rw-r--r-- | pkg/systemd/generate/pods.go | 5 |
9 files changed, 212 insertions, 23 deletions
diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index 117c5e2aa..5205d875d 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -17,14 +17,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) query := struct { - Name bool `schema:"useName"` - New bool `schema:"new"` - NoHeader bool `schema:"noHeader"` - RestartPolicy *string `schema:"restartPolicy"` - StopTimeout uint `schema:"stopTimeout"` - ContainerPrefix string `schema:"containerPrefix"` - PodPrefix string `schema:"podPrefix"` - Separator string `schema:"separator"` + Name bool `schema:"useName"` + New bool `schema:"new"` + NoHeader bool `schema:"noHeader"` + TemplateUnitFile bool `schema:"templateUnitFile"` + RestartPolicy *string `schema:"restartPolicy"` + StopTimeout uint `schema:"stopTimeout"` + ContainerPrefix string `schema:"containerPrefix"` + PodPrefix string `schema:"podPrefix"` + Separator string `schema:"separator"` }{ StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout, ContainerPrefix: "container", @@ -40,14 +41,15 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) { containerEngine := abi.ContainerEngine{Libpod: runtime} options := entities.GenerateSystemdOptions{ - Name: query.Name, - New: query.New, - NoHeader: query.NoHeader, - RestartPolicy: query.RestartPolicy, - StopTimeout: &query.StopTimeout, - ContainerPrefix: query.ContainerPrefix, - PodPrefix: query.PodPrefix, - Separator: query.Separator, + Name: query.Name, + New: query.New, + NoHeader: query.NoHeader, + TemplateUnitFile: query.TemplateUnitFile, + 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) diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go index 3c9ea87d4..6f2594604 100644 --- a/pkg/bindings/generate/types.go +++ b/pkg/bindings/generate/types.go @@ -16,6 +16,8 @@ type SystemdOptions struct { New *bool // NoHeader - Removes autogenerated by Podman and timestamp if set to true NoHeader *bool + // TemplateUnitFile - Create a template unit file that uses the identity specifiers + TemplateUnitFile *bool // RestartPolicy - systemd restart policy. RestartPolicy *string // StopTimeout - time when stopping the container. diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go index 7a778a52b..b26aa7fc2 100644 --- a/pkg/bindings/generate/types_systemd_options.go +++ b/pkg/bindings/generate/types_systemd_options.go @@ -62,6 +62,21 @@ func (o *SystemdOptions) GetNoHeader() bool { return *o.NoHeader } +// WithTemplateUnitFile set field TemplateUnitFile to given value +func (o *SystemdOptions) WithTemplateUnitFile(value bool) *SystemdOptions { + o.TemplateUnitFile = &value + return o +} + +// GetTemplateUnitFile returns value of field TemplateUnitFile +func (o *SystemdOptions) GetTemplateUnitFile() bool { + if o.TemplateUnitFile == nil { + var z bool + return z + } + return *o.TemplateUnitFile +} + // WithRestartPolicy set field RestartPolicy to given value func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions { o.RestartPolicy = &value diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index 7809c5241..dfb5bfc6c 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -20,6 +20,8 @@ type GenerateSystemdOptions struct { Separator string // NoHeader - skip header generation NoHeader bool + // TemplateUnitFile - make use of %i and %I to differentiate between the different instances of the unit + TemplateUnitFile bool } // GenerateSystemdReport diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index 9f69abb1a..3a35dd59c 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -8,7 +8,7 @@ import ( ) func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, opts entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { - options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader) + options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader).WithTemplateUnitFile(opts.TemplateUnitFile) options.WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator) if opts.RestartPolicy != nil { options.WithRestartPolicy(*opts.RestartPolicy) diff --git a/pkg/systemd/generate/common.go b/pkg/systemd/generate/common.go index 3515bb3b7..24c85a27e 100644 --- a/pkg/systemd/generate/common.go +++ b/pkg/systemd/generate/common.go @@ -23,7 +23,7 @@ func validateRestartPolicy(restart string) error { return errors.Errorf("%s is not a valid restart policy", restart) } -const headerTemplate = `# {{{{.ServiceName}}}}.service +const headerTemplate = `# {{{{.ServiceName}}}}{{{{- if (eq .IdentifySpecifier true) }}}}@{{{{- end}}}}.service {{{{- if (eq .GenerateNoHeader false) }}}} # autogenerated by Podman {{{{.PodmanVersion}}}} {{{{- if .TimeStamp}}}} @@ -32,7 +32,7 @@ const headerTemplate = `# {{{{.ServiceName}}}}.service {{{{- end}}}} [Unit] -Description=Podman {{{{.ServiceName}}}}.service +Description=Podman {{{{.ServiceName}}}}.service{{{{- if (eq .IdentifySpecifier true) }}}} for %I{{{{- end}}}} Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 037652a6d..95ff13371 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -90,6 +90,8 @@ type containerInfo struct { // Location of the RunRoot for the container. Required for ensuring the tmpfs // or volume exists and is mounted when coming online at boot. RunRoot string + // Add %i and %I to description and execute parts + IdentifySpecifier bool } const containerTemplate = headerTemplate + ` @@ -99,7 +101,7 @@ After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{ {{{{- end}}}} [Service] -Environment={{{{.EnvVariable}}}}=%n +Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i{{{{- end}}}} {{{{- if .ExtraEnvs}}}} Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} {{{{- end}}}} @@ -204,6 +206,46 @@ func containerServiceName(ctr *libpod.Container, options entities.GenerateSystem return nameOrID, serviceName } +// setContainerNameForTemplate updates startCommand to contain the name argument with +// a value that includes the identify specifier. +// In case startCommand doesn't contain that argument it's added after "run" and its +// value will be set to info.ServiceName concated with the identify specifier %i. +func setContainerNameForTemplate(startCommand []string, info *containerInfo) ([]string, error) { + // find the index of "--name" in the command slice + nameIx := -1 + for argIx, arg := range startCommand { + if arg == "--name" { + nameIx = argIx + 1 + break + } + if strings.HasPrefix(arg, "--name=") { + nameIx = argIx + break + } + } + switch { + case nameIx == -1: + // if not found, add --name argument in the command slice before the "run" argument. + // it's assumed that the command slice contains this argument. + runIx := -1 + for argIx, arg := range startCommand { + if arg == "run" { + runIx = argIx + break + } + } + if runIx == -1 { + return startCommand, fmt.Errorf("\"run\" is missing in the command arguments") + } + startCommand = append(startCommand[:runIx+1], startCommand[runIx:]...) + startCommand[runIx+1] = fmt.Sprintf("--name=%s-%%i", info.ServiceName) + default: + // append the identity specifier (%i) to the end of the --name value + startCommand[nameIx] = fmt.Sprintf("%s-%%i", startCommand[nameIx]) + } + return startCommand, nil +} + // executeContainerTemplate executes the container template on the specified // containerInfo. Note that the containerInfo is also post processed and // completed, which allows for an easier unit testing. @@ -273,7 +315,6 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst "--rm", ) remainingCmd := info.CreateCommand[index:] - // Presence check for certain flags/options. fs := pflag.NewFlagSet("args", pflag.ContinueOnError) fs.ParseErrorsWhitelist.UnknownFlags = true @@ -389,6 +430,13 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst startCommand = append(startCommand, remainingCmd...) startCommand = escapeSystemdArguments(startCommand) + if options.TemplateUnitFile { + info.IdentifySpecifier = true + startCommand, err = setContainerNameForTemplate(startCommand, info) + if err != nil { + return "", err + } + } info.ExecStart = strings.Join(startCommand, " ") } diff --git a/pkg/systemd/generate/containers_test.go b/pkg/systemd/generate/containers_test.go index f46513459..eab2c2e67 100644 --- a/pkg/systemd/generate/containers_test.go +++ b/pkg/systemd/generate/containers_test.go @@ -1,6 +1,7 @@ package generate import ( + "fmt" "testing" "github.com/containers/podman/v3/pkg/domain/entities" @@ -522,6 +523,32 @@ NotifyAccess=all [Install] WantedBy=multi-user.target default.target ` + + templateGood := `# container-foo@.service +# autogenerated by Podman CI + +[Unit] +Description=Podman container-foo.service for %I +Documentation=man:podman-generate-systemd(1) +Wants=network-online.target +After=network-online.target +RequiresMountsFor=/var/run/containers/storage + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n-%i +Restart=on-failure +StartLimitBurst=42 +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/%n.ctr-id +ExecStart=/usr/bin/podman run --name=container-foo-%i --cidfile=%t/%n.ctr-id --cgroups=no-conmon --rm --sdnotify=conmon -d awesome-image:latest +ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id +ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id +Type=notify +NotifyAccess=all + +[Install] +WantedBy=multi-user.target default.target +` tests := []struct { name string info containerInfo @@ -529,6 +556,7 @@ WantedBy=multi-user.target default.target new bool noHeader bool wantErr bool + template bool }{ {"good with id", @@ -547,6 +575,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with noHeader", containerInfo{ @@ -564,6 +593,7 @@ WantedBy=multi-user.target default.target false, true, false, + false, }, {"good with name", containerInfo{ @@ -581,6 +611,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with name and bound to", containerInfo{ @@ -599,6 +630,7 @@ WantedBy=multi-user.target default.target false, false, false, + false, }, {"good with name and generic", containerInfo{ @@ -617,6 +649,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with name and sdnotify", containerInfo{ @@ -635,6 +668,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit short detach param", containerInfo{ @@ -653,6 +687,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit short detach param and podInfo", containerInfo{ @@ -674,6 +709,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit full detach param", containerInfo{ @@ -692,6 +728,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with id and no param", containerInfo{ @@ -710,6 +747,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=true param", containerInfo{ @@ -728,6 +766,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=false param", containerInfo{ @@ -746,6 +785,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with explicit detach=false param", containerInfo{ @@ -764,6 +804,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple detach=false params", containerInfo{ @@ -782,6 +823,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple shorthand params detach first", containerInfo{ @@ -800,6 +842,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with multiple shorthand params detach last", containerInfo{ @@ -818,6 +861,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with container create", containerInfo{ @@ -836,6 +880,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with journald log tag (see #9034)", containerInfo{ @@ -854,6 +899,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with special chars", containerInfo{ @@ -872,6 +918,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with ID files", containerInfo{ @@ -890,6 +937,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with pod ID files", containerInfo{ @@ -911,6 +959,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with environment variables", containerInfo{ @@ -930,6 +979,7 @@ WantedBy=multi-user.target default.target true, false, false, + false, }, {"good with restart policy", containerInfo{ @@ -948,14 +998,34 @@ WantedBy=multi-user.target default.target true, false, false, + false, + }, + {"good template", + containerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "container-foo", + ContainerNameOrID: "foo", + PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 10, + PodmanVersion: "CI", + GraphRoot: "/var/lib/containers/storage", + RunRoot: "/var/run/containers/storage", + CreateCommand: []string{"I'll get stripped", "create", "--restart", "on-failure:42", "awesome-image:latest"}, + }, + templateGood, + true, + false, + false, + true, }, } for _, tt := range tests { test := tt t.Run(tt.name, func(t *testing.T) { opts := entities.GenerateSystemdOptions{ - New: test.new, - NoHeader: test.noHeader, + New: test.new, + NoHeader: test.noHeader, + TemplateUnitFile: test.template, } test.info.RestartPolicy = define.DefaultRestartPolicy got, err := executeContainerTemplate(&test.info, opts) @@ -967,3 +1037,48 @@ WantedBy=multi-user.target default.target }) } } + +func TestSetContainerNameForTemplate(t *testing.T) { + tt := []struct { + name string + startCommand []string + info *containerInfo + expected []string + err error + }{ + { + name: "no name argument is set", + startCommand: []string{"/usr/bin/podman", "run", "busybox", "top"}, + info: &containerInfo{ServiceName: "container-122"}, + expected: []string{"/usr/bin/podman", "run", "--name=container-122-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name=value is used in arguments", + startCommand: []string{"/usr/bin/podman", "run", "--name=lovely_james", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "run", "--name=lovely_james-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name value is used in arguments", + startCommand: []string{"/usr/bin/podman", "run", "--name", "lovely_james", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "run", "--name", "lovely_james-%i", "busybox", "top"}, + err: nil, + }, + { + name: "--name value is used in arguments", + startCommand: []string{"/usr/bin/podman", "create", "busybox", "top"}, + info: &containerInfo{}, + expected: []string{"/usr/bin/podman", "create", "busybox", "top"}, + err: fmt.Errorf("\"run\" is missing in the command arguments"), + }, + } + + for _, te := range tt { + res, err := setContainerNameForTemplate(te.startCommand, te.info) + assert.Equal(t, te.err, err) + assert.Equal(t, te.expected, res) + } +} diff --git a/pkg/systemd/generate/pods.go b/pkg/systemd/generate/pods.go index e755b8eea..38f7e8e3e 100644 --- a/pkg/systemd/generate/pods.go +++ b/pkg/systemd/generate/pods.go @@ -79,6 +79,8 @@ type podInfo struct { // Location of the RunRoot for the pod. Required for ensuring the tmpfs // or volume exists and is mounted when coming online at boot. RunRoot string + // Add %i and %I to description and execute parts - this should not be used + IdentifySpecifier bool } const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}} @@ -108,6 +110,9 @@ WantedBy=multi-user.target default.target // 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) (map[string]string, error) { + if options.TemplateUnitFile { + return nil, errors.New("--template is not supported for pods") + } // Error out if the pod has no infra container, which we require to be the // main service. if !pod.HasInfraContainer() { |