Skip to content

Commit

Permalink
Merge pull request containers#5998 from vrothberg/generate-systemd
Browse files Browse the repository at this point in the history
generate systemd
  • Loading branch information
openshift-merge-robot authored Apr 29, 2020
2 parents 4e21d09 + b2414b5 commit 62a4bef
Show file tree
Hide file tree
Showing 10 changed files with 297 additions and 1 deletion.
27 changes: 27 additions & 0 deletions cmd/podman/generate/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pods

import (
"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/util"
"github.com/spf13/cobra"
)

var (
// Command: podman _generate_
generateCmd = &cobra.Command{
Use: "generate",
Short: "Generate structured data based on containers and pods.",
Long: "Generate structured data (e.g., Kubernetes yaml or systemd units) based on containers and pods.",
TraverseChildren: true,
RunE: registry.SubCommandExists,
}
containerConfig = util.DefaultContainerConfig()
)

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
Command: generateCmd,
})
}
57 changes: 57 additions & 0 deletions cmd/podman/generate/systemd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package pods

import (
"fmt"

"github.com/containers/libpod/cmd/podman/registry"
"github.com/containers/libpod/cmd/podman/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/spf13/cobra"
)

var (
systemdTimeout 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 [flags] CTR|POD",
Short: "Generate systemd units.",
Long: systemdDescription,
RunE: systemd,
Args: cobra.MinimumNArgs(1),
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{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: systemdCmd,
Parent: generateCmd,
})
flags := systemdCmd.Flags()
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.UintVarP(&systemdTimeout, "time", "t", containerConfig.Engine.StopTimeout, "Stop timeout override")
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.SetNormalizeFunc(utils.AliasFlags)
}

func systemd(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("time") {
systemdOptions.StopTimeout = &systemdTimeout
}

report, err := registry.ContainerEngine().GenerateSystemd(registry.GetContext(), args[0], systemdOptions)
if err != nil {
return err
}

fmt.Println(report.Output)
return nil
}
1 change: 1 addition & 0 deletions cmd/podman/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"os"

_ "github.com/containers/libpod/cmd/podman/containers"
_ "github.com/containers/libpod/cmd/podman/generate"
_ "github.com/containers/libpod/cmd/podman/healthcheck"
_ "github.com/containers/libpod/cmd/podman/images"
_ "github.com/containers/libpod/cmd/podman/manifest"
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/engine_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ContainerEngine interface {
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
Events(ctx context.Context, opts EventsOptions) error
GenerateSystemd(ctx context.Context, nameOrID string, opts GenerateSystemdOptions) (*GenerateSystemdReport, error)
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
Expand Down
22 changes: 22 additions & 0 deletions pkg/domain/entities/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package entities

// GenerateSystemdOptions control the generation of systemd unit files.
type GenerateSystemdOptions struct {
// Files - generate files instead of printing to stdout.
Files bool
// Name - use container/pod name instead of its ID.
Name bool
// New - create a new container instead of starting a new one.
New bool
// RestartPolicy - systemd restart policy.
RestartPolicy string
// StopTimeout - time when stopping the container.
StopTimeout *uint
}

// GenerateSystemdReport
type GenerateSystemdReport struct {
// Output of the generate process. Either the generated files or their
// entire content.
Output string
}
174 changes: 174 additions & 0 deletions pkg/domain/infra/abi/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package abi

import (
"context"
"fmt"
"strings"

"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/systemd/generate"
"github.com/pkg/errors"
)

func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
opts := generate.Options{
Files: options.Files,
New: options.New,
}

// First assume it's a container.
if info, found, err := ic.generateSystemdgenContainerInfo(nameOrID, nil, options); found && err != nil {
return nil, err
} else if found && err == nil {
output, err := generate.CreateContainerSystemdUnit(info, opts)
if err != nil {
return nil, err
}
return &entities.GenerateSystemdReport{Output: output}, nil
}

// --new does not support pods.
if options.New {
return nil, errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
}

// We're either having a pod or garbage.
pod, err := ic.Libpod.LookupPod(nameOrID)
if err != nil {
return nil, err
}

// Error out if the pod has no infra container, which we require to be the
// main service.
if !pod.HasInfraContainer() {
return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
}

// Generate a systemdgen.ContainerInfo for the infra container. This
// ContainerInfo acts as the main service of the pod.
infraID, err := pod.InfraContainerID()
if err != nil {
return nil, nil
}
podInfo, _, err := ic.generateSystemdgenContainerInfo(infraID, pod, options)
if err != nil {
return nil, err
}

// Compute the container-dependency graph for the Pod.
containers, err := pod.AllContainers()
if err != nil {
return nil, err
}
if len(containers) == 0 {
return nil, fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
}
graph, err := libpod.BuildContainerGraph(containers)
if err != nil {
return nil, err
}

// Traverse the dependency graph and create systemdgen.ContainerInfo's for
// each container.
containerInfos := []*generate.ContainerInfo{podInfo}
for ctr, dependencies := range graph.DependencyMap() {
// Skip the infra container as we already generated it.
if ctr.ID() == infraID {
continue
}
ctrInfo, _, err := ic.generateSystemdgenContainerInfo(ctr.ID(), nil, options)
if err != nil {
return nil, err
}
// Now add the container's dependencies and at the container as a
// required service of the infra container.
for _, dep := range dependencies {
if dep.ID() == infraID {
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
} else {
_, serviceName := generateServiceName(dep, nil, options)
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
}
}
podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
containerInfos = append(containerInfos, ctrInfo)
}

// Now generate the systemd service for all containers.
builder := strings.Builder{}
for i, info := range containerInfos {
if i > 0 {
builder.WriteByte('\n')
}
out, err := generate.CreateContainerSystemdUnit(info, opts)
if err != nil {
return nil, err
}
builder.WriteString(out)
}

return &entities.GenerateSystemdReport{Output: builder.String()}, nil
}

