github.com/containerd/nerdctl@v1.7.7/cmd/nerdctl/container_exec.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"errors"
    21  
    22  	"github.com/containerd/containerd"
    23  	"github.com/containerd/nerdctl/pkg/api/types"
    24  	"github.com/containerd/nerdctl/pkg/clientutil"
    25  	"github.com/containerd/nerdctl/pkg/cmd/container"
    26  	"github.com/spf13/cobra"
    27  )
    28  
    29  func newExecCommand() *cobra.Command {
    30  	var execCommand = &cobra.Command{
    31  		Use:               "exec [flags] CONTAINER COMMAND [ARG...]",
    32  		Args:              cobra.MinimumNArgs(2),
    33  		Short:             "Run a command in a running container",
    34  		RunE:              execAction,
    35  		ValidArgsFunction: execShellComplete,
    36  		SilenceUsage:      true,
    37  		SilenceErrors:     true,
    38  	}
    39  	execCommand.Flags().SetInterspersed(false)
    40  
    41  	execCommand.Flags().BoolP("tty", "t", false, "Allocate a pseudo-TTY")
    42  	execCommand.Flags().BoolP("interactive", "i", false, "Keep STDIN open even if not attached")
    43  	execCommand.Flags().BoolP("detach", "d", false, "Detached mode: run command in the background")
    44  	execCommand.Flags().StringP("workdir", "w", "", "Working directory inside the container")
    45  	// env needs to be StringArray, not StringSlice, to prevent "FOO=foo1,foo2" from being split to {"FOO=foo1", "foo2"}
    46  	execCommand.Flags().StringArrayP("env", "e", nil, "Set environment variables")
    47  	// env-file is defined as StringSlice, not StringArray, to allow specifying "--env-file=FILE1,FILE2" (compatible with Podman)
    48  	execCommand.Flags().StringSlice("env-file", nil, "Set environment variables from file")
    49  	execCommand.Flags().Bool("privileged", false, "Give extended privileges to the command")
    50  	execCommand.Flags().StringP("user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
    51  	return execCommand
    52  }
    53  
    54  func processExecCommandOptions(cmd *cobra.Command) (types.ContainerExecOptions, error) {
    55  	globalOptions, err := processRootCmdFlags(cmd)
    56  	if err != nil {
    57  		return types.ContainerExecOptions{}, err
    58  	}
    59  
    60  	flagI, err := cmd.Flags().GetBool("interactive")
    61  	if err != nil {
    62  		return types.ContainerExecOptions{}, err
    63  	}
    64  	flagT, err := cmd.Flags().GetBool("tty")
    65  	if err != nil {
    66  		return types.ContainerExecOptions{}, err
    67  	}
    68  	flagD, err := cmd.Flags().GetBool("detach")
    69  	if err != nil {
    70  		return types.ContainerExecOptions{}, err
    71  	}
    72  
    73  	if flagI {
    74  		if flagD {
    75  			return types.ContainerExecOptions{}, errors.New("currently flag -i and -d cannot be specified together (FIXME)")
    76  		}
    77  	}
    78  
    79  	if flagT {
    80  		if flagD {
    81  			return types.ContainerExecOptions{}, errors.New("currently flag -t and -d cannot be specified together (FIXME)")
    82  		}
    83  	}
    84  
    85  	workdir, err := cmd.Flags().GetString("workdir")
    86  	if err != nil {
    87  		return types.ContainerExecOptions{}, err
    88  	}
    89  
    90  	envFile, err := cmd.Flags().GetStringSlice("env-file")
    91  	if err != nil {
    92  		return types.ContainerExecOptions{}, err
    93  	}
    94  	env, err := cmd.Flags().GetStringArray("env")
    95  	if err != nil {
    96  		return types.ContainerExecOptions{}, err
    97  	}
    98  	privileged, err := cmd.Flags().GetBool("privileged")
    99  	if err != nil {
   100  		return types.ContainerExecOptions{}, err
   101  	}
   102  	user, err := cmd.Flags().GetString("user")
   103  	if err != nil {
   104  		return types.ContainerExecOptions{}, err
   105  	}
   106  
   107  	return types.ContainerExecOptions{
   108  		GOptions:    globalOptions,
   109  		TTY:         flagT,
   110  		Interactive: flagI,
   111  		Detach:      flagD,
   112  		Workdir:     workdir,
   113  		Env:         env,
   114  		EnvFile:     envFile,
   115  		Privileged:  privileged,
   116  		User:        user,
   117  	}, nil
   118  }
   119  
   120  func execAction(cmd *cobra.Command, args []string) error {
   121  	options, err := processExecCommandOptions(cmd)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	// simulate the behavior of double dash
   126  	newArg := []string{}
   127  	if len(args) >= 2 && args[1] == "--" {
   128  		newArg = append(newArg, args[:1]...)
   129  		newArg = append(newArg, args[2:]...)
   130  		args = newArg
   131  	}
   132  
   133  	client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer cancel()
   138  
   139  	return container.Exec(ctx, client, args, options)
   140  }
   141  
   142  func execShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   143  	if len(args) == 0 {
   144  		// show running container names
   145  		statusFilterFn := func(st containerd.ProcessStatus) bool {
   146  			return st == containerd.Running
   147  		}
   148  		return shellCompleteContainerNames(cmd, statusFilterFn)
   149  	}
   150  	return nil, cobra.ShellCompDirectiveNoFileComp
   151  }