APIv2 add generate systemd endpoint

Add support for generating systemd units
via the api and podman-remote.

Change the GenerateSystemdReport type to return the
units as map[string]string with the unit name as key.

Add `--format` flag to `podman generate systemd`
to allow the output to be formatted as json.

Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
Paul Holzinger
2020-08-05 09:29:59 +02:00
parent 1184cdf03d
commit ebfea2f4f8
13 changed files with 275 additions and 84 deletions

View File

@ -1,15 +1,22 @@
package pods package pods
import ( import (
"encoding/json"
"fmt" "fmt"
"os"
"path/filepath"
"github.com/containers/podman/v2/cmd/podman/registry" "github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils" "github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var (
files bool
format string
systemdTimeout uint systemdTimeout uint
systemdOptions = entities.GenerateSystemdOptions{} systemdOptions = entities.GenerateSystemdOptions{}
systemdDescription = `Generate systemd units for a pod or container. systemdDescription = `Generate systemd units for a pod or container.
@ -29,19 +36,20 @@ var (
func init() { func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{ registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode}, Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: systemdCmd, Command: systemdCmd,
Parent: generateCmd, Parent: generateCmd,
}) })
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(&systemdOptions.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.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override") flags.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy") flags.StringVar(&systemdOptions.RestartPolicy, "restart-policy", "on-failure", "Systemd restart-policy")
flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one") flags.BoolVarP(&systemdOptions.New, "new", "", false, "Create a new container instead of starting an existing one")
flags.StringVar(&systemdOptions.ContainerPrefix, "container-prefix", "container", "Systemd unit name prefix for containers") flags.StringVar(&systemdOptions.ContainerPrefix, "container-prefix", "container", "Systemd unit name prefix for containers")
flags.StringVar(&systemdOptions.PodPrefix, "pod-prefix", "pod", "Systemd unit name prefix for pods") flags.StringVar(&systemdOptions.PodPrefix, "pod-prefix", "pod", "Systemd unit name prefix for pods")
flags.StringVar(&systemdOptions.Separator, "separator", "-", "Systemd unit name separator between name/id and prefix") flags.StringVar(&systemdOptions.Separator, "separator", "-", "Systemd unit name separator between name/id and prefix")
flags.StringVar(&format, "format", "", "Print the created units in specified format (json)")
flags.SetNormalizeFunc(utils.AliasFlags) flags.SetNormalizeFunc(utils.AliasFlags)
} }
@ -50,11 +58,68 @@ func systemd(cmd *cobra.Command, args []string) error {
systemdOptions.StopTimeout = &systemdTimeout systemdOptions.StopTimeout = &systemdTimeout
} }
if registry.IsRemote() {
logrus.Warnln("The generated units should be placed on your remote system")
}
report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions) report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
if err != nil { if err != nil {
return err return err
} }
fmt.Println(report.Output) if files {
cwd, err := os.Getwd()
if err != nil {
return errors.Wrap(err, "error getting current working directory")
}
for name, content := range report.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
report.Units[name] = path
}
}
switch format {
case "json":
return printJSON(report.Units)
case "":
return printDefault(report.Units)
default:
return errors.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 return nil
} }

View File

