From 816e50ba02837946afade83e3cad06dd44d213ec Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Thu, 9 Jan 2020 12:27:24 +0100 Subject: podman-generate-systemd --new Add a --new flag to podman-generate-systemd to create a new container via podman-run instead of starting an existing container. Creating a new container presents the challenge to find a reverse mapping from a container to the CLI flags it can be created with. We are doing this via `(Container).Config.CreateCommand` field, which includes a copy of the process' command from procFS at creating time. This field may not be useful when the container was not created via the Podman CLI (e.g., via a Python script). Hence, we do not guarantee the correctness of the generated files. Signed-off-by: Valentin Rothberg --- pkg/adapter/containers.go | 15 ++++++++-- pkg/systemdgen/systemdgen.go | 63 +++++++++++++++++++++++++++++++++++++-- pkg/systemdgen/systemdgen_test.go | 51 +++++++++++++++++++++++++++---- 3 files changed, 118 insertions(+), 11 deletions(-) (limited to 'pkg') diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 3334e9fa1..fdd9f6ab3 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -1230,6 +1230,7 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst PIDFile: conmonPidFile, StopTimeout: timeout, GenerateTimestamp: true, + CreateCommand: config.CreateCommand, } return info, true, nil @@ -1237,11 +1238,21 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst // GenerateSystemd creates a unit file for a container or pod. func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) { + opts := systemdgen.Options{ + Files: c.Files, + New: c.New, + } + // First assume it's a container. if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil { return "", err } else if found && err == nil { - return systemdgen.CreateContainerSystemdUnit(info, c.Files) + return systemdgen.CreateContainerSystemdUnit(info, opts) + } + + // --new does not support pods. + if c.New { + return "", errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod") } // We're either having a pod or garbage. @@ -1312,7 +1323,7 @@ func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (stri if i > 0 { builder.WriteByte('\n') } - out, err := systemdgen.CreateContainerSystemdUnit(info, c.Files) + out, err := systemdgen.CreateContainerSystemdUnit(info, opts) if err != nil { return "", err } diff --git a/pkg/systemdgen/systemdgen.go b/pkg/systemdgen/systemdgen.go index 09d3c6fd5..b6167a23e 100644 --- a/pkg/systemdgen/systemdgen.go +++ b/pkg/systemdgen/systemdgen.go @@ -7,6 +7,7 @@ import ( "os" "path/filepath" "sort" + "strings" "text/template" "time" @@ -48,6 +49,14 @@ type ContainerInfo struct { Executable string // TimeStamp at the time of creating the unit file. Will be set internally. TimeStamp string + // New controls if a new container is created or if an existing one is started. + New bool + // CreateCommand is the full command plus arguments of the process the + // container has been created with. + CreateCommand []string + // RunCommand is a post-processed variant of CreateCommand and used for + // the ExecStart field in generic unit files. + RunCommand string } var restartPolicies = []string{"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", "always"} @@ -84,17 +93,35 @@ Before={{- range $index, $value := .RequiredServices -}}{{if $index}} {{end}}{{ [Service] Restart={{.RestartPolicy}} +{{- if .New}} +ExecStartPre=/usr/bin/rm -f /%t/%n-pid /%t/%n-cid +ExecStart={{.RunCommand}} +ExecStop={{.Executable}} stop --cidfile /%t/%n-cid {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} +ExecStopPost={{.Executable}} rm -f --cidfile /%t/%n-cid +PIDFile=/%t/%n-pid +{{- else}} ExecStart={{.Executable}} start {{.ContainerName}} ExecStop={{.Executable}} stop {{if (ge .StopTimeout 0)}}-t {{.StopTimeout}}{{end}} {{.ContainerName}} +PIDFile={{.PIDFile}} +{{- end}} KillMode=none Type=forking -PIDFile={{.PIDFile}} [Install] WantedBy=multi-user.target` +// Options include different options to control the unit file generation. +type Options struct { + // When set, generate service files in the current working directory and + // return the paths to these files instead of returning all contents in one + // big string. + Files bool + // New controls if a new container is created or if an existing one is started. + New bool +} + // CreateContainerSystemdUnit creates a systemd unit file for a container. -func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string, error) { +func CreateContainerSystemdUnit(info *ContainerInfo, opts Options) (string, error) { if err := validateRestartPolicy(info.RestartPolicy); err != nil { return "", err } @@ -109,6 +136,36 @@ func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string info.Executable = executable } + // Assemble the ExecStart command when creating a new container. + // + // Note that we cannot catch all corner cases here such that users + // *must* manually check the generated files. A container might have + // been created via a Python script, which would certainly yield an + // invalid `info.CreateCommand`. Hence, we're doing a best effort unit + // generation and don't try aiming at completeness. + if opts.New { + // The create command must at least have three arguments: + // /usr/bin/podman run $IMAGE + index := 2 + if info.CreateCommand[1] == "container" { + index = 3 + } + if len(info.CreateCommand) < index+1 { + return "", errors.Errorf("container's create command is too short or invalid: %v", info.CreateCommand) + } + // We're hard-coding the first four arguments and append the + // CreatCommand with a stripped command and subcomand. + command := []string{ + info.Executable, + "run", + "--conmon-pidfile", "/%t/%n-pid", + "--cidfile", "/%t/%n-cid", + } + command = append(command, info.CreateCommand[index:]...) + info.RunCommand = strings.Join(command, " ") + info.New = true + } + if info.PodmanVersion == "" { info.PodmanVersion = version.Version } @@ -131,7 +188,7 @@ func CreateContainerSystemdUnit(info *ContainerInfo, generateFiles bool) (string return "", err } - if !generateFiles { + if !opts.Files { return buf.String(), nil } diff --git a/pkg/systemdgen/systemdgen_test.go b/pkg/systemdgen/systemdgen_test.go index 1ddb0c514..e1da7e8e0 100644 --- a/pkg/systemdgen/systemdgen_test.go +++ b/pkg/systemdgen/systemdgen_test.go @@ -44,9 +44,9 @@ Documentation=man:podman-generate-systemd(1) Restart=always ExecStart=/usr/bin/podman start 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 ExecStop=/usr/bin/podman stop -t 10 639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401 +PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking -PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid [Install] WantedBy=multi-user.target` @@ -62,9 +62,9 @@ Documentation=man:podman-generate-systemd(1) Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar +PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking -PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid [Install] WantedBy=multi-user.target` @@ -84,9 +84,9 @@ After=a.service b.service c.service pod.service Restart=always ExecStart=/usr/bin/podman start foobar ExecStop=/usr/bin/podman stop -t 10 foobar +PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid KillMode=none Type=forking -PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid [Install] WantedBy=multi-user.target` @@ -104,9 +104,29 @@ Before=container-1.service container-2.service Restart=always ExecStart=/usr/bin/podman start jadda-jadda-infra ExecStop=/usr/bin/podman stop -t 10 jadda-jadda-infra +PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid +KillMode=none +Type=forking + +[Install] +WantedBy=multi-user.target` + + goodNameNew := `# jadda-jadda.service +# autogenerated by Podman CI + +[Unit] +Description=Podman jadda-jadda.service +Documentation=man:podman-generate-systemd(1) + +[Service] +Restart=always +ExecStartPre=/usr/bin/rm -f /%t/%n-pid /%t/%n-cid +ExecStart=/usr/bin/podman run --conmon-pidfile /%t/%n-pid --cidfile /%t/%n-cid --name jadda-jadda --hostname hello-world awesome-image:latest command arg1 ... argN +ExecStop=/usr/bin/podman stop --cidfile /%t/%n-cid -t 42 +ExecStopPost=/usr/bin/podman rm -f --cidfile /%t/%n-cid +PIDFile=/%t/%n-pid KillMode=none Type=forking -PIDFile=/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid [Install] WantedBy=multi-user.target` @@ -184,16 +204,35 @@ WantedBy=multi-user.target` "", true, }, + {"good with name and generic", + ContainerInfo{ + Executable: "/usr/bin/podman", + ServiceName: "jadda-jadda", + ContainerName: "jadda-jadda", + RestartPolicy: "always", + PIDFile: "/var/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid", + StopTimeout: 42, + PodmanVersion: "CI", + New: true, + CreateCommand: []string{"I'll get stripped", "container", "run", "--name", "jadda-jadda", "--hostname", "hello-world", "awesome-image:latest", "command", "arg1", "...", "argN"}, + }, + goodNameNew, + false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := CreateContainerSystemdUnit(&tt.info, false) + opts := Options{ + Files: false, + New: tt.info.New, + } + got, err := CreateContainerSystemdUnit(&tt.info, opts) if (err != nil) != tt.wantErr { t.Errorf("CreateContainerSystemdUnit() error = \n%v, wantErr \n%v", err, tt.wantErr) return } if got != tt.want { - t.Errorf("CreateContainerSystemdUnit() = \n%v, want \n%v", got, tt.want) + t.Errorf("CreateContainerSystemdUnit() = \n%v\n---------> want\n%v", got, tt.want) } }) } -- cgit v1.2.3-54-g00ecf