Skip to content

Commit

Permalink
APIv2 add generate systemd endpoint
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
Paul Holzinger committed Sep 2, 2020
1 parent 1184cdf commit ebfea2f
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 84 deletions.
71 changes: 68 additions & 3 deletions cmd/podman/generate/systemd.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package pods

import (
"encoding/json"
"fmt"
"os"
"path/filepath"

"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/cmd/podman/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var (
files bool
format string
systemdTimeout uint
systemdOptions = entities.GenerateSystemdOptions{}
systemdDescription = `Generate systemd units for a pod or container.
Expand All @@ -29,19 +36,20 @@ var (

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode},
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.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.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.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.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)
}

Expand All @@ -50,11 +58,68 @@ func systemd(cmd *cobra.Command, args []string) error {
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)
if err != nil {
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
}
6 changes: 5 additions & 1 deletion docs/source/markdown/podman-generate-systemd.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
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:

Expand All @@ -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`.

**--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**

Use the name of the container for the start, stop, and description in the unit file
Expand Down
45 changes: 45 additions & 0 deletions pkg/api/handlers/libpod/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,55 @@ import (
"github.com/containers/podman/v2/pkg/api/handlers/utils"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/containers/podman/v2/pkg/domain/infra/abi"
"github.com/containers/podman/v2/pkg/util"
"github.com/gorilla/schema"
"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) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
Expand Down
62 changes: 62 additions & 0 deletions pkg/api/server/register_generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,68 @@ import (
)

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
// ---
// tags:
Expand Down
27 changes: 27 additions & 0 deletions pkg/bindings/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ import (
"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) {
conn, err := bindings.GetClient(ctx)
if err != nil {
Expand Down
7 changes: 2 additions & 5 deletions pkg/domain/entities/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import "io"

// 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.
Expand All @@ -24,9 +22,8 @@ type GenerateSystemdOptions struct {

// GenerateSystemdReport
type GenerateSystemdReport struct {
// Output of the generate process. Either the generated files or their
// entire content.
Output string
// Units of the generate process. key = unit name -> value = unit content
Units map[string]string
}

// GenerateKubeOptions control the generation of Kubernetes YAML files.
Expand Down
8 changes: 4 additions & 4 deletions pkg/domain/infra/abi/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
ctr, ctrErr := ic.Libpod.LookupContainer(nameOrID)
if ctrErr == nil {
// Generate the unit for the container.
s, err := generate.ContainerUnit(ctr, options)
name, content, err := generate.ContainerUnit(ctr, options)
if err != nil {
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.
Expand All @@ -34,11 +34,11 @@ func (ic *ContainerEngine) GenerateSystemd(ctx context.Context, nameOrID string,
}

// 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 {
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) {
Expand Down
3 changes: 1 addition & 2 deletions pkg/domain/infra/tunnel/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import (

"github.com/containers/podman/v2/pkg/bindings/generate"
"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) {
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) {
Expand Down
Loading

0 comments on commit ebfea2f

Please sign in to comment.