Skip to content

Commit

Permalink
Merge pull request containers#7329 from Luap99/generate-systemd-remote
Browse files Browse the repository at this point in the history
APIv2 add generate systemd endpoint
  • Loading branch information
openshift-merge-robot authored Sep 5, 2020
2 parents 6862cc6 + ebfea2f commit f1323a9
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 f1323a9

Please sign in to comment.