Merge pull request #15584 from sstosh/generate-systemd-env

Add generate systemd -e/--env option
This commit is contained in:
OpenShift Merge Robot
2022-09-06 15:40:55 +02:00
committed by GitHub
11 changed files with 263 additions and 150 deletions

View File

@ -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", nil, "Set environment variables to the systemd unit files")
_ = systemdCmd.RegisterFlagCompletionFunc(envFlagName, completion.AutocompleteNone)
flags.SetNormalizeFunc(utils.TimeoutAliasFlags)
}
@ -141,6 +147,13 @@ func systemd(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed(stopTimeoutCompatFlagName) {
setStopTimeout++
}
if cmd.Flags().Changed(envFlagName) {
cliEnv, err := envLib.ParseSlice(envInput)
if err != nil {
return fmt.Errorf("error parsing environment variables: %w", err)
}
systemdOptions.AdditionalEnvVariables = envLib.Slice(cliEnv)
}
switch setStopTimeout {
case 1:
systemdOptions.StopTimeout = &stopTimeout

View File

@ -44,6 +44,12 @@ 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.
#### **--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.

View File

@ -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 []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)

View File

@ -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:

View File

@ -38,4 +38,6 @@ type SystemdOptions struct {
After *[]string
// Requires - systemd requires list for the container or pods
Requires *[]string
// AdditionalEnvVariables - Sets environment variables to a systemd unit file
AdditionalEnvVariables *[]string
}

View File

@ -226,3 +226,18 @@ func (o *SystemdOptions) GetRequires() []string {
}
return *o.Requires
}
// WithAdditionalEnvVariables set field AdditionalEnvVariables to given value
func (o *SystemdOptions) WithAdditionalEnvVariables(value []string) *SystemdOptions {
o.AdditionalEnvVariables = &value
return o
}
// GetAdditionalEnvVariables returns value of field AdditionalEnvVariables
func (o *SystemdOptions) GetAdditionalEnvVariables() []string {
if o.AdditionalEnvVariables == nil {
var z []string
return z
}
return *o.AdditionalEnvVariables
}

View File

@ -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 []string
}
// GenerateSystemdReport

View File

@ -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)

View File

@ -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 []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={{{{ $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
@ -324,6 +284,9 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
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}}}}"
for i, env := range info.AdditionalEnvVariables {
info.AdditionalEnvVariables[i] = escapeSystemdArg(env)
}
// Assemble the ExecStart command when creating a new container.
//

View File

@ -780,6 +780,33 @@ ExecStopPost=/usr/bin/podman rm -f --ignore --cidfile=%t/%n.ctr-id
Type=notify
NotifyAccess=all
[Install]
WantedBy=default.target
`
goodEnvironment := `# container-foobar.service
# autogenerated by Podman CI
[Unit]
Description=Podman container-foobar.service
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
Environment=FOO=abc
Environment="BAR=my test"
Environment=USER=%%a
Restart=on-failure
TimeoutStopSec=70
ExecStart=/usr/bin/podman start foobar
ExecStop=/usr/bin/podman stop -t 10 foobar
ExecStopPost=/usr/bin/podman stop -t 10 foobar
PIDFile=/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid
Type=forking
[Install]
WantedBy=default.target
`
@ -1424,7 +1451,7 @@ WantedBy=default.target
false,
false,
},
{"good with environment variables",
{"good with container environment variables",
containerInfo{
Executable: "/usr/bin/podman",
ServiceName: "jadda-jadda",
@ -1444,6 +1471,25 @@ WantedBy=default.target
false,
false,
},
{"good with systemd environment variables",
containerInfo{
Executable: "/usr/bin/podman",
ServiceName: "container-foobar",
ContainerNameOrID: "foobar",
PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
StopTimeout: 10,
PodmanVersion: "CI",
GraphRoot: "/var/lib/containers/storage",
RunRoot: "/var/run/containers/storage",
EnvVariable: define.EnvVariable,
AdditionalEnvVariables: []string{"FOO=abc", "BAR=my test", "USER=%a"},
},
goodEnvironment,
false,
false,
false,
false,
},
{"good with restart policy",
containerInfo{
Executable: "/usr/bin/podman",

View File

@ -600,4 +600,75 @@ 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"))
// Escape systemd arguments
session = podmanTest.Podman([]string{"generate", "systemd", "--env", "BAR=my test", "-e", "USER=%a", "test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("\"BAR=my test\""))
Expect(session.OutputToString()).To(ContainSubstring("USER=%%a"))
session = podmanTest.Podman([]string{"generate", "systemd", "--env", "BAR=my test", "-e", "USER=%a", "--new", "test"})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
Expect(session.OutputToString()).To(ContainSubstring("\"BAR=my test\""))
Expect(session.OutputToString()).To(ContainSubstring("USER=%%a"))
// Specify the environment variables without a value
os.Setenv("FOO1", "BAR1")
os.Setenv("FOO2", "BAR2")
os.Setenv("FOO3", "BAR3")
defer os.Unsetenv("FOO1")
defer os.Unsetenv("FOO2")
defer os.Unsetenv("FOO3")
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"))
session = podmanTest.Podman([]string{"generate", "systemd", "--env", "FOO*", "--new", "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"))
})
})