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 }