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