@ -10,7 +10,7 @@ podman\-generate\-systemd - Generate systemd unit file(s) for a container or pod
**podman generate systemd** will create a systemd unit file that can be used to control a container or pod. **podman generate systemd** will create a systemd unit file that can be used to control a container or pod.
By default, the command will print the content of the unit files to stdout. By default, the command will print the content of the unit files to stdout.
Note that this command is not supported for the remote client. _Note: If you use this command with the remote client, you would still have to place the generated units on the remote system._
## OPTIONS: ## OPTIONS:
@ -20,6 +20,10 @@ Generate files instead of printing to stdout. The generated files are named {co
Note: On a system with SELinux enabled, the generated files will inherit contexts from the current working directory. Depending on the SELinux setup, changes to the generated files using `restorecon`, `chcon`, or `semanage` may be required to allow systemd to access these files. Alternatively, use the `-Z` option when running `mv` or `cp`. Note: On a system with SELinux enabled, the generated files will inherit contexts from the current working directory. Depending on the SELinux setup, changes to the generated files using `restorecon`, `chcon`, or `semanage` may be required to allow systemd to access these files. Alternatively, use the `-Z` option when running `mv` or `cp`.
**--format**=*format*
Print the created units in specified format (json). If `--files` is specified the paths to the created files will be printed instead of the unit content.
**--name**, **-n** **--name**, **-n**
Use the name of the container for the start, stop, and description in the unit file Use the name of the container for the start, stop, and description in the unit file

View File

@ -7,10 +7,55 @@ import (
"github.com/containers/podman/v2/pkg/api/handlers/utils" "github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi" "github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/containers/podman/v2/pkg/util"
"github.com/gorilla/schema" "github.com/gorilla/schema"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func GenerateSystemd(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Name bool `schema:"useName"`
New bool `schema:"new"`
RestartPolicy string `schema:"restartPolicy"`
StopTimeout uint `schema:"stopTimeout"`
ContainerPrefix string `schema:"containerPrefix"`
PodPrefix string `schema:"podPrefix"`
Separator string `schema:"separator"`
}{
RestartPolicy: "on-failure",
StopTimeout: util.DefaultContainerConfig().Engine.StopTimeout,
ContainerPrefix: "container",
PodPrefix: "pod",
Separator: "-",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest,
errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
return
}
containerEngine := abi.ContainerEngine{Libpod: runtime}
options := entities.GenerateSystemdOptions{
Name: query.Name,
New: query.New,
RestartPolicy: query.RestartPolicy,
StopTimeout: &query.StopTimeout,
ContainerPrefix: query.ContainerPrefix,
PodPrefix: query.PodPrefix,
Separator: query.Separator,
}
report, err := containerEngine.GenerateSystemd(r.Context(), utils.GetName(r), options)
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "error generating systemd units"))
return
}
utils.WriteResponse(w, http.StatusOK, report.Units)
}
func GenerateKube(w http.ResponseWriter, r *http.Request) { func GenerateKube(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime) runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder) decoder := r.Context().Value("decoder").(*schema.Decoder)

View File

