mirror of
https://github.com/containers/podman.git
synced 2025-06-26 12:56:45 +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"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/containers/libpod/libpod/define"
|
"github.com/containers/libpod/libpod/define"
|
||||||
"github.com/containers/libpod/pkg/specgen"
|
"github.com/containers/libpod/pkg/specgen"
|
||||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
"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 {
|
type WaitOptions struct {
|
||||||
Condition define.ContainerStatus
|
Condition define.ContainerStatus
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
|
@ -34,6 +34,7 @@ type ContainerEngine interface {
|
|||||||
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
|
||||||
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
|
||||||
ContainerRun(ctx context.Context, opts ContainerRunOptions) (*ContainerRunReport, 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)
|
ContainerStart(ctx context.Context, namesOrIds []string, options ContainerStartOptions) ([]*ContainerStartReport, error)
|
||||||
ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
|
ContainerStats(ctx context.Context, namesOrIds []string, options ContainerStatsOptions) error
|
||||||
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, 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"
|
"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) {
|
func (ic *ContainerEngine) ContainerExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) {
|
||||||
exists, err := containers.Exists(ic.ClientCxt, nameOrId)
|
exists, err := containers.Exists(ic.ClientCxt, nameOrId)
|
||||||
return &entities.BoolReport{Value: exists}, err
|
return &entities.BoolReport{Value: exists}, err
|
||||||
|
@ -31,7 +31,6 @@ var _ = Describe("podman container runlabel", 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)
|
||||||
|
Reference in New Issue
Block a user