package generate import ( "encoding/json" "errors" "fmt" "os" "path/filepath" "github.com/containers/common/pkg/completion" "github.com/containers/common/pkg/report" "github.com/containers/podman/v4/cmd/podman/common" "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" ) const ( startTimeoutFlagName = "start-timeout" stopTimeoutFlagName = "stop-timeout" stopTimeoutCompatFlagName = "time" restartPolicyFlagName = "restart-policy" restartSecFlagName = "restart-sec" newFlagName = "new" wantsFlagName = "wants" afterFlagName = "after" requiresFlagName = "requires" envFlagName = "env" ) var ( envInput []string files bool format string systemdRestart string systemdRestartSec uint startTimeout uint stopTimeout uint systemdOptions = entities.GenerateSystemdOptions{} systemdDescription = `Generate systemd units for a pod or container. The generated units can later be controlled via systemctl(1).` systemdCmd = &cobra.Command{ Use: "systemd [options] {CONTAINER|POD}", Short: "Generate systemd units.", Long: systemdDescription, RunE: systemd, Args: cobra.ExactArgs(1), ValidArgsFunction: common.AutocompleteContainersAndPods, Example: `podman generate systemd CTR podman generate systemd --new --time 10 CTR podman generate systemd --files --name POD`, } ) func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ Command: systemdCmd, Parent: GenerateCmd, }) flags := systemdCmd.Flags() 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.BoolVar(&systemdOptions.TemplateUnitFile, "template", false, "Make it a template file and use %i and %I specifiers. Working only for containers") flags.UintVarP(&startTimeout, startTimeoutFlagName, "", 0, "Start timeout override") _ = systemdCmd.RegisterFlagCompletionFunc(startTimeoutFlagName, completion.AutocompleteNone) // NOTE: initially, there was only a --time/-t flag which mapped to // stop-timeout. To remain backwards compatible create a hidden flag // that maps to StopTimeout. flags.UintVarP(&stopTimeout, stopTimeoutFlagName, "", containerConfig.Engine.StopTimeout, "Stop timeout override") _ = systemdCmd.RegisterFlagCompletionFunc(stopTimeoutFlagName, completion.AutocompleteNone) flags.UintVarP(&stopTimeout, stopTimeoutCompatFlagName, "t", containerConfig.Engine.StopTimeout, "Backwards alias for --stop-timeout") _ = flags.MarkHidden("time") 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") containerPrefixFlagName := "container-prefix" flags.StringVar(&systemdOptions.ContainerPrefix, containerPrefixFlagName, "container", "Systemd unit name prefix for containers") _ = systemdCmd.RegisterFlagCompletionFunc(containerPrefixFlagName, completion.AutocompleteNone) podPrefixFlagName := "pod-prefix" flags.StringVar(&systemdOptions.PodPrefix, podPrefixFlagName, "pod", "Systemd unit name prefix for pods") _ = systemdCmd.RegisterFlagCompletionFunc(podPrefixFlagName, completion.AutocompleteNone) separatorFlagName := "separator" flags.StringVar(&systemdOptions.Separator, separatorFlagName, "-", "Systemd unit name separator between name/id and prefix") _ = systemdCmd.RegisterFlagCompletionFunc(separatorFlagName, completion.AutocompleteNone) flags.StringVar(&systemdRestart, restartPolicyFlagName, systemDefine.DefaultRestartPolicy, "Systemd restart-policy") _ = systemdCmd.RegisterFlagCompletionFunc(restartPolicyFlagName, common.AutocompleteSystemdRestartOptions) flags.UintVarP(&systemdRestartSec, restartSecFlagName, "", 0, "Systemd restart-sec") _ = systemdCmd.RegisterFlagCompletionFunc(restartSecFlagName, completion.AutocompleteNone) formatFlagName := "format" flags.StringVar(&format, formatFlagName, "", "Print the created units in specified format (json)") _ = systemdCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil)) flags.StringArrayVar(&systemdOptions.Wants, wantsFlagName, nil, "Add (weak) requirement dependencies to the generated unit file") _ = systemdCmd.RegisterFlagCompletionFunc(wantsFlagName, completion.AutocompleteNone) flags.StringArrayVar(&systemdOptions.After, afterFlagName, nil, "Add dependencies order to the generated unit file") _ = systemdCmd.RegisterFlagCompletionFunc(afterFlagName, completion.AutocompleteNone) 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) } func systemd(cmd *cobra.Command, args []string) error { if cmd.Flags().Changed(restartPolicyFlagName) { systemdOptions.RestartPolicy = &systemdRestart } if registry.IsRemote() { 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 } if cmd.Flags().Changed(restartSecFlagName) { systemdOptions.RestartSec = &systemdRestartSec } if cmd.Flags().Changed(startTimeoutFlagName) { systemdOptions.StartTimeout = &startTimeout } setStopTimeout := 0 if cmd.Flags().Changed(stopTimeoutFlagName) { setStopTimeout++ } if cmd.Flags().Changed(stopTimeoutCompatFlagName) { setStopTimeout++ } if cmd.Flags().Changed(envFlagName) { cliEnv, err := envLib.ParseSlice(envInput) if err != nil { return fmt.Errorf("parsing environment variables: %w", err) } systemdOptions.AdditionalEnvVariables = envLib.Slice(cliEnv) } switch setStopTimeout { case 1: systemdOptions.StopTimeout = &stopTimeout case 2: return fmt.Errorf("%s and %s are redundant and cannot be used together", stopTimeoutFlagName, stopTimeoutCompatFlagName) } reports, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) if err != nil { return err } if files { cwd, err := os.Getwd() if err != nil { return fmt.Errorf("getting current working directory: %w", err) } for name, content := range reports.Units { path := filepath.Join(cwd, fmt.Sprintf("%s.service", name)) f, err := os.Create(path) if err != nil { return err } _, err = f.WriteString(content) if err != nil { return err } err = f.Close() if err != nil { return err } // add newline if default format is given if format == "" { path += "\n" } // modify in place so we can print the // paths when --files is set reports.Units[name] = path } } switch { case report.IsJSON(format): return printJSON(reports.Units) case format == "": return printDefault(reports.Units) default: return fmt.Errorf("unknown --format argument: %s", format) } } func printDefault(units map[string]string) error { for _, content := range units { fmt.Print(content) } return nil } func printJSON(units map[string]string) error { b, err := json.MarshalIndent(units, "", " ") if err != nil { return err } fmt.Println(string(b)) return nil }