@ -8,6 +8,68 @@ import (
) )
func (s *APIServer) registerGenerateHandlers(r *mux.Router) error { func (s *APIServer) registerGenerateHandlers(r *mux.Router) error {
// swagger:operation GET /libpod/generate/{name:.*}/systemd libpod libpodGenerateSystemd
// ---
// tags:
// - containers
// - pods
// summary: Generate Systemd Units
// description: Generate Systemd Units based on a pod or container.
// parameters:
// - in: path
// name: name:.*
// type: string
// required: true
// description: Name or ID of the container or pod.
// - in: query
// name: useName
// type: boolean
// default: false
// description: Use container/pod names instead of IDs.
// - in: query
// name: new
// type: boolean
// default: false
// description: Create a new container instead of starting an existing one.
// - in: query
// name: time
// type: integer
// default: 10
// description: Stop timeout override.
// - in: query
// name: restartPolicy
// default: on-failure
// type: string
// enum: ["no", on-success, on-failure, on-abnormal, on-watchdog, on-abort, always]
// description: Systemd restart-policy.
// - in: query
// name: containerPrefix
// type: string
// default: container
// description: Systemd unit name prefix for containers.
// - in: query
// name: podPrefix
// type: string
// default: pod
// description: Systemd unit name prefix for pods.
// - in: query
// name: separator
// type: string
// default: "-"
// description: Systemd unit name separator between name/id and prefix.
// produces:
// - application/json
// responses:
// 200:
// description: no error
// schema:
// type: object
// additionalProperties:
// type: string
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/generate/{name:.*}/systemd"), s.APIHandler(libpod.GenerateSystemd)).Methods(http.MethodGet)
// swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube // swagger:operation GET /libpod/generate/{name:.*}/kube libpod libpodGenerateKube
// --- // ---
// tags: // tags:

View File

@ -10,6 +10,33 @@ import (
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
) )
func Systemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
conn, err := bindings.GetClient(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
params.Set("useName", strconv.FormatBool(options.Name))
params.Set("new", strconv.FormatBool(options.New))
if options.RestartPolicy != "" {
params.Set("restartPolicy", options.RestartPolicy)
}
if options.StopTimeout != nil {
params.Set("stopTimeout", strconv.FormatUint(uint64(*options.StopTimeout), 10))
}
params.Set("containerPrefix", options.ContainerPrefix)
params.Set("podPrefix", options.PodPrefix)
params.Set("separator", options.Separator)
response, err := conn.DoRequest(nil, http.MethodGet, "/generate/%s/systemd", params, nil, nameOrID)
if err != nil {
return nil, err
}
report := &entities.GenerateSystemdReport{}
return report, response.Process(&report.Units)
}
func Kube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { func Kube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {
conn, err := bindings.GetClient(ctx) conn, err := bindings.GetClient(ctx)
if err != nil { if err != nil {

View File

@ -4,8 +4,6 @@ import "io"
// GenerateSystemdOptions control the generation of systemd unit files. // GenerateSystemdOptions control the generation of systemd unit files.
type GenerateSystemdOptions struct { type GenerateSystemdOptions struct {
// Files - generate files instead of printing to stdout.
Files bool
// Name - use container/pod name instead of its ID. // Name - use container/pod name instead of its ID.
Name bool Name bool
// New - create a new container instead of starting a new one. // New - create a new container instead of starting a new one.
@ -24,9 +22,8 @@ type GenerateSystemdOptions struct {
// GenerateSystemdReport // GenerateSystemdReport
type GenerateSystemdReport struct { type GenerateSystemdReport struct {
// Output of the generate process. Either the generated files or their // Units of the generate process. key = unit name -> value = unit content
// entire content. Units map[string]string
Output string
} }
// GenerateKubeOptions control the generation of Kubernetes YAML files. // GenerateKubeOptions control the generation of Kubernetes YAML files.

View File

@ -19,11 +19,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
ctr, ctrErr := ic.Libpod.LookupContainer(nameOrID) ctr, ctrErr := ic.Libpod.LookupContainer(nameOrID)
if ctrErr == nil { if ctrErr == nil {
// Generate the unit for the container. // Generate the unit for the container.
s, err := generate.ContainerUnit(ctr, options) name, content, err := generate.ContainerUnit(ctr, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &entities.GenerateSystemdReport{Output: s}, nil return &entities.GenerateSystemdReport{Units: map[string]string{name: content}}, nil
} }
// If it's not a container, we either have a pod or garbage. // If it's not a container, we either have a pod or garbage.
@ -34,11 +34,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
} }
// Generate the units for the pod and all its containers. // Generate the units for the pod and all its containers.
s, err := generate.PodUnits(pod, options) units, err := generate.PodUnits(pod, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &entities.GenerateSystemdReport{Output: s}, nil return &entities.GenerateSystemdReport{Units: units}, nil
} }
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {

View File

@ -5,11 +5,10 @@ import (
"github.com/containers/podman/v2/pkg/bindings/generate" "github.com/containers/podman/v2/pkg/bindings/generate"
"github.com/containers/podman/v2/pkg/domain/entities" "github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
) )
func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) { func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
return nil, errors.New("not implemented for tunnel") return generate.Systemd(ic.ClientCxt, nameOrID, options)
} }
func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) { func (ic *ContainerEngine) GenerateKube(ctx context.Context, nameOrID string, options entities.GenerateKubeOptions) (*entities.GenerateKubeReport, error) {

View File

@ -3,9 +3,7 @@ package generate
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"text/template" "text/template"
@ -87,17 +85,22 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
// ContainerUnit generates a systemd unit for the specified container. Based // ContainerUnit generates a systemd unit for the specified container. Based
// on the options, the return value might be the entire unit or a file it has // on the options, the return value might be the entire unit or a file it has
// been written to. // been written to.
func ContainerUnit(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, error) { func ContainerUnit(ctr *libpod.Container, options entities.GenerateSystemdOptions) (string, string, error) {
info, err := generateContainerInfo(ctr, options) info, err := generateContainerInfo(ctr, options)
if err != nil { if err != nil {
return "", err return "", "", err
} }
return executeContainerTemplate(info, options) content, err := executeContainerTemplate(info, options)
if err != nil {
return "", "", err
}
return info.ServiceName, content, nil
} }
func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSystemdOptions) (*containerInfo, error) { func generateContainerInfo(ctr *libpod.Container, options entities.GenerateSystemdOptions) (*containerInfo, error) {
@ -288,18 +291,5 @@ func executeContainerTemplate(info *containerInfo, options entities.GenerateSyst
return "", err return "", err
} }
if !options.Files {
return buf.String(), nil return buf.String(), nil
}
buf.WriteByte('\n')
cwd, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "error getting current working directory")
}
path := filepath.Join(cwd, fmt.Sprintf("%s.service", info.ServiceName))
if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil {
return "", errors.Wrap(err, "error generating systemd unit")
}
return path, nil
} }

View File

@ -56,7 +56,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodName := `# container-foobar.service goodName := `# container-foobar.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -78,7 +79,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodNameBoundTo := `# container-foobar.service goodNameBoundTo := `# container-foobar.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -102,7 +104,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodWithNameAndGeneric := `# jadda-jadda.service goodWithNameAndGeneric := `# jadda-jadda.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -125,7 +128,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodWithExplicitShortDetachParam := `# jadda-jadda.service goodWithExplicitShortDetachParam := `# jadda-jadda.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -148,7 +152,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodNameNewWithPodFile := `# jadda-jadda.service goodNameNewWithPodFile := `# jadda-jadda.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -171,7 +176,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodNameNewDetach := `# jadda-jadda.service goodNameNewDetach := `# jadda-jadda.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -194,7 +200,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
goodIDNew := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service goodIDNew := `# container-639c53578af4d84b8800b4635fa4e680ee80fd67e0e6a2d4eea48d1e3230f401.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -217,7 +224,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
tests := []struct { tests := []struct {
name string name string
@ -375,7 +383,6 @@ WantedBy=multi-user.target default.target`
test := tt test := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{ opts := entities.GenerateSystemdOptions{
Files: false,
New: test.new, New: test.new,
} }
got, err := executeContainerTemplate(&test.info, opts) got, err := executeContainerTemplate(&test.info, opts)

View File

@ -3,9 +3,7 @@ package generate
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
"text/template" "text/template"
@ -88,39 +86,40 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
// PodUnits generates systemd units for the specified pod and its containers. // PodUnits generates systemd units for the specified pod and its containers.
// 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) (string, error) { func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (map[string]string, error) {
// 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() {
return "", errors.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name()) return nil, errors.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
} }
podInfo, err := generatePodInfo(pod, options) podInfo, err := generatePodInfo(pod, options)
if err != nil { if err != nil {
return "", err return nil, err
} }
infraID, err := pod.InfraContainerID() infraID, err := pod.InfraContainerID()
if err != nil { if err != nil {
return "", err return nil, err
} }
// Compute the container-dependency graph for the Pod. // Compute the container-dependency graph for the Pod.
containers, err := pod.AllContainers() containers, err := pod.AllContainers()
if err != nil { if err != nil {
return "", err return nil, err
} }
if len(containers) == 0 { if len(containers) == 0 {
return "", errors.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name()) return nil, errors.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
} }
graph, err := libpod.BuildContainerGraph(containers) graph, err := libpod.BuildContainerGraph(containers)
if err != nil { if err != nil {
return "", err return nil, err
} }
// Traverse the dependency graph and create systemdgen.containerInfo's for // Traverse the dependency graph and create systemdgen.containerInfo's for
@ -133,7 +132,7 @@ func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string,
} }
ctrInfo, err := generateContainerInfo(ctr, options) ctrInfo, err := generateContainerInfo(ctr, options)
if err != nil { if err != nil {
return "", err return nil, err
} }
// Now add the container's dependencies and at the container as a // Now add the container's dependencies and at the container as a
// required service of the infra container. // required service of the infra container.
@ -149,24 +148,23 @@ func PodUnits(pod *libpod.Pod, options entities.GenerateSystemdOptions) (string,
containerInfos = append(containerInfos, ctrInfo) containerInfos = append(containerInfos, ctrInfo)
} }
units := map[string]string{}
// Now generate the systemd service for all containers. // Now generate the systemd service for all containers.
builder := strings.Builder{}
out, err := executePodTemplate(podInfo, options) out, err := executePodTemplate(podInfo, options)
if err != nil { if err != nil {
return "", err return nil, err
} }
builder.WriteString(out) units[podInfo.ServiceName] = out
for _, info := range containerInfos { for _, info := range containerInfos {
info.pod = podInfo info.pod = podInfo
builder.WriteByte('\n')
out, err := executeContainerTemplate(info, options) out, err := executeContainerTemplate(info, options)
if err != nil { if err != nil {
return "", err return nil, err
} }
builder.WriteString(out) units[info.ServiceName] = out
} }
return builder.String(), nil return units, nil
} }
func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) (*podInfo, error) { func generatePodInfo(pod *libpod.Pod, options entities.GenerateSystemdOptions) (*podInfo, error) {
@ -339,18 +337,5 @@ func executePodTemplate(info *podInfo, options entities.GenerateSystemdOptions)
return "", err return "", err
} }
if !options.Files {
return buf.String(), nil return buf.String(), nil
}
buf.WriteByte('\n')
cwd, err := os.Getwd()
if err != nil {
return "", errors.Wrap(err, "error getting current working directory")
}
path := filepath.Join(cwd, fmt.Sprintf("%s.service", info.ServiceName))
if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil {
return "", errors.Wrap(err, "error generating systemd unit")
}
return path, nil
} }

