mirror of https://github.com/containers/podman.git
Support template unit files in podman generate systemd
Signed-off-by: Boaz Shuster <boaz.shuster.github@gmail.com>
This commit is contained in:
parent
c09fab59dd
commit
ece0c7e5d3
|
@ -21,6 +21,7 @@ import (
|
||||||
const (
|
const (
|
||||||
restartPolicyFlagName = "restart-policy"
|
restartPolicyFlagName = "restart-policy"
|
||||||
timeFlagName = "time"
|
timeFlagName = "time"
|
||||||
|
newFlagName = "new"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -53,10 +54,11 @@ func init() {
|
||||||
flags := systemdCmd.Flags()
|
flags := systemdCmd.Flags()
|
||||||
flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs")
|
flags.BoolVarP(&systemdOptions.Name, "name", "n", false, "Use container/pod names instead of IDs")
|
||||||
flags.BoolVarP(&files, "files", "f", false, "Generate .service files instead of printing to stdout")
|
flags.BoolVarP(&files, "files", "f", false, "Generate .service files instead of printing to stdout")
|
||||||
|
flags.BoolVar(&systemdOptions.TemplateUnitFile, "template", false, "Make it a template file and use %i and %I specifiers. Working only for containers")
|
||||||
|
|
||||||
flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
|
flags.UintVarP(&systemdTimeout, timeFlagName, "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
|
||||||
_ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)
|
_ = systemdCmd.RegisterFlagCompletionFunc(timeFlagName, completion.AutocompleteNone)
|
||||||
flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container or pod instead of starting an existing one")
|
flags.BoolVar(&systemdOptions.New, newFlagName, false, "Create a new container or pod instead of starting an existing one")
|
||||||
flags.BoolVarP(&systemdOptions.NoHeader, "no-header", "", false, "Skip header generation")
|
flags.BoolVarP(&systemdOptions.NoHeader, "no-header", "", false, "Skip header generation")
|
||||||
|
|
||||||
containerPrefixFlagName := "container-prefix"
|
containerPrefixFlagName := "container-prefix"
|
||||||
|
@ -93,6 +95,13 @@ func systemd(cmd *cobra.Command, args []string) error {
|
||||||
logrus.Warnln("The generated units should be placed on your remote system")
|
logrus.Warnln("The generated units should be placed on your remote system")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed(newFlagName) && !systemdOptions.New && systemdOptions.TemplateUnitFile {
|
||||||
|
return errors.New("--template cannot be set with --new=false")
|
||||||
|
}
|
||||||
|
if !systemdOptions.New && systemdOptions.TemplateUnitFile {
|
||||||
|
systemdOptions.New = true
|
||||||
|
}
|
||||||
|
|
||||||
reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
|
reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -59,6 +59,12 @@ Set the systemd unit name prefix for pods. The default is *pod*.
|
||||||
|
|
||||||
Set the systemd unit name separator between the name/id of a container/pod and the prefix. The default is *-*.
|
Set the systemd unit name separator between the name/id of a container/pod and the prefix. The default is *-*.
|
||||||
|
|
||||||
|
#### **--template**
|
||||||
|
|
||||||
|
Add template specifiers to run multiple services from the systemd unit file.
|
||||||
|
|
||||||
|
Note that if `--new` was not set to true, it is set to true by default. However, if `--new` is set to `false` explicitly the command will fail.
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
### Generate and print a systemd unit file for a container
|
### Generate and print a systemd unit file for a container
|
||||||
|
|
|
@ -20,6 +20,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
|
||||||
Name bool `schema:"useName"`
|
Name bool `schema:"useName"`
|
||||||
New bool `schema:"new"`
|
New bool `schema:"new"`
|
||||||
NoHeader bool `schema:"noHeader"`
|
NoHeader bool `schema:"noHeader"`
|
||||||
|
TemplateUnitFile bool `schema:"templateUnitFile"`
|
||||||
RestartPolicy *string `schema:"restartPolicy"`
|
RestartPolicy *string `schema:"restartPolicy"`
|
||||||
StopTimeout uint `schema:"stopTimeout"`
|
StopTimeout uint `schema:"stopTimeout"`
|
||||||
ContainerPrefix string `schema:"containerPrefix"`
|
ContainerPrefix string `schema:"containerPrefix"`
|
||||||
|
@ -43,6 +44,7 @@ func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
|
||||||
Name: query.Name,
|
Name: query.Name,
|
||||||
New: query.New,
|
New: query.New,
|
||||||
NoHeader: query.NoHeader,
|
NoHeader: query.NoHeader,
|
||||||
|
TemplateUnitFile: query.TemplateUnitFile,
|
||||||
RestartPolicy: query.RestartPolicy,
|
RestartPolicy: query.RestartPolicy,
|
||||||
StopTimeout: &query.StopTimeout,
|
StopTimeout: &query.StopTimeout,
|
||||||
ContainerPrefix: query.ContainerPrefix,
|
ContainerPrefix: query.ContainerPrefix,
|
||||||
|
|
|
@ -16,6 +16,8 @@ type SystemdOptions struct {
|
||||||
New *bool
|
New *bool
|
||||||
// NoHeader - Removes autogenerated by Podman and timestamp if set to true
|
// NoHeader - Removes autogenerated by Podman and timestamp if set to true
|
||||||
NoHeader *bool
|
NoHeader *bool
|
||||||
|
// TemplateUnitFile - Create a template unit file that uses the identity specifiers
|
||||||
|
TemplateUnitFile *bool
|
||||||
// RestartPolicy - systemd restart policy.
|
// RestartPolicy - systemd restart policy.
|
||||||
RestartPolicy *string
|
RestartPolicy *string
|
||||||
// StopTimeout - time when stopping the container.
|
// StopTimeout - time when stopping the container.
|
||||||
|
|
|
@ -62,6 +62,21 @@ func (o *SystemdOptions) GetNoHeader() bool {
|
||||||
return *o.NoHeader
|
return *o.NoHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTemplateUnitFile set field TemplateUnitFile to given value
|
||||||
|
func (o *SystemdOptions) WithTemplateUnitFile(value bool) *SystemdOptions {
|
||||||
|
o.TemplateUnitFile = &value
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTemplateUnitFile returns value of field TemplateUnitFile
|
||||||
|
func (o *SystemdOptions) GetTemplateUnitFile() bool {
|
||||||
|
if o.TemplateUnitFile == nil {
|
||||||
|
var z bool
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
return *o.TemplateUnitFile
|
||||||
|
}
|
||||||
|
|
||||||
// WithRestartPolicy set field RestartPolicy to given value
|
// WithRestartPolicy set field RestartPolicy to given value
|
||||||
func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions {
|
func (o *SystemdOptions) WithRestartPolicy(value string) *SystemdOptions {
|
||||||
o.RestartPolicy = &value
|
o.RestartPolicy = &value
|
||||||
|
|
|
@ -20,6 +20,8 @@ type GenerateSystemdOptions struct {
|
||||||
Separator string
|
Separator string
|
||||||
// NoHeader - skip header generation
|
// NoHeader - skip header generation
|
||||||
NoHeader bool
|
NoHeader bool
|
||||||
|
// TemplateUnitFile - make use of %i and %I to differentiate between the different instances of the unit
|
||||||
|
TemplateUnitFile bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GenerateSystemdReport
|
// GenerateSystemdReport
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, opts entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
|
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).WithNoHeader(opts.NoHeader)
|
options := new(generate.SystemdOptions).WithUseName(opts.Name).WithContainerPrefix(opts.ContainerPrefix).WithNew(opts.New).WithNoHeader(opts.NoHeader).WithTemplateUnitFile(opts.TemplateUnitFile)
|
||||||
options.WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator)
|
options.WithPodPrefix(opts.PodPrefix).WithSeparator(opts.Separator)
|
||||||
if opts.RestartPolicy != nil {
|
if opts.RestartPolicy != nil {
|
||||||
options.WithRestartPolicy(*opts.RestartPolicy)
|
options.WithRestartPolicy(*opts.RestartPolicy)
|
||||||
|
|
|
@ -23,7 +23,7 @@ func validateRestartPolicy(restart string) error {
|
||||||
return errors.Errorf("%s is not a valid restart policy", restart)
|
return errors.Errorf("%s is not a valid restart policy", restart)
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerTemplate = `# {{{{.ServiceName}}}}.service
|
const headerTemplate = `# {{{{.ServiceName}}}}{{{{- if (eq .IdentifySpecifier true) }}}}@{{{{- end}}}}.service
|
||||||
{{{{- if (eq .GenerateNoHeader false) }}}}
|
{{{{- if (eq .GenerateNoHeader false) }}}}
|
||||||
# autogenerated by Podman {{{{.PodmanVersion}}}}
|
# autogenerated by Podman {{{{.PodmanVersion}}}}
|
||||||
{{{{- if .TimeStamp}}}}
|
{{{{- if .TimeStamp}}}}
|
||||||
|
@ -32,7 +32,7 @@ const headerTemplate = `# {{{{.ServiceName}}}}.service
|
||||||
{{{{- end}}}}
|
{{{{- end}}}}
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Podman {{{{.ServiceName}}}}.service
|
Description=Podman {{{{.ServiceName}}}}.service{{{{- if (eq .IdentifySpecifier true) }}}} for %I{{{{- end}}}}
|
||||||
Documentation=man:podman-generate-systemd(1)
|
Documentation=man:podman-generate-systemd(1)
|
||||||
Wants=network-online.target
|
Wants=network-online.target
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
|
|
|
@ -90,6 +90,8 @@ type containerInfo struct {
|
||||||
// Location of the RunRoot for the container. Required for ensuring the tmpfs
|
// Location of the RunRoot for the container. Required for ensuring the tmpfs
|
||||||
// or volume exists and is mounted when coming online at boot.
|
// or volume exists and is mounted when coming online at boot.
|
||||||
RunRoot string
|
RunRoot string
|
||||||
|
// Add %i and %I to description and execute parts
|
||||||
|
IdentifySpecifier bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerTemplate = headerTemplate + `
|
const containerTemplate = headerTemplate + `
|
||||||
|
@ -99,7 +101,7 @@ After={{{{- range $index, $value := .BoundToServices -}}}}{{{{if $index}}}} {{{{
|
||||||
{{{{- end}}}}
|
{{{{- end}}}}
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Environment={{{{.EnvVariable}}}}=%n
|
Environment={{{{.EnvVariable}}}}=%n{{{{- if (eq .IdentifySpecifier true) }}}}-%i{{{{- end}}}}
|
||||||
{{{{- if .ExtraEnvs}}}}
|
{{{{- if .ExtraEnvs}}}}
|
||||||
Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}}
|
Environment={{{{- range $index, $value := .ExtraEnvs -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}{{{{end}}}}
|
||||||
{{{{- end}}}}
|
{{{{- end}}}}
|
||||||
|
@ -273,7 +275,6 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
||||||
"--rm",
|
"--rm",
|
||||||
)
|
)
|
||||||
remainingCmd := info.CreateCommand[index:]
|
remainingCmd := info.CreateCommand[index:]
|
||||||
|
|
||||||
// Presence check for certain flags/options.
|
// Presence check for certain flags/options.
|
||||||
fs := pflag.NewFlagSet("args", pflag.ContinueOnError)
|
fs := pflag.NewFlagSet("args", pflag.ContinueOnError)
|
||||||
fs.ParseErrorsWhitelist.UnknownFlags = true
|
fs.ParseErrorsWhitelist.UnknownFlags = true
|
||||||
|
@ -389,6 +390,32 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
|
||||||
|
|
||||||
startCommand = append(startCommand, remainingCmd...)
|
startCommand = append(startCommand, remainingCmd...)
|
||||||
startCommand = escapeSystemdArguments(startCommand)
|
startCommand = escapeSystemdArguments(startCommand)
|
||||||
|
if options.TemplateUnitFile {
|
||||||
|
info.IdentifySpecifier = true
|
||||||
|
runIx := -1
|
||||||
|
nameIx := -1
|
||||||
|
for argIx, arg := range startCommand {
|
||||||
|
if arg == "run" {
|
||||||
|
runIx = argIx
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if arg == "--name" {
|
||||||
|
nameIx = argIx + 1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(arg, "--name=") {
|
||||||
|
nameIx = argIx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nameIx == -1 {
|
||||||
|
startCommand = append(startCommand[:runIx+1], startCommand[runIx:]...)
|
||||||
|
startCommand[runIx+1] = fmt.Sprintf("--name=%s-%%i", info.ServiceName)
|
||||||
|
fmt.Println(startCommand)
|
||||||
|
} else {
|
||||||
|
startCommand[nameIx] = fmt.Sprintf("%s-%%i", startCommand[nameIx])
|
||||||
|
}
|
||||||
|
}
|
||||||
info.ExecStart = strings.Join(startCommand, " ")
|
info.ExecStart = strings.Join(startCommand, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,8 @@ type podInfo struct {
|
||||||
// Location of the RunRoot for the pod. Required for ensuring the tmpfs
|
// Location of the RunRoot for the pod. Required for ensuring the tmpfs
|
||||||
// or volume exists and is mounted when coming online at boot.
|
// or volume exists and is mounted when coming online at boot.
|
||||||
RunRoot string
|
RunRoot string
|
||||||
|
// Add %i and %I to description and execute parts - this should not be used
|
||||||
|
IdentifySpecifier bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
const podTemplate = headerTemplate + `Requires={{{{- range $index, $value := .RequiredServices -}}}}{{{{if $index}}}} {{{{end}}}}{{{{ $value }}}}.service{{{{end}}}}
|
||||||
|
@ -108,6 +110,9 @@ WantedBy=multi-user.target default.target
|
||||||
// Based on the options, the return value might be the content of all units or
|
// Based on the options, the return value might be the content of all units or
|
||||||
// the files they been written to.
|
// the files they been written to.
|
||||||
func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) {
|
func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) {
|
||||||
|
if options.TemplateUnitFile {
|
||||||
|
return nil, errors.New("--template is not supported for pods")
|
||||||
|
}
|
||||||
// Error out if the pod has no infra container, which we require to be the
|
// Error out if the pod has no infra container, which we require to be the
|
||||||
// main service.
|
// main service.
|
||||||
if !pod.HasInfraContainer() {
|
if !pod.HasInfraContainer() {
|
||||||
|
|
|
@ -201,4 +201,33 @@ LISTEN_FDNAMES=listen_fdnames" "LISTEN Environment passed: $context"
|
||||||
check_listen_env "$stdenv" "podman start"
|
check_listen_env "$stdenv" "podman start"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@test "podman generate - systemd template" {
|
||||||
|
cname=$(random_string)
|
||||||
|
run_podman run -dt --name $cname $IMAGE top
|
||||||
|
|
||||||
|
run_podman generate systemd --template -n $cname
|
||||||
|
is "$output" ".*%I.*" "%I indentifiers in template"
|
||||||
|
is "$output" ".*%i.*" "%i indentifiers in template"
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "podman generate - systemd template no support for pod" {
|
||||||
|
cname=$(random_string)
|
||||||
|
podname=$(random_string)
|
||||||
|
run_podman pod create --name $podname
|
||||||
|
run_podman run --pod $podname -dt --name $cname $IMAGE top
|
||||||
|
|
||||||
|
run_podman 125 generate systemd --new --template -n $podname
|
||||||
|
is "$output" ".*--template is not supported for pods.*" "Error message contains 'not supported'"
|
||||||
|
|
||||||
|
run_podman rm -f $cname
|
||||||
|
run_podman pod rm -f $podname
|
||||||
|
}
|
||||||
|
|
||||||
|
@test "podman generate - systemd template only used on --new" {
|
||||||
|
cname=$(random_string)
|
||||||
|
run_podman create --name $cname $IMAGE top
|
||||||
|
run_podman 125 generate systemd --new=false --template -n $cname
|
||||||
|
is "$output" ".*--template cannot be set" "Error message should be '--template requires --new'"
|
||||||
|
}
|
||||||
|
|
||||||
# vim: filetype=sh
|
# vim: filetype=sh
|
||||||
|
|
Loading…
Reference in New Issue