mirror of
https://github.com/containers/podman.git
synced 2025-06-26 04:46:57 +08:00
generate systemd
Implement `podman generate systemd` for Podman v2 and enable associated tests. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
27
cmd/podman/generate/generate.go
Normal file
27
cmd/podman/generate/generate.go
Normal file
@ -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
cmd/podman/generate/systemd.go
Normal file
57
cmd/podman/generate/systemd.go
Normal file
@ -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
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
_ "github.com/containers/libpod/cmd/podman/containers"
|
_ "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/healthcheck"
|
||||||
_ "github.com/containers/libpod/cmd/podman/images"
|
_ "github.com/containers/libpod/cmd/podman/images"
|
||||||
_ "github.com/containers/libpod/cmd/podman/manifest"
|
_ "github.com/containers/libpod/cmd/podman/manifest"
|
||||||
|
@ -41,6 +41,7 @@ type ContainerEngine interface {
|
|||||||
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
|
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
|
||||||
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
|
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
|
||||||
Events(ctx context.Context, opts EventsOptions) 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)
|
HealthCheckRun(ctx context.Context, nameOrId string, options HealthCheckOptions) (*define.HealthCheckResults, error)
|
||||||
Info(ctx context.Context) (*define.Info, error)
|
Info(ctx context.Context) (*define.Info, error)
|
||||||
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
|
PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error)
|
||||||
|
22
pkg/domain/entities/generate.go
Normal file
22
pkg/domain/entities/generate.go
Normal file
@ -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
pkg/domain/infra/abi/generate.go
Normal file
174
pkg/domain/infra/abi/generate.go
Normal file
@ -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
pkg/domain/infra/tunnel/generate.go
Normal file
12
pkg/domain/infra/tunnel/generate.go
Normal file
@ -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")
|
||||||
|
}
|
@ -76,6 +76,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener
|
|||||||
}
|
}
|
||||||
|
|
||||||
options := []libpod.CtrCreateOption{}
|
options := []libpod.CtrCreateOption{}
|
||||||
|
options = append(options, libpod.WithCreateCommand())
|
||||||
|
|
||||||
var newImage *image.Image
|
var newImage *image.Image
|
||||||
if s.Rootfs != "" {
|
if s.Rootfs != "" {
|
||||||
|
@ -18,7 +18,6 @@ var _ = Describe("Podman generate systemd", func() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
Skip(v2fail)
|
|
||||||
tempdir, err = CreateTempDirInTempDir()
|
tempdir, err = CreateTempDirInTempDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
@ -10,6 +10,8 @@ SERVICE_NAME="podman_test_$(random_string)"
|
|||||||
UNIT_DIR="$HOME/.config/systemd/user"
|
UNIT_DIR="$HOME/.config/systemd/user"
|
||||||
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"
|
UNIT_FILE="$UNIT_DIR/$SERVICE_NAME.service"
|
||||||
|
|
||||||
|
# FIXME: the must run as root (because of CI). It's also broken...
|
||||||
|
|
||||||
function setup() {
|
function setup() {
|
||||||
skip_if_not_systemd
|
skip_if_not_systemd
|
||||||
skip_if_remote
|
skip_if_remote
|
||||||
|
Reference in New Issue
Block a user