mirror of
https://github.com/containers/podman.git
synced 2025-06-25 12:20:42 +08:00
container runlabel
Implement container runlabel for v2. Local client only. Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
80
cmd/podman/containers/runlabel.go
Normal file
80
cmd/podman/containers/runlabel.go
Normal file
@ -0,0 +1,80 @@
|
||||
package containers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/libpod/cmd/podman/registry"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// runlabelOptionsWrapper allows for combining API-only with CLI-only options
|
||||
// and to convert between them.
|
||||
type runlabelOptionsWrapper struct {
|
||||
entities.ContainerRunlabelOptions
|
||||
TLSVerifyCLI bool
|
||||
}
|
||||
|
||||
var (
|
||||
runlabelOptions = runlabelOptionsWrapper{}
|
||||
runlabelDescription = "Executes a command as described by a container image label."
|
||||
runlabelCommand = &cobra.Command{
|
||||
Use: "runlabel [flags] LABEL IMAGE [ARG...]",
|
||||
Short: "Execute the command described by an image label",
|
||||
Long: runlabelDescription,
|
||||
RunE: runlabel,
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
Example: `podman container runlabel run imageID
|
||||
podman container runlabel --pull install imageID arg1 arg2
|
||||
podman container runlabel --display run myImage`,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
registry.Commands = append(registry.Commands, registry.CliCommand{
|
||||
Mode: []entities.EngineMode{entities.ABIMode},
|
||||
Command: runlabelCommand,
|
||||
Parent: containerCmd,
|
||||
})
|
||||
|
||||
flags := rmCommand.Flags()
|
||||
flags.StringVar(&runlabelOptions.Authfile, "authfile", auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
||||
flags.StringVar(&runlabelOptions.CertDir, "cert-dir", "", "`Pathname` of a directory containing TLS certificates and keys")
|
||||
flags.StringVar(&runlabelOptions.Credentials, "creds", "", "`Credentials` (USERNAME:PASSWORD) to use for authenticating to a registry")
|
||||
flags.BoolVar(&runlabelOptions.Display, "display", false, "Preview the command that the label would run")
|
||||
flags.StringVarP(&runlabelOptions.Name, "name", "n", "", "Assign a name to the container")
|
||||
flags.StringVar(&runlabelOptions.Optional1, "opt1", "", "Optional parameter to pass for install")
|
||||
flags.StringVar(&runlabelOptions.Optional2, "opt2", "", "Optional parameter to pass for install")
|
||||
flags.StringVar(&runlabelOptions.Optional3, "opt3", "", "Optional parameter to pass for install")
|
||||
flags.BoolP("pull", "p", false, "Pull the image if it does not exist locally prior to executing the label contents")
|
||||
flags.BoolVarP(&runlabelOptions.Quiet, "quiet", "q", false, "Suppress output information when installing images")
|
||||
flags.BoolVar(&runlabelOptions.Replace, "replace", false, "Replace existing container with a new one from the image")
|
||||
flags.StringVar(&runlabelOptions.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)")
|
||||
flags.BoolVar(&runlabelOptions.TLSVerifyCLI, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries")
|
||||
|
||||
// Hide the optional flags.
|
||||
_ = flags.MarkHidden("opt1")
|
||||
_ = flags.MarkHidden("opt2")
|
||||
_ = flags.MarkHidden("opt3")
|
||||
|
||||
if err := flags.MarkDeprecated("pull", "podman will pull if not found in local storage"); err != nil {
|
||||
logrus.Error("unable to mark pull flag deprecated")
|
||||
}
|
||||
}
|
||||
|
||||
func runlabel(cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("tls-verify") {
|
||||
runlabelOptions.SkipTLSVerify = types.NewOptionalBool(!runlabelOptions.TLSVerifyCLI)
|
||||
}
|
||||
if runlabelOptions.Authfile != "" {
|
||||
if _, err := os.Stat(runlabelOptions.Authfile); err != nil {
|
||||
return errors.Wrapf(err, "error getting authfile %s", runlabelOptions.Authfile)
|
||||
}
|
||||
}
|
||||
return registry.ContainerEngine().ContainerRunlabel(context.Background(), args[0], args[1], args[2:], runlabelOptions.ContainerRunlabelOptions)
|
||||
}
|
@ -6,11 +6,49 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/pkg/specgen"
|
||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||
)
|
||||
|
||||
// ContainerRunlabelOptions are the options to execute container-runlabel.
|
||||
type ContainerRunlabelOptions struct {
|
||||
// Authfile - path to an authentication file.
|
||||
Authfile string
|
||||
// CertDir - path to a directory containing TLS certifications and
|
||||
// keys.
|
||||
CertDir string
|
||||
// Credentials - `user:password` to use when pulling an image.
|
||||
Credentials string
|
||||
// Display - do not execute but print the command.
|
||||
Display bool
|
||||
// Replace - replace an existing container with a new one from the
|
||||
// image.
|
||||
Replace bool
|
||||
// Name - use this name when executing the runlabel container.
|
||||
Name string
|
||||
// Optional1 - fist optional parameter for install.
|
||||
Optional1 string
|
||||
// Optional2 - second optional parameter for install.
|
||||
Optional2 string
|
||||
// Optional3 - third optional parameter for install.
|
||||
Optional3 string
|
||||
// Pull - pull the specified image if it's not in the local storage.
|
||||
Pull bool
|
||||
// Quiet - suppress output when pulling images.
|
||||
Quiet bool
|
||||
// SignaturePolicy - path to a signature-policy file.
|
||||
SignaturePolicy string
|
||||
// SkipTLSVerify - skip HTTPS and certificate verifications when
|
||||
// contacting registries.
|
||||
SkipTLSVerify types.OptionalBool
|
||||
}
|
||||
|
||||
// ContainerRunlabelReport contains the results from executing container-runlabel.
|
||||
type ContainerRunlabelReport struct {
|
||||
}
|
||||
|
||||
type WaitOptions struct {
|
||||
Condition define.ContainerStatus
|
||||
Interval time.Duration
|
||||
|
@ -34,6 +34,7 @@ type ContainerEngine interface {
|
||||
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
||||
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
||||
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, error)
|
||||
ContainerRunlabel(ctx context.Context, label string, image string, args []string, opts ContainerRunlabelOptions) error
|
||||
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
|
||||
ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
|
||||
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
|
||||
|
280
pkg/domain/infra/abi/containers_runlabel.go
Normal file
280
pkg/domain/infra/abi/containers_runlabel.go
Normal file
@ -0,0 +1,280 @@
|
||||
package abi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/image/v5/types"
|
||||
"github.com/containers/libpod/libpod/define"
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/domain/entities"
|
||||
envLib "github.com/containers/libpod/pkg/env"
|
||||
"github.com/containers/libpod/pkg/util"
|
||||
"github.com/containers/libpod/utils"
|
||||
"github.com/google/shlex"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, imageRef string, args []string, options entities.ContainerRunlabelOptions) error {
|
||||
// First, get the image and pull it if needed.
|
||||
img, err := ic.runlabelImage(ctx, label, imageRef, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Extract the runlabel from the image.
|
||||
runlabel, err := img.GetLabel(ctx, label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, env, err := generateRunlabelCommand(runlabel, img, args, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stdErr := os.Stderr
|
||||
stdOut := os.Stdout
|
||||
stdIn := os.Stdin
|
||||
if options.Quiet {
|
||||
stdErr = nil
|
||||
stdOut = nil
|
||||
stdIn = nil
|
||||
}
|
||||
|
||||
// If container already exists && --replace given -- Nuke it
|
||||
if options.Replace {
|
||||
for i, entry := range cmd {
|
||||
if entry == "--name" {
|
||||
name := cmd[i+1]
|
||||
ctr, err := ic.Libpod.LookupContainer(name)
|
||||
if err != nil {
|
||||
if errors.Cause(err) != define.ErrNoSuchCtr {
|
||||
logrus.Debugf("Error occurred searching for container %s: %s", name, err.Error())
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
logrus.Debugf("Runlabel --replace option given. Container %s will be deleted. The new container will be named %s", ctr.ID(), name)
|
||||
if err := ic.Libpod.RemoveContainer(ctx, ctr, true, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return utils.ExecCmdWithStdStreams(stdIn, stdOut, stdErr, env, cmd[0], cmd[1:]...)
|
||||
}
|
||||
|
||||
// runlabelImage returns an image based on the specified image AND options.
|
||||
func (ic *ContainerEngine) runlabelImage(ctx context.Context, label string, imageRef string, options entities.ContainerRunlabelOptions) (*image.Image, error) {
|
||||
// First, look up the image locally. If we get an error and requested
|
||||
// to pull, fallthrough and pull it.
|
||||
img, err := ic.Libpod.ImageRuntime().NewFromLocal(imageRef)
|
||||
switch {
|
||||
case err == nil:
|
||||
return img, nil
|
||||
case !options.Pull:
|
||||
return nil, err
|
||||
default:
|
||||
// Fallthrough and pull!
|
||||
}
|
||||
|
||||
// Parse credentials if specified.
|
||||
var credentials *types.DockerAuthConfig
|
||||
if options.Credentials != "" {
|
||||
credentials, err = util.ParseRegistryCreds(options.Credentials)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Suppress pull progress bars if requested.
|
||||
pullOutput := os.Stdout
|
||||
if options.Quiet {
|
||||
pullOutput = nil // c/image/copy takes care of the rest
|
||||
}
|
||||
|
||||
// Pull the image.
|
||||
dockerRegistryOptions := image.DockerRegistryOptions{
|
||||
DockerCertPath: options.CertDir,
|
||||
DockerInsecureSkipTLSVerify: options.SkipTLSVerify,
|
||||
DockerRegistryCreds: credentials,
|
||||
}
|
||||
|
||||
return ic.Libpod.ImageRuntime().New(ctx, imageRef, options.SignaturePolicy, options.Authfile, pullOutput, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing)
|
||||
}
|
||||
|
||||
// generateRunlabelCommand generates the to-be-executed command as a string
|
||||
// slice along with a base environment.
|
||||
func generateRunlabelCommand(runlabel string, img *image.Image, args []string, options entities.ContainerRunlabelOptions) ([]string, []string, error) {
|
||||
var (
|
||||
err error
|
||||
name, imageName string
|
||||
globalOpts string
|
||||
cmd, env []string
|
||||
)
|
||||
|
||||
// TODO: How do we get global opts as done in v1?
|
||||
|
||||
// Extract the imageName (or ID).
|
||||
imgNames := img.Names()
|
||||
if len(imgNames) == 0 {
|
||||
imageName = img.ID()
|
||||
} else {
|
||||
imageName = imgNames[0]
|
||||
}
|
||||
|
||||
// Use the user-specified name or extract one from the image.
|
||||
if options.Name != "" {
|
||||
name = options.Name
|
||||
} else {
|
||||
name, err = image.GetImageBaseName(imageName)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Append the user-specified arguments to the runlabel (command).
|
||||
if len(args) > 0 {
|
||||
runlabel = fmt.Sprintf("%s %s", runlabel, strings.Join(args, " "))
|
||||
}
|
||||
|
||||
cmd, err = generateCommand(runlabel, imageName, name, globalOpts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
env = generateRunEnvironment(name, imageName, options)
|
||||
env = append(env, "PODMAN_RUNLABEL_NESTED=1")
|
||||
envmap, err := envLib.ParseSlice(env)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
envmapper := func(k string) string {
|
||||
switch k {
|
||||
case "OPT1":
|
||||
return envmap["OPT1"]
|
||||
case "OPT2":
|
||||
return envmap["OPT2"]
|
||||
case "OPT3":
|
||||
return envmap["OPT3"]
|
||||
case "PWD":
|
||||
// I would prefer to use os.getenv but it appears PWD is not in the os env list.
|
||||
d, err := os.Getwd()
|
||||
if err != nil {
|
||||
logrus.Error("unable to determine current working directory")
|
||||
return ""
|
||||
}
|
||||
return d
|
||||
}
|
||||
return ""
|
||||
}
|
||||
newS := os.Expand(strings.Join(cmd, " "), envmapper)
|
||||
cmd, err = shlex.Split(newS)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cmd, env, nil
|
||||
}
|
||||
|
||||
// generateCommand takes a label (string) and converts it to an executable command
|
||||
func generateCommand(command, imageName, name, globalOpts string) ([]string, error) {
|
||||
var (
|
||||
newCommand []string
|
||||
)
|
||||
if name == "" {
|
||||
name = imageName
|
||||
}
|
||||
|
||||
cmd, err := shlex.Split(command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prog, err := substituteCommand(cmd[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newCommand = append(newCommand, prog)
|
||||
|
||||
for _, arg := range cmd[1:] {
|
||||
var newArg string
|
||||
switch arg {
|
||||
case "IMAGE":
|
||||
newArg = imageName
|
||||
case "$IMAGE":
|
||||
newArg = imageName
|
||||
case "IMAGE=IMAGE":
|
||||
newArg = fmt.Sprintf("IMAGE=%s", imageName)
|
||||
case "IMAGE=$IMAGE":
|
||||
newArg = fmt.Sprintf("IMAGE=%s", imageName)
|
||||
case "NAME":
|
||||
newArg = name
|
||||
case "NAME=NAME":
|
||||
newArg = fmt.Sprintf("NAME=%s", name)
|
||||
case "NAME=$NAME":
|
||||
newArg = fmt.Sprintf("NAME=%s", name)
|
||||
case "$NAME":
|
||||
newArg = name
|
||||
case "$GLOBAL_OPTS":
|
||||
newArg = globalOpts
|
||||
default:
|
||||
newArg = arg
|
||||
}
|
||||
newCommand = append(newCommand, newArg)
|
||||
}
|
||||
return newCommand, nil
|
||||
}
|
||||
|
||||
// GenerateRunEnvironment merges the current environment variables with optional
|
||||
// environment variables provided by the user
|
||||
func generateRunEnvironment(name, imageName string, options entities.ContainerRunlabelOptions) []string {
|
||||
newEnv := os.Environ()
|
||||
if options.Optional1 != "" {
|
||||
newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", options.Optional1))
|
||||
}
|
||||
if options.Optional2 != "" {
|
||||
newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", options.Optional2))
|
||||
}
|
||||
if options.Optional3 != "" {
|
||||
newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", options.Optional3))
|
||||
}
|
||||
return newEnv
|
||||
}
|
||||
|
||||
func substituteCommand(cmd string) (string, error) {
|
||||
var (
|
||||
newCommand string
|
||||
)
|
||||
|
||||
// Replace cmd with "/proc/self/exe" if "podman" or "docker" is being
|
||||
// used. If "/usr/bin/docker" is provided, we also sub in podman.
|
||||
// Otherwise, leave the command unchanged.
|
||||
if cmd == "podman" || filepath.Base(cmd) == "docker" {
|
||||
newCommand = "/proc/self/exe"
|
||||
} else {
|
||||
newCommand = cmd
|
||||
}
|
||||
|
||||
// If cmd is an absolute or relative path, check if the file exists.
|
||||
// Throw an error if it doesn't exist.
|
||||
if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") {
|
||||
res, err := filepath.Abs(newCommand)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if _, err := os.Stat(res); !os.IsNotExist(err) {
|
||||
return res, nil
|
||||
} else if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return newCommand, nil
|
||||
}
|
@ -14,6 +14,10 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (ic *ContainerEngine) ContainerRunlabel(ctx context.Context, label string, image string, args []string, options entities.ContainerRunlabelOptions) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
|
||||
exists, err := containers.Exists(ic.ClientCxt, nameOrId)
|
||||
return &entities.BoolReport{Value: exists}, err
|
||||
|
@ -31,7 +31,6 @@ var _ = Describe("podman container runlabel", func() {
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
Skip(v2fail)
|
||||
tempdir, err = CreateTempDirInTempDir()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
|
Reference in New Issue
Block a user