github.com/containerd/nerdctl@v1.7.7/pkg/taskutil/taskutil.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 taskutil 18 19 import ( 20 "context" 21 "errors" 22 "io" 23 "net/url" 24 "os" 25 "runtime" 26 "slices" 27 "strings" 28 "sync" 29 "syscall" 30 31 "github.com/Masterminds/semver/v3" 32 "github.com/containerd/console" 33 "github.com/containerd/containerd" 34 "github.com/containerd/containerd/cio" 35 "github.com/containerd/log" 36 "github.com/containerd/nerdctl/pkg/cioutil" 37 "github.com/containerd/nerdctl/pkg/consoleutil" 38 "github.com/containerd/nerdctl/pkg/infoutil" 39 "golang.org/x/term" 40 ) 41 42 // NewTask is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/tasks_unix.go#L70-L108 43 func NewTask(ctx context.Context, client *containerd.Client, container containerd.Container, 44 attachStreamOpt []string, flagI, flagT, flagD bool, con console.Console, logURI, detachKeys, namespace string, detachC chan<- struct{}) (containerd.Task, error) { 45 46 var t containerd.Task 47 closer := func() { 48 if detachC != nil { 49 detachC <- struct{}{} 50 } 51 // t will be set by container.NewTask at the end of this function. 52 // 53 // We cannot use container.Task(ctx, cio.Load) to get the IO here 54 // because the `cancel` field of the returned `*cio` is nil. [1] 55 // 56 // [1] https://github.com/containerd/containerd/blob/8f756bc8c26465bd93e78d9cd42082b66f276e10/cio/io.go#L358-L359 57 io := t.IO() 58 if io == nil { 59 log.G(ctx).Errorf("got a nil io") 60 return 61 } 62 io.Cancel() 63 } 64 var ioCreator cio.Creator 65 if len(attachStreamOpt) != 0 { 66 log.G(ctx).Debug("attaching output instead of using the log-uri") 67 if flagT { 68 in, err := consoleutil.NewDetachableStdin(con, detachKeys, closer) 69 if err != nil { 70 return nil, err 71 } 72 ioCreator = cio.NewCreator(cio.WithStreams(in, con, nil), cio.WithTerminal) 73 } else { 74 streams := processAttachStreamsOpt(attachStreamOpt) 75 ioCreator = cio.NewCreator(cio.WithStreams(streams.stdIn, streams.stdOut, streams.stdErr)) 76 } 77 78 } else if flagT && flagD { 79 u, err := url.Parse(logURI) 80 if err != nil { 81 return nil, err 82 } 83 84 var args []string 85 for k, vs := range u.Query() { 86 args = append(args, k) 87 if len(vs) > 0 { 88 args = append(args, vs[0]) 89 } 90 } 91 92 // args[0]: _NERDCTL_INTERNAL_LOGGING 93 // args[1]: /var/lib/nerdctl/1935db59 94 if len(args) != 2 { 95 return nil, errors.New("parse logging path error") 96 } 97 ioCreator = cio.TerminalBinaryIO(u.Path, map[string]string{ 98 args[0]: args[1], 99 }) 100 } else if flagT && !flagD { 101 102 if con == nil { 103 return nil, errors.New("got nil con with flagT=true") 104 } 105 var in io.Reader 106 if flagI { 107 // FIXME: check IsTerminal on Windows too 108 if runtime.GOOS != "windows" && !term.IsTerminal(0) { 109 return nil, errors.New("the input device is not a TTY") 110 } 111 var err error 112 in, err = consoleutil.NewDetachableStdin(con, detachKeys, closer) 113 if err != nil { 114 return nil, err 115 } 116 } 117 ioCreator = cioutil.NewContainerIO(namespace, logURI, true, in, os.Stdout, os.Stderr) 118 } else if flagD && logURI != "" { 119 u, err := url.Parse(logURI) 120 if err != nil { 121 return nil, err 122 } 123 ioCreator = cio.LogURI(u) 124 } else { 125 var in io.Reader 126 if flagI { 127 if sv, err := infoutil.ServerSemVer(ctx, client); err != nil { 128 log.G(ctx).Warn(err) 129 } else if sv.LessThan(semver.MustParse("1.6.0-0")) { 130 log.G(ctx).Warnf("`nerdctl (run|exec) -i` without `-t` expects containerd 1.6 or later, got containerd %v", sv) 131 } 132 var stdinC io.ReadCloser = &StdinCloser{ 133 Stdin: os.Stdin, 134 Closer: func() { 135 if t, err := container.Task(ctx, nil); err != nil { 136 log.G(ctx).WithError(err).Debugf("failed to get task for StdinCloser") 137 } else { 138 t.CloseIO(ctx, containerd.WithStdinCloser) 139 } 140 }, 141 } 142 in = stdinC 143 } 144 ioCreator = cioutil.NewContainerIO(namespace, logURI, false, in, os.Stdout, os.Stderr) 145 } 146 t, err := container.NewTask(ctx, ioCreator) 147 if err != nil { 148 return nil, err 149 } 150 return t, nil 151 } 152 153 // struct used to store streams specified with attachStreamOpt (-a, --attach) 154 type streams struct { 155 stdIn *os.File 156 stdOut *os.File 157 stdErr *os.File 158 } 159 160 func nullStream() *os.File { 161 devNull, err := os.Open(os.DevNull) 162 if err != nil { 163 return nil 164 } 165 defer devNull.Close() 166 167 return devNull 168 } 169 170 func processAttachStreamsOpt(streamsArr []string) streams { 171 stdIn := os.Stdin 172 stdOut := os.Stdout 173 stdErr := os.Stderr 174 175 for i, str := range streamsArr { 176 streamsArr[i] = strings.ToUpper(str) 177 } 178 179 if !slices.Contains(streamsArr, "STDIN") { 180 stdIn = nullStream() 181 } 182 183 if !slices.Contains(streamsArr, "STDOUT") { 184 stdOut = nullStream() 185 } 186 187 if !slices.Contains(streamsArr, "STDERR") { 188 stdErr = nullStream() 189 } 190 191 return streams{ 192 stdIn: stdIn, 193 stdOut: stdOut, 194 stdErr: stdErr, 195 } 196 } 197 198 // StdinCloser is from https://github.com/containerd/containerd/blob/v1.4.3/cmd/ctr/commands/tasks/exec.go#L181-L194 199 type StdinCloser struct { 200 mu sync.Mutex 201 Stdin *os.File 202 Closer func() 203 closed bool 204 } 205 206 func (s *StdinCloser) Read(p []byte) (int, error) { 207 s.mu.Lock() 208 defer s.mu.Unlock() 209 if s.closed { 210 return 0, syscall.EBADF 211 } 212 n, err := s.Stdin.Read(p) 213 if err != nil { 214 if s.Closer != nil { 215 s.Closer() 216 s.closed = true 217 } 218 } 219 return n, err 220 } 221 222 // Close implements Closer 223 func (s *StdinCloser) Close() error { 224 s.mu.Lock() 225 defer s.mu.Unlock() 226 if s.closed { 227 return nil 228 } 229 if s.Closer != nil { 230 s.Closer() 231 } 232 s.closed = true 233 return nil 234 }