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  }