github.com/containerd/nerdctl/v2@v2.0.0-beta.5.0.20240520001846-b5758f54fa28/pkg/cmd/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 container
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  
    25  	"github.com/containerd/console"
    26  	"github.com/containerd/containerd"
    27  	"github.com/containerd/containerd/cio"
    28  	"github.com/containerd/log"
    29  	"github.com/containerd/nerdctl/v2/pkg/api/types"
    30  	"github.com/containerd/nerdctl/v2/pkg/consoleutil"
    31  	"github.com/containerd/nerdctl/v2/pkg/flagutil"
    32  	"github.com/containerd/nerdctl/v2/pkg/idgen"
    33  	"github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker"
    34  	"github.com/containerd/nerdctl/v2/pkg/signalutil"
    35  	"github.com/containerd/nerdctl/v2/pkg/taskutil"
    36  	"github.com/opencontainers/runtime-spec/specs-go"
    37  )
    38  
    39  // Exec will find the right running container to run a new command.
    40  func Exec(ctx context.Context, client *containerd.Client, args []string, options types.ContainerExecOptions) error {
    41  	walker := &containerwalker.ContainerWalker{
    42  		Client: client,
    43  		OnFound: func(ctx context.Context, found containerwalker.Found) error {
    44  			if found.MatchCount > 1 {
    45  				return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
    46  			}
    47  			return execActionWithContainer(ctx, client, found.Container, args, options)
    48  		},
    49  	}
    50  	req := args[0]
    51  	n, err := walker.Walk(ctx, req)
    52  	if err != nil {
    53  		return err
    54  	} else if n == 0 {
    55  		return fmt.Errorf("no such container %s", req)
    56  	}
    57  	return nil
    58  }
    59  
    60  func execActionWithContainer(ctx context.Context, client *containerd.Client, container containerd.Container, args []string, options types.ContainerExecOptions) error {
    61  	pspec, err := generateExecProcessSpec(ctx, client, container, args, options)
    62  	if err != nil {
    63  		return err
    64  	}
    65  
    66  	task, err := container.Task(ctx, nil)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	var (
    71  		ioCreator cio.Creator
    72  		in        io.Reader
    73  		stdinC    = &taskutil.StdinCloser{
    74  			Stdin: os.Stdin,
    75  		}
    76  	)
    77  
    78  	if options.Interactive {
    79  		in = stdinC
    80  	}
    81  	cioOpts := []cio.Opt{cio.WithStreams(in, os.Stdout, os.Stderr)}
    82  	if options.TTY {
    83  		cioOpts = append(cioOpts, cio.WithTerminal)
    84  	}
    85  	ioCreator = cio.NewCreator(cioOpts...)
    86  
    87  	execID := "exec-" + idgen.GenerateID()
    88  	process, err := task.Exec(ctx, execID, pspec, ioCreator)
    89  	if err != nil {
    90  		return err
    91  	}
    92  	stdinC.Closer = func() {
    93  		process.CloseIO(ctx, containerd.WithStdinCloser)
    94  	}
    95  	// if detach, we should not call this defer
    96  	if !options.Detach {
    97  		defer process.Delete(ctx)
    98  	}
    99  
   100  	statusC, err := process.Wait(ctx)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	var con console.Console
   106  	if options.TTY {
   107  		con = console.Current()
   108  		defer con.Reset()
   109  		if err := con.SetRaw(); err != nil {
   110  			return err
   111  		}
   112  	}
   113  	if !options.Detach {
   114  		if options.TTY {
   115  			if err := consoleutil.HandleConsoleResize(ctx, process, con); err != nil {
   116  				log.G(ctx).WithError(err).Error("console resize")
   117  			}
   118  		} else {
   119  			sigc := signalutil.ForwardAllSignals(ctx, process)
   120  			defer signalutil.StopCatch(sigc)
   121  		}
   122  	}
   123  
   124  	if err := process.Start(ctx); err != nil {
   125  		return err
   126  	}
   127  	if options.Detach {
   128  		return nil
   129  	}
   130  	status := <-statusC
   131  	code, _, err := status.Result()
   132  	if err != nil {
   133  		return err
   134  	}
   135  	if code != 0 {
   136  		return fmt.Errorf("exec failed with exit code %d", code)
   137  	}
   138  	return nil
   139  }
   140  
   141  func generateExecProcessSpec(ctx context.Context, client *containerd.Client, container containerd.Container, args []string, options types.ContainerExecOptions) (*specs.Process, error) {
   142  	spec, err := container.Spec(ctx)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	userOpts, err := generateUserOpts(options.User)
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	if userOpts != nil {
   151  		c, err := container.Info(ctx)
   152  		if err != nil {
   153  			return nil, err
   154  		}
   155  		for _, opt := range userOpts {
   156  			if err := opt(ctx, client, &c, spec); err != nil {
   157  				return nil, err
   158  			}
   159  		}
   160  	}
   161  
   162  	pspec := spec.Process
   163  	pspec.Terminal = options.TTY
   164  	if pspec.Terminal {
   165  		if size, err := console.Current().Size(); err == nil {
   166  			pspec.ConsoleSize = &specs.Box{Height: uint(size.Height), Width: uint(size.Width)}
   167  		}
   168  	}
   169  	pspec.Args = args[1:]
   170  
   171  	if options.Workdir != "" {
   172  		pspec.Cwd = options.Workdir
   173  	}
   174  	envs, err := flagutil.MergeEnvFileAndOSEnv(options.EnvFile, options.Env)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	pspec.Env = flagutil.ReplaceOrAppendEnvValues(pspec.Env, envs)
   179  
   180  	if options.Privileged {
   181  		err = setExecCapabilities(pspec)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  	}
   186  
   187  	return pspec, nil
   188  }