Skip to content

Commit

Permalink
Add generate systemd -e/--env option
Browse files Browse the repository at this point in the history
-e/--env option sets environment variables to the systemd unit files.

Fixes: containers#15523

Signed-off-by: Toshiki Sonoda <sonoda.toshiki@fujitsu.com>
  • Loading branch information
sstosh committed Sep 2, 2022
1 parent 72f4c77 commit 773b15d
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 121 deletions.
16 changes: 16 additions & 0 deletions cmd/podman/generate/systemd.go
Expand Up @@ -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"
Expand All @@ -28,9 +29,11 @@ const (
wantsFlagName = "wants"
afterFlagName = "after"
requiresFlagName = "requires"
envFlagName = "env"
)

var (
envInput []string
files bool
format string
systemdRestart string
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -141,6 +147,16 @@ func systemd(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed(stopTimeoutCompatFlagName) {
setStopTimeout++
}
if cmd.Flags().Changed(envFlagName) {
systemdOptions.Envs = make(map[string]string)

cliEnv, err := envLib.ParseSlice(envInput)
if err != nil {
return fmt.Errorf("error parsing environment variables: %w", err)
}

systemdOptions.Envs = envLib.Join(systemdOptions.Envs, cliEnv)
}
switch setStopTimeout {
case 1:
systemdOptions.StopTimeout = &stopTimeout
Expand Down
8 changes: 8 additions & 0 deletions docs/source/markdown/podman-generate-systemd.1.md
Expand Up @@ -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.

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.
Expand Down
30 changes: 16 additions & 14 deletions pkg/api/handlers/libpod/generate.go
Expand Up @@ -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"`
Envs map[string]string `schema:"envs"`
}{
StartTimeout: 0,
StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout,
Expand Down Expand Up @@ -72,6 +73,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
Wants: query.Wants,
After: query.After,
Requires: query.Requires,
Envs: query.Envs,
}

report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options)
Expand Down
8 changes: 8 additions & 0 deletions pkg/api/server/register_generate.go
Expand Up @@ -93,6 +93,14 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// type: string
// default: []
// description: Systemd Requires list for the container or pods.
// - in: query
// name: envs
// schema:
// type: object
// additionalProperties:
// type: string
// default: []
// description: Set environment variables to the systemd unit files.
// produces:
// - application/json
// responses:
Expand Down
2 changes: 2 additions & 0 deletions pkg/bindings/generate/types.go
Expand Up @@ -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
}
15 changes: 15 additions & 0 deletions pkg/bindings/generate/types_systemd_options.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 14 additions & 27 deletions pkg/domain/entities/generate.go
Expand Up @@ -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
Name bool
New bool
RestartPolicy *string
RestartSec *uint
StartTimeout *uint
StopTimeout *uint
ContainerPrefix string
PodPrefix string
Separator string
NoHeader bool
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
Wants []string
After []string
Requires []string
Envs map[string]string
}

// GenerateSystemdReport
Expand Down
3 changes: 2 additions & 1 deletion pkg/domain/infra/tunnel/generate.go
Expand Up @@ -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).
WithEnvs(opts.Envs)

if opts.StartTimeout != nil {
options.WithStartTimeout(*opts.StartTimeout)
Expand Down
118 changes: 39 additions & 79 deletions pkg/systemd/generate/containers.go
Expand Up @@ -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 + `
Expand All @@ -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}}}}
Expand Down Expand Up @@ -321,6 +280,7 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst

info.Type = "forking"
info.EnvVariable = define.EnvVariable
info.AdditionalEnvVariables = options.Envs
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}}}}"
Expand Down
28 changes: 28 additions & 0 deletions test/e2e/generate_systemd_test.go
Expand Up @@ -600,4 +600,32 @@ 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"))

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"))
})
})

0 comments on commit 773b15d

Please sign in to comment.