summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/libpod/generate.go34
-rw-r--r--pkg/bindings/generate/types.go2
-rw-r--r--pkg/bindings/generate/types_systemd_options.go15
-rw-r--r--pkg/domain/entities/generate.go2
-rw-r--r--pkg/domain/infra/tunnel/generate.go2
-rw-r--r--pkg/systemd/generate/common.go4
-rw-r--r--pkg/systemd/generate/containers.go52
-rw-r--r--pkg/systemd/generate/containers_test.go119
-rw-r--r--pkg/systemd/generate/pods.go5
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() {