// generateSystemdgenContainerInfo is a helper to generate a
// systemdgen.ContainerInfo for `GenerateSystemd`.
func (ic *ContainerEngine) generateSystemdgenContainerInfo(nameOrID string, pod *libpod.Pod, options entities.GenerateSystemdOptions) (*generate.ContainerInfo, bool, error) {
ctr, err := ic.Libpod.LookupContainer(nameOrID)
if err != nil {
return nil, false, err
}

timeout := ctr.StopTimeout()
if options.StopTimeout != nil {
timeout = *options.StopTimeout
}

config := ctr.Config()
conmonPidFile := config.ConmonPidFile
if conmonPidFile == "" {
return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
}

createCommand := []string{}
if config.CreateCommand != nil {
createCommand = config.CreateCommand
} else if options.New {
return nil, true, errors.Errorf("cannot use --new on container %q: no create command found", nameOrID)
}

name, serviceName := generateServiceName(ctr, pod, options)
info := &generate.ContainerInfo{
ServiceName: serviceName,
ContainerName: name,
RestartPolicy: options.RestartPolicy,
PIDFile: conmonPidFile,
StopTimeout: timeout,
GenerateTimestamp: true,
CreateCommand: createCommand,
}

return info, true, nil
}

// generateServiceName generates the container name and the service name for systemd service.
func generateServiceName(ctr *libpod.Container, pod *libpod.Pod, options entities.GenerateSystemdOptions) (string, string) {
var kind, name, ctrName string
if pod == nil {
kind = "container"
name = ctr.ID()
if options.Name {
name = ctr.Name()
}
ctrName = name
} else {
kind = "pod"
name = pod.ID()
ctrName = ctr.ID()
if options.Name {
name = pod.Name()
ctrName = ctr.Name()
}
}
return ctrName, fmt.Sprintf("%s-%s", kind, name)
}
12 changes: 12 additions & 0 deletions pkg/domain/infra/tunnel/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package tunnel

import (
"context"

"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
)

func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string, options entities.GenerateSystemdOptions) (*entities.GenerateSystemdReport, error) {
return nil, errors.New("not implemented for tunnel")
}
1 change: 1 addition & 0 deletions pkg/specgen/generate/container_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
}

options := []libpod.CtrCreateOption{}
options = append(options, libpod.WithCreateCommand())

var newImage *image.Image
if s.Rootfs != "" {
Expand Down
1 change: 0 additions & 1 deletion test/e2e/generate_systemd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ var _ = Describe("Podman generate systemd", func() {
)

BeforeEach(func() {
Skip(v2fail)
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
Expand Down
2 changes: 2 additions & 0 deletions test/system/250-generate-systemd.bats
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ SERVICE_NAME="podman_test_$(random_string)"
UNIT_DIR="$HOME/.config/systemd/user"
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"

# FIXME: the must run as root (because of CI). It's also broken...

function setup() {
skip_if_not_systemd
skip_if_remote
Expand Down

0 comments on commit 62a4bef

Please sign in to comment.