--no-header flag implementation for generate systemd

Signed-off-by: Jakub Guzik <jakubmguzik@gmail.com>
This commit is contained in:
Jakub Guzik
2021-02-22 14:47:08 +01:00
parent 4aaaa6c1df
commit d2f3098c6f
14 changed files with 175 additions and 21 deletions

View File

@ -52,6 +52,7 @@ func init() {
flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
_ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)
flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one")
flags.BoolVarP(&systemdOptions.NoHeader, "no-header", "", false, "Skip header generation")
containerPrefixFlagName := "container-prefix"
flags.StringVar(&systemdOptions.ContainerPrefix, containerPrefixFlagName, "container", "Systemd unit name prefix for containers")

View File

@ -32,6 +32,10 @@ Use the name of the container for the start, stop, and description in the unit f
Using this flag will yield unit files that do not expect containers and pods to exist. Instead, new containers and pods are created based on their configuration files. The unit files are created best effort and may need to be further edited; please review the generated files carefully before using them in production.
#### **--no-header**
Do not generate the header including meta data such as the Podman version and the timestamp.
#### **--time**, **-t**=*value*
Override the default stop timeout for the container with the given value.

View File

@ -18,6 +18,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
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"`
@ -41,6 +42,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
options := entities.GenerateSystemdOptions{
Name: query.Name,
New: query.New,
NoHeader: query.NoHeader,
RestartPolicy: query.RestartPolicy,
StopTimeout: &query.StopTimeout,
ContainerPrefix: query.ContainerPrefix,

View File

@ -32,6 +32,11 @@ func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// default: false
// description: Create a new container instead of starting an existing one.
// - in: query
// name: noHeader
// type: boolean
// default: false
// description: Do not generate the header including the Podman version and the timestamp.
// - in: query
// name: time
// type: integer
// default: 10

View File

@ -14,6 +14,8 @@ type SystemdOptions struct {
UseName *bool
// New - create a new container instead of starting a new one.
New *bool
// NoHeader - Removes autogenerated by Podman and timestamp if set to true
NoHeader *bool
// RestartPolicy - systemd restart policy.
RestartPolicy *string
// StopTimeout - time when stopping the container.

View File

@ -52,6 +52,22 @@ func (o *SystemdOptions) GetNew() bool {
return *o.New
}
// WithNoHeader
func (o *SystemdOptions) WithNoHeader(value bool) *SystemdOptions {
v := &value
o.NoHeader = v
return o
}
// GetNoHeader
func (o *SystemdOptions) GetNoHeader() bool {
var noHeader bool
if o.NoHeader == nil {
return noHeader
}
return *o.NoHeader
}
// WithRestartPolicy
func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions {
v := &value

View File

@ -18,6 +18,8 @@ type GenerateSystemdOptions struct {
PodPrefix string
// Separator - systemd unit name separator between name/id and prefix
Separator string
// NoHeader - skip header generation
NoHeader bool
}
// GenerateSystemdReport

View File

@ -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)
options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader)
options.WithPodPrefix(opts.PodPrefix).WithRestartPolicy(opts.RestartPolicy).WithSeparator(opts.Separator)
if to := opts.StopTimeout; to != nil {
options.WithStopTimeout(*opts.StopTimeout)

View File

@ -31,10 +31,12 @@ func validateRestartPolicy(restart string) error {
}
const headerTemplate = `# {{{{.ServiceName}}}}.service
{{{{- if (eq .GenerateNoHeader false) }}}}
# autogenerated by Podman {{{{.PodmanVersion}}}}
{{{{- if .TimeStamp}}}}
# {{{{.TimeStamp}}}}
{{{{- end}}}}
{{{{- end}}}}
[Unit]
Description=Podman {{{{.ServiceName}}}}.service

View File

@ -65,7 +65,8 @@ type containerInfo struct {
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
@ -292,10 +293,15 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
if info.PodmanVersion == "" {
info.PodmanVersion = version.Version.String()
}
if options.NoHeader {
info.GenerateNoHeader = true
info.GenerateTimestamp = false
}
if info.GenerateTimestamp {
info.TimeStamp = fmt.Sprintf("%v", time.Now().Format(time.UnixDate))
}
// Sort the slices to assure a deterministic output.
sort.Strings(info.BoundToServices)

View File

@ -37,9 +37,11 @@ func TestValidateRestartPolicyContainer(t *testing.T) {
}
func TestCreateContainerSystemdUnit(t *testing.T) {
goodID := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
# autogenerated by Podman CI
serviceInfo := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
`
headerInfo := `# autogenerated by Podman CI
`
goodIDContent := `
[Unit]
Description=Podman container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
Documentation=man:podman-generate-systemd(1)
@ -59,6 +61,8 @@ Type=forking
[Install]
WantedBy=multi-user.target default.target
`
goodID := serviceInfo + headerInfo + goodIDContent
goodIDNoHeaderInfo := serviceInfo + goodIDContent
goodName := `# container-foobar.service
# autogenerated by Podman CI
@ -377,11 +381,12 @@ Type=forking
WantedBy=multi-user.target default.target
`
tests := []struct {
name string
info containerInfo
want string
new bool
wantErr bool
name string
info containerInfo
want string
new bool
noHeader bool
wantErr bool
}{
{"good with id",
@ -398,6 +403,23 @@ WantedBy=multi-user.target default.target
goodID,
false,
false,
false,
},
{"good with noHeader",
containerInfo{
Executable: "/usr/bin/podman",
ServiceName: "container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
ContainerNameOrID: "639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401",
RestartPolicy: "always",
PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
StopTimeout: 22,
PodmanVersion: "CI",
EnvVariable: EnvVariable,
},
goodIDNoHeaderInfo,
false,
true,
false,
},
{"good with name",
containerInfo{
@ -413,6 +435,7 @@ WantedBy=multi-user.target default.target
goodName,
false,
false,
false,
},
{"good with name and bound to",
containerInfo{
@ -429,6 +452,7 @@ WantedBy=multi-user.target default.target
goodNameBoundTo,
false,
false,
false,
},
{"bad restart policy",
containerInfo{
@ -442,6 +466,7 @@ WantedBy=multi-user.target default.target
},
"",
false,
false,
true,
},
{"good with name and generic",
@ -459,6 +484,7 @@ WantedBy=multi-user.target default.target
goodWithNameAndGeneric,
true,
false,
false,
},
{"good with explicit short detach param",
containerInfo{
@ -475,6 +501,7 @@ WantedBy=multi-user.target default.target
goodWithExplicitShortDetachParam,
true,
false,
false,
},
{"good with explicit short detach param and podInfo",
containerInfo{
@ -494,6 +521,7 @@ WantedBy=multi-user.target default.target
goodNameNewWithPodFile,
true,
false,
false,
},
{"good with explicit full detach param",
containerInfo{
@ -510,6 +538,7 @@ WantedBy=multi-user.target default.target
goodNameNewDetach,
true,
false,
false,
},
{"good with id and no param",
containerInfo{
@ -526,6 +555,7 @@ WantedBy=multi-user.target default.target
goodIDNew,
true,
false,
false,
},
{"good with explicit detach=true param",
containerInfo{
@ -542,6 +572,7 @@ WantedBy=multi-user.target default.target
genGoodNewDetach("--detach=true"),
true,
false,
false,
},
{"good with explicit detach=false param",
containerInfo{
@ -558,6 +589,7 @@ WantedBy=multi-user.target default.target
genGoodNewDetach("-d"),
true,
false,
false,
},
{"good with explicit detach=false param",
containerInfo{
@ -574,6 +606,7 @@ WantedBy=multi-user.target default.target
goodNameNewDetachFalseWithCmd,
true,
false,
false,
},
{"good with multiple detach=false params",
containerInfo{
@ -590,6 +623,7 @@ WantedBy=multi-user.target default.target
goodNameNewDetachFalseWithCmd,
true,
false,
false,
},
{"good with multiple shorthand params detach first",
containerInfo{
@ -606,6 +640,7 @@ WantedBy=multi-user.target default.target
genGoodNewDetach("-dti"),
true,
false,
false,
},
{"good with multiple shorthand params detach last",
containerInfo{
@ -622,6 +657,7 @@ WantedBy=multi-user.target default.target
genGoodNewDetach("-tid"),
true,
false,
false,
},
{"good with root flags",
containerInfo{
@ -638,6 +674,7 @@ WantedBy=multi-user.target default.target
goodNewRootFlags,
true,
false,
false,
},
{"good with container create",
containerInfo{
@ -654,6 +691,7 @@ WantedBy=multi-user.target default.target
goodContainerCreate,
true,
false,
false,
},
{"good with journald log tag (see #9034)",
containerInfo{
@ -670,6 +708,7 @@ WantedBy=multi-user.target default.target
goodNewWithJournaldTag,
true,
false,
false,
},
{"good with special chars",
containerInfo{
@ -686,13 +725,15 @@ WantedBy=multi-user.target default.target
goodNewWithSpecialChars,
true,
false,
false,
},
}
for _, tt := range tests {
test := tt
t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{
New: test.new,
New: test.new,
NoHeader: test.noHeader,
}
got, err := executeContainerTemplate(&test.info, opts)
if (err != nil) != test.wantErr {

View File

@ -70,6 +70,8 @@ type podInfo struct {
ExecStop string
// ExecStopPost of the unit.
ExecStopPost string
// Removes autogenerated by Podman and timestamp if set to true
GenerateNoHeader bool
}
const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
@ -319,6 +321,12 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
if info.PodmanVersion == "" {
info.PodmanVersion = version.Version.String()
}
if options.NoHeader {
info.GenerateNoHeader = true
info.GenerateTimestamp = false
}
if info.GenerateTimestamp {
info.TimeStamp = fmt.Sprintf("%v", time.Now().Format(time.UnixDate))
}

View File

@ -37,9 +37,11 @@ func TestValidateRestartPolicyPod(t *testing.T) {
}
func TestCreatePodSystemdUnit(t *testing.T) {
podGood := `# pod-123abc.service
# autogenerated by Podman CI
serviceInfo := `# pod-123abc.service
`
headerInfo := `# autogenerated by Podman CI
`
podContent := `
[Unit]
Description=Podman pod-123abc.service
Documentation=man:podman-generate-systemd(1)
@ -61,6 +63,8 @@ Type=forking
[Install]
WantedBy=multi-user.target default.target
`
podGood := serviceInfo + headerInfo + podContent
podGoodNoHeaderInfo := serviceInfo + podContent
podGoodNamedNew := `# pod-123abc.service
# autogenerated by Podman CI
@ -171,11 +175,12 @@ WantedBy=multi-user.target default.target
`
tests := []struct {
name string
info podInfo
want string
new bool
wantErr bool
name string
info podInfo
want string
new bool
noHeader bool
wantErr bool
}{
{"pod",
podInfo{
@ -192,6 +197,24 @@ WantedBy=multi-user.target default.target
podGood,
false,
false,
false,
},
{"pod noHeader",
podInfo{
Executable: "/usr/bin/podman",
ServiceName: "pod-123abc",
InfraNameOrID: "jadda-jadda-infra",
RestartPolicy: "always",
PIDFile: "/run/containers/storage/overlay-containers/639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401/userdata/conmon.pid",
StopTimeout: 42,
PodmanVersion: "CI",
RequiredServices: []string{"container-1", "container-2"},
CreateCommand: []string{"podman", "pod", "create", "--name", "foo", "bar=arg with space"},
},
podGoodNoHeaderInfo,
false,
true,
false,
},
{"pod with root args",
podInfo{
@ -208,6 +231,7 @@ WantedBy=multi-user.target default.target
podGood,
false,
false,
false,
},
{"pod --new",
podInfo{
@ -224,6 +248,7 @@ WantedBy=multi-user.target default.target
podGoodNamedNew,
true,
false,
false,
},
{"pod --new with root args",
podInfo{
@ -240,6 +265,7 @@ WantedBy=multi-user.target default.target
podGoodNamedNewWithRootArgs,
true,
false,
false,
},
{"pod --new with --replace=false",
podInfo{
@ -256,6 +282,7 @@ WantedBy=multi-user.target default.target
podGoodNamedNewWithReplaceFalse,
true,
false,
false,
},
{"pod --new with double curly braces",
podInfo{
@ -272,6 +299,7 @@ WantedBy=multi-user.target default.target
podNewLabelWithCurlyBraces,
true,
false,
false,
},
}
@ -279,7 +307,8 @@ WantedBy=multi-user.target default.target
test := tt
t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{
New: test.new,
New: test.new,
NoHeader: test.noHeader,
}
got, err := executePodTemplate(&test.info, opts)
if (err != nil) != test.wantErr {

View File

@ -62,6 +62,42 @@ var _ = Describe("Podman generate systemd", func() {
Expect(session.ErrorToString()).To(ContainSubstring("bogus is not a valid restart policy"))
})
It("podman generate systemd with --no-header=true", func() {
session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"generate", "systemd", "foobar", "--no-header=true"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).NotTo(ContainSubstring("autogenerated by"))
})
It("podman generate systemd with --no-header", func() {
session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"generate", "systemd", "foobar", "--no-header"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).NotTo(ContainSubstring("autogenerated by"))
})
It("podman generate systemd with --no-header=false", func() {
session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
session = podmanTest.Podman([]string{"generate", "systemd", "foobar", "--no-header=false"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.OutputToString()).To(ContainSubstring("autogenerated by"))
})
It("podman generate systemd good timeout value", func() {
session := podmanTest.Podman([]string{"create", "--name", "foobar", "alpine", "top"})
session.WaitWithDefaultTimeout()