View File

@ -58,7 +58,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
podGoodNamedNew := `# pod-123abc.service podGoodNamedNew := `# pod-123abc.service
# autogenerated by Podman CI # autogenerated by Podman CI
@ -84,7 +85,8 @@ KillMode=none
Type=forking Type=forking
[Install] [Install]
WantedBy=multi-user.target default.target` WantedBy=multi-user.target default.target
`
tests := []struct { tests := []struct {
name string name string
@ -130,7 +132,6 @@ WantedBy=multi-user.target default.target`
test := tt test := tt
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
opts := entities.GenerateSystemdOptions{ opts := entities.GenerateSystemdOptions{
Files: false,
New: test.new, New: test.new,
} }
got, err := executePodTemplate(&test.info, opts) got, err := executePodTemplate(&test.info, opts)

View File

@ -1,5 +1,3 @@
// +build !remote
package integration package integration
import ( import (
@ -61,7 +59,7 @@ var _ = Describe("Podman generate systemd", func() {
session = podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "bogus", "foobar"}) session = podmanTest.Podman([]string{"generate", "systemd", "--restart-policy", "bogus", "foobar"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError()) Expect(session).To(ExitWithError())
found, _ := session.ErrorGrepString("Error: bogus is not a valid restart policy") found, _ := session.ErrorGrepString("bogus is not a valid restart policy")
Expect(found).Should(BeTrue()) Expect(found).Should(BeTrue())
}) })
@ -383,4 +381,15 @@ var _ = Describe("Podman generate systemd", func() {
found, _ = session.GrepString("pod rm --ignore -f --pod-id-file %t/pod-foo.pod-id") found, _ = session.GrepString("pod rm --ignore -f --pod-id-file %t/pod-foo.pod-id")
Expect(found).To(BeTrue()) Expect(found).To(BeTrue())
}) })
It("podman generate systemd --format json", func() {
n := podmanTest.Podman([]string{"create", "--name", "foo", ALPINE})
n.WaitWithDefaultTimeout()
Expect(n.ExitCode()).To(Equal(0))
session := podmanTest.Podman([]string{"generate", "systemd", "--format", "json", "foo"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
Expect(session.IsJSONOutputValid()).To(BeTrue())
})
}) })