From a8d8e75f0f03db1f209badc1dbc2a8f4491d532e Mon Sep 17 00:00:00 2001 From: Toshiki Sonoda Date: Fri, 2 Sep 2022 16:20:26 +0900 Subject: [PATCH] Add generate systemd -e/--env option -e/--env option sets environment variables to the systemd unit files. Fixes: #15523 Signed-off-by: Toshiki Sonoda --- cmd/podman/generate/systemd.go | 16 ++ .../markdown/podman-generate-systemd.1.md | 8 + pkg/api/handlers/libpod/generate.go | 58 +++---- pkg/api/server/register_generate.go | 7 + pkg/bindings/generate/types.go | 2 + .../generate/types_systemd_options.go | 15 ++ pkg/domain/entities/generate.go | 43 ++---- pkg/domain/infra/tunnel/generate.go | 3 +- pkg/systemd/generate/containers.go | 145 +++++++----------- test/e2e/generate_systemd_test.go | 50 ++++++ 10 files changed, 198 insertions(+), 149 deletions(-) diff --git a/cmd/podman/generate/systemd.go b/cmd/podman/generate/systemd.go index e40416534a6e..40de5fe20d13 100644 --- a/cmd/podman/generate/systemd.go +++ b/cmd/podman/generate/systemd.go @@ -13,6 +13,7 @@ import ( "github.com/containers/podman/v4/cmd/podman/registry" "github.com/containers/podman/v4/cmd/podman/utils" "github.com/containers/podman/v4/pkg/domain/entities" + envLib "github.com/containers/podman/v4/pkg/env" systemDefine "github.com/containers/podman/v4/pkg/systemd/define" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -28,9 +29,11 @@ const ( wantsFlagName = "wants" afterFlagName = "after" requiresFlagName = "requires" + envFlagName = "env" ) var ( + envInput []string files bool format string systemdRestart string @@ -109,6 +112,9 @@ func init() { flags.StringArrayVar(&systemdOptions.Requires, requiresFlagName, nil, "Similar to wants, but declares stronger requirement dependencies") _ = systemdCmd.RegisterFlagCompletionFunc(requiresFlagName, completion.AutocompleteNone) + flags.StringArrayVarP(&envInput, envFlagName, "e", []string{}, "Set environment variables to the systemd unit files") + _ = systemdCmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone) + flags.SetNormalizeFunc(utils.TimeoutAliasFlags) } @@ -141,6 +147,16 @@ func systemd(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed(stopTimeoutCompatFlagName) { setStopTimeout++ } + if cmd.Flags().Changed(envFlagName) { + systemdOptions.AdditionalEnvVariables = make(map[string]string) + + cliEnv, err := envLib.ParseSlice(envInput) + if err != nil { + return fmt.Errorf("error parsing environment variables: %w", err) + } + + systemdOptions.AdditionalEnvVariables = envLib.Join(systemdOptions.AdditionalEnvVariables, cliEnv) + } switch setStopTimeout { case 1: systemdOptions.StopTimeout = &stopTimeout diff --git a/docs/source/markdown/podman-generate-systemd.1.md b/docs/source/markdown/podman-generate-systemd.1.md index 88dff2a4568c..f40007191c96 100644 --- a/docs/source/markdown/podman-generate-systemd.1.md +++ b/docs/source/markdown/podman-generate-systemd.1.md @@ -44,6 +44,14 @@ User-defined dependencies will be appended to the generated unit file, but any e Set the systemd unit name prefix for containers. The default is *container*. +#### **--env**, **-e**=*env* + +Set environment variables to the systemd unit files. + +If an environment variable is specified without a value, Podman will check the host environment for a value and set the variable only if it is set on the host. As a special case, if an environment variable ending in __*__ is specified without a value, Podman will search the host environment for variables starting with the prefix and will add those variables to the systemd unit files. + +See [**Environment**](#environment) note below for precedence and examples. + #### **--files**, **-f** Generate files instead of printing to stdout. The generated files are named {container,pod}-{ID,name}.service and will be placed in the current working directory. diff --git a/pkg/api/handlers/libpod/generate.go b/pkg/api/handlers/libpod/generate.go index 48c4c59e1da3..196f47a703a9 100644 --- a/pkg/api/handlers/libpod/generate.go +++ b/pkg/api/handlers/libpod/generate.go @@ -17,20 +17,21 @@ 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"` - TemplateUnitFile bool `schema:"templateUnitFile"` - RestartPolicy *string `schema:"restartPolicy"` - RestartSec uint `schema:"restartSec"` - StopTimeout uint `schema:"stopTimeout"` - StartTimeout uint `schema:"startTimeout"` - ContainerPrefix *string `schema:"containerPrefix"` - PodPrefix *string `schema:"podPrefix"` - Separator *string `schema:"separator"` - Wants []string `schema:"wants"` - After []string `schema:"after"` - Requires []string `schema:"requires"` + Name bool `schema:"useName"` + New bool `schema:"new"` + NoHeader bool `schema:"noHeader"` + TemplateUnitFile bool `schema:"templateUnitFile"` + RestartPolicy *string `schema:"restartPolicy"` + RestartSec uint `schema:"restartSec"` + StopTimeout uint `schema:"stopTimeout"` + StartTimeout uint `schema:"startTimeout"` + ContainerPrefix *string `schema:"containerPrefix"` + PodPrefix *string `schema:"podPrefix"` + Separator *string `schema:"separator"` + Wants []string `schema:"wants"` + After []string `schema:"after"` + Requires []string `schema:"requires"` + AdditionalEnvVariables map[string]string `schema:"additionalEnvVariables"` }{ StartTimeout: 0, StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout, @@ -58,20 +59,21 @@ 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, - TemplateUnitFile: query.TemplateUnitFile, - RestartPolicy: query.RestartPolicy, - StartTimeout: &query.StartTimeout, - StopTimeout: &query.StopTimeout, - ContainerPrefix: ContainerPrefix, - PodPrefix: PodPrefix, - Separator: Separator, - RestartSec: &query.RestartSec, - Wants: query.Wants, - After: query.After, - Requires: query.Requires, + Name: query.Name, + New: query.New, + NoHeader: query.NoHeader, + TemplateUnitFile: query.TemplateUnitFile, + RestartPolicy: query.RestartPolicy, + StartTimeout: &query.StartTimeout, + StopTimeout: &query.StopTimeout, + ContainerPrefix: ContainerPrefix, + PodPrefix: PodPrefix, + Separator: Separator, + RestartSec: &query.RestartSec, + Wants: query.Wants, + After: query.After, + Requires: query.Requires, + AdditionalEnvVariables: query.AdditionalEnvVariables, } report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options) diff --git a/pkg/api/server/register_generate.go b/pkg/api/server/register_generate.go index 82fbe3d0959e..ac2818db05da 100644 --- a/pkg/api/server/register_generate.go +++ b/pkg/api/server/register_generate.go @@ -93,6 +93,13 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { // type: string // default: [] // description: Systemd Requires list for the container or pods. + // - in: query + // name: additionalEnvVariables + // type: array + // items: + // type: string + // default: [] + // description: Set environment variables to the systemd unit files. // produces: // - application/json // responses: diff --git a/pkg/bindings/generate/types.go b/pkg/bindings/generate/types.go index 25c398c8b93b..73b260e4e1f0 100644 --- a/pkg/bindings/generate/types.go +++ b/pkg/bindings/generate/types.go @@ -38,4 +38,6 @@ type SystemdOptions struct { After *[]string // Requires - systemd requires list for the container or pods Requires *[]string + // AdditionalEnvVariables - environment variables setted by -e/--env + AdditionalEnvVariables map[string]string } diff --git a/pkg/bindings/generate/types_systemd_options.go b/pkg/bindings/generate/types_systemd_options.go index 4d436945b4d3..8bc42894ce6e 100644 --- a/pkg/bindings/generate/types_systemd_options.go +++ b/pkg/bindings/generate/types_systemd_options.go @@ -226,3 +226,18 @@ func (o *SystemdOptions) GetRequires() []string { } return *o.Requires } + +// WithAdditionalEnvVariables set field AdditionalEnvVariables to given value +func (o *SystemdOptions) WithAdditionalEnvVariables(value map[string]string) *SystemdOptions { + o.AdditionalEnvVariables = value + return o +} + +// GetAdditionalEnvVariables returns value of field AdditionalEnvVariables +func (o *SystemdOptions) GetAdditionalEnvVariables() map[string]string { + if o.AdditionalEnvVariables == nil { + var z map[string]string + return z + } + return o.AdditionalEnvVariables +} diff --git a/pkg/domain/entities/generate.go b/pkg/domain/entities/generate.go index f18e79b472f0..3d352df4a6f2 100644 --- a/pkg/domain/entities/generate.go +++ b/pkg/domain/entities/generate.go @@ -4,34 +4,21 @@ import "io" // GenerateSystemdOptions control the generation of systemd unit files. type GenerateSystemdOptions struct { - // 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 - // RestartSec - systemd service restartsec. Configures the time to sleep before restarting a service. - RestartSec *uint - // StartTimeout - time when starting the container. - StartTimeout *uint - // StopTimeout - time when stopping the container. - StopTimeout *uint - // ContainerPrefix - systemd unit name prefix for containers - ContainerPrefix string - // PodPrefix - systemd unit name prefix for pods - PodPrefix string - // Separator - systemd unit name separator between name/id and prefix - 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 - // Wants - systemd wants list for the container or pods - Wants []string - // After - systemd after list for the container or pods - After []string - // Requires - systemd requires list for the container or pods - Requires []string + Name bool + New bool + RestartPolicy *string + RestartSec *uint + StartTimeout *uint + StopTimeout *uint + ContainerPrefix string + PodPrefix string + Separator string + NoHeader bool + TemplateUnitFile bool + Wants []string + After []string + Requires []string + AdditionalEnvVariables map[string]string } // GenerateSystemdReport diff --git a/pkg/domain/infra/tunnel/generate.go b/pkg/domain/infra/tunnel/generate.go index ed63d363a148..d3c3638cb364 100644 --- a/pkg/domain/infra/tunnel/generate.go +++ b/pkg/domain/infra/tunnel/generate.go @@ -19,7 +19,8 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, WithSeparator(opts.Separator). WithWants(opts.Wants). WithAfter(opts.After). - WithRequires(opts.Requires) + WithRequires(opts.Requires). + WithAdditionalEnvVariables(opts.AdditionalEnvVariables) if opts.StartTimeout != nil { options.WithStartTimeout(*opts.StartTimeout) diff --git a/pkg/systemd/generate/containers.go b/pkg/systemd/generate/containers.go index 1f8c519b7998..91d45462eb66 100644 --- a/pkg/systemd/generate/containers.go +++ b/pkg/systemd/generate/containers.go @@ -22,85 +22,40 @@ import ( // containerInfo contains data required for generating a container's systemd // unit file. type containerInfo struct { - // ServiceName of the systemd service. - ServiceName string - // Name or ID of the container. - ContainerNameOrID string - // Type of the unit. - Type string - // NotifyAccess of the unit. - NotifyAccess string - // StopTimeout sets the timeout Podman waits before killing the container - // during service stop. - StopTimeout uint - // RestartPolicy of the systemd unit (e.g., no, on-failure, always). - RestartPolicy string - // Custom number of restart attempts. - StartLimitBurst string - // PIDFile of the service. Required for forking services. Must point to the - // PID of the associated conmon process. - PIDFile string - // ContainerIDFile to be used in the unit. - ContainerIDFile string - // GenerateTimestamp, if set the generated unit file has a time stamp. - GenerateTimestamp bool - // BoundToServices are the services this service binds to. Note that this - // service runs after them. - BoundToServices []string - // PodmanVersion for the header. Will be set internally. Will be auto-filled - // if left empty. - PodmanVersion string - // Executable is the path to the podman executable. Will be auto-filled if - // left empty. - Executable string - // RootFlags contains the root flags which were used to create the container - // Only used with --new - RootFlags string - // TimeStamp at the time of creating the unit file. Will be set internally. - TimeStamp string - // CreateCommand is the full command plus arguments of the process the - // container has been created with. - CreateCommand []string - // containerEnv stores the container environment variables - containerEnv []string - // ExtraEnvs contains the container environment variables referenced - // by only the key in the container create command, e.g. --env FOO. - // This is only used with --new - ExtraEnvs []string - // EnvVariable is generate.EnvVariable and must not be set. - EnvVariable string - // ExecStartPre of the unit. - ExecStartPre string - // ExecStart of the unit. - ExecStart string - // TimeoutStartSec of the unit. - TimeoutStartSec uint - // TimeoutStopSec of the unit. - TimeoutStopSec uint - // ExecStop of the unit. - ExecStop string - // ExecStopPost of the unit. - ExecStopPost string - // Removes autogenerated by Podman and timestamp if set to true - GenerateNoHeader bool - // If not nil, the container is part of the pod. We can use the - // podInfo to extract the relevant data. - Pod *podInfo - // Location of the GraphRoot for the container. Required for ensuring the - // volume has finished mounting when coming online at boot. - GraphRoot string - // 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 - // Wants are the list of services that this service is (weak) dependent on. This - // option does not influence the order in which services are started or stopped. - Wants []string - // After ordering dependencies between the list of services and this service. - After []string - // Similar to Wants, but declares a stronger requirement dependency. - Requires []string + ServiceName string + ContainerNameOrID string + Type string + NotifyAccess string + StopTimeout uint + RestartPolicy string + StartLimitBurst string + PIDFile string + ContainerIDFile string + GenerateTimestamp bool + BoundToServices []string + PodmanVersion string + Executable string + RootFlags string + TimeStamp string + CreateCommand []string + containerEnv []string + ExtraEnvs []string + EnvVariable string + AdditionalEnvVariables map[string]string + ExecStartPre string + ExecStart string + TimeoutStartSec uint + TimeoutStopSec uint + ExecStop string + ExecStopPost string + GenerateNoHeader bool + Pod *podInfo + GraphRoot string + RunRoot string + IdentifySpecifier bool + Wants []string + After []string + Requires []string } const containerTemplate = headerTemplate + ` @@ -127,6 +82,10 @@ Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i {{{{- if .ExtraEnvs}}}} Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}} {{{{- end}}}} +{{{{- if .AdditionalEnvVariables}}}} +{{{{- range $index, $value := .AdditionalEnvVariables -}}}}{{{{if $index}}}}{{{{end}}}} +Environment={{{{ $index }}}}={{{{ $value }}}}{{{{end}}}} +{{{{- end}}}} Restart={{{{.RestartPolicy}}}} {{{{- if .StartLimitBurst}}}} StartLimitBurst={{{{.StartLimitBurst}}}} @@ -211,19 +170,20 @@ func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSyste envs := config.Spec.Process.Env info := containerInfo{ - ServiceName: serviceName, - ContainerNameOrID: nameOrID, - RestartPolicy: define.DefaultRestartPolicy, - PIDFile: conmonPidFile, - TimeoutStartSec: startTimeout, - StopTimeout: stopTimeout, - GenerateTimestamp: true, - CreateCommand: createCommand, - RunRoot: runRoot, - containerEnv: envs, - Wants: options.Wants, - After: options.After, - Requires: options.Requires, + ServiceName: serviceName, + ContainerNameOrID: nameOrID, + RestartPolicy: define.DefaultRestartPolicy, + PIDFile: conmonPidFile, + TimeoutStartSec: startTimeout, + StopTimeout: stopTimeout, + GenerateTimestamp: true, + CreateCommand: createCommand, + RunRoot: runRoot, + containerEnv: envs, + Wants: options.Wants, + After: options.After, + Requires: options.Requires, + AdditionalEnvVariables: options.AdditionalEnvVariables, } return &info, nil @@ -321,6 +281,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst info.Type = "forking" info.EnvVariable = define.EnvVariable + info.AdditionalEnvVariables = options.AdditionalEnvVariables info.ExecStart = "{{{{.Executable}}}} start {{{{.ContainerNameOrID}}}}" info.ExecStop = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}" info.ExecStopPost = "{{{{.Executable}}}} stop {{{{if (ge .StopTimeout 0)}}}}-t {{{{.StopTimeout}}}}{{{{end}}}} {{{{.ContainerNameOrID}}}}" diff --git a/test/e2e/generate_systemd_test.go b/test/e2e/generate_systemd_test.go index 45a2f1f86359..86deee83ee37 100644 --- a/test/e2e/generate_systemd_test.go +++ b/test/e2e/generate_systemd_test.go @@ -600,4 +600,54 @@ var _ = Describe("Podman generate systemd", func() { Expect(session).Should(Exit(0)) Expect(session.OutputToString()).To(ContainSubstring(" --label key={{someval}}")) }) + + It("podman generate systemd --env", func() { + session := podmanTest.RunTopContainer("test") + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "hoge=fuga", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Environment=foo=bar")) + Expect(session.OutputToString()).To(ContainSubstring("Environment=hoge=fuga")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "=bar", "-e", "hoge=fuga", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("invalid environment variable")) + + // Use -e/--env option with --new option + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "hoge=fuga", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("Environment=foo=bar")) + Expect(session.OutputToString()).To(ContainSubstring("Environment=hoge=fuga")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "foo=bar", "-e", "=fuga", "--new", "test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(125)) + Expect(session.ErrorToString()).To(ContainSubstring("invalid environment variable")) + + // Specify the environment variables without a value + os.Setenv("FOO1", "BAR1") + os.Setenv("FOO2", "BAR2") + os.Setenv("FOO3", "BAR3") + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO1","test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("BAR1")) + Expect(session.OutputToString()).NotTo(ContainSubstring("BAR2")) + Expect(session.OutputToString()).NotTo(ContainSubstring("BAR3")) + + session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO*","test"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).To(ContainSubstring("BAR1")) + Expect(session.OutputToString()).To(ContainSubstring("BAR2")) + Expect(session.OutputToString()).To(ContainSubstring("BAR3")) + os.Unsetenv("FOO1") + os.Unsetenv("FOO2") + os.Unsetenv("FOO3") + }) })