mirror of
https://github.com/containers/podman.git
synced 2025-06-25 12:20:42 +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"
|
||||
|
||||
_ "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"
|
||||
|
@ -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)
|
||||
|
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 = append(options, libpod.WithCreateCommand())
|
||||
|
||||
var newImage *image.Image
|
||||
if s.Rootfs != "" {
|
||||
|
@ -18,7 +18,6 @@ var _ = Describe("Podman generate systemd", func() {
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
Skip(v2fail)
|
||||
tempdir, err = CreateTempDirInTempDir()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user