github.com/mgoltzsche/ctnr@v0.7.1-alpha/run/runcrunner/runccontainer.go (about)

     1  package runcrunner
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"sync"
    11  	"syscall"
    12  	"time"
    13  
    14  	exterrors "github.com/mgoltzsche/ctnr/pkg/errors"
    15  	"github.com/mgoltzsche/ctnr/pkg/log"
    16  	"github.com/mgoltzsche/ctnr/run"
    17  	"github.com/opencontainers/runtime-spec/specs-go"
    18  	"github.com/pkg/errors"
    19  )
    20  
    21  type RuncContainer struct {
    22  	io             run.ContainerIO
    23  	id             string
    24  	bundleDir      string
    25  	rootfs         string
    26  	noNewKeyring   bool
    27  	noPivot        bool
    28  	destroyOnClose bool
    29  	rootDir        string
    30  	process        *RuncProcess
    31  	created        bool
    32  	debug          log.FieldLogger
    33  	err            error
    34  }
    35  
    36  func NewRuncContainer(cfg *run.ContainerConfig, rootDir string, debug log.FieldLogger) (c *RuncContainer, err error) {
    37  	id := cfg.Id
    38  	if id == "" {
    39  		if id = cfg.Bundle.ID(); id == "" {
    40  			panic("no container ID provided and bundle ID is empty")
    41  		}
    42  	}
    43  
    44  	spec, err := cfg.Bundle.Spec()
    45  	if err != nil {
    46  		return nil, errors.Wrapf(err, "new container %q", id)
    47  	}
    48  
    49  	// TODO: handle config option destroyOnClose
    50  	c = &RuncContainer{
    51  		id:             id,
    52  		bundleDir:      cfg.Bundle.Dir(),
    53  		io:             cfg.Io,
    54  		rootfs:         filepath.Join(cfg.Bundle.Dir(), spec.Root.Path),
    55  		noPivot:        cfg.NoPivotRoot,
    56  		noNewKeyring:   cfg.NoNewKeyring,
    57  		destroyOnClose: cfg.DestroyOnClose,
    58  		rootDir:        rootDir,
    59  		debug:          debug.WithField("id", id),
    60  	}
    61  
    62  	// Create process
    63  	c.process = NewRuncProcess(c.runcCreateArgs("run", "--bundle", cfg.Bundle.Dir(), c.ID()), spec.Process.Terminal, cfg.Io, c.debug)
    64  	return
    65  }
    66  
    67  func (c *RuncContainer) Close() (err error) {
    68  	c.Stop()
    69  	err = c.Wait()
    70  	if c.destroyOnClose {
    71  		err = exterrors.Append(err, c.destroy())
    72  	}
    73  	return
    74  }
    75  
    76  func (c *RuncContainer) ID() string {
    77  	return c.id
    78  }
    79  
    80  func (c *RuncContainer) Rootfs() string {
    81  	return c.rootfs
    82  }
    83  
    84  func (c *RuncContainer) Start() (err error) {
    85  	c.debug.Println("Starting container")
    86  	return c.process.Start()
    87  }
    88  
    89  func (c *RuncContainer) runcCreateArgs(cmd string, a ...string) []string {
    90  	args := c.runcArgs(cmd)
    91  	if c.noPivot {
    92  		args = append(args, "--no-pivot")
    93  	}
    94  	if c.noNewKeyring {
    95  		args = append(args, "--no-new-keyring")
    96  	}
    97  	return append(args, a...)
    98  }
    99  
   100  func (c *RuncContainer) runcArgs(a ...string) []string {
   101  	return append(append(make([]string, 0, len(a)+3), "runc", "--root", c.rootDir), a...)
   102  }
   103  
   104  func (c *RuncContainer) Stop() {
   105  	c.process.Stop()
   106  }
   107  
   108  func (c *RuncContainer) Wait() error {
   109  	return c.process.Wait()
   110  }
   111  
   112  func (c *RuncContainer) Destroy() (err error) {
   113  	err = c.Close()
   114  	return exterrors.Append(err, c.destroy())
   115  }
   116  
   117  func (c *RuncContainer) destroy() (err error) {
   118  	c.debug.Println("Destroying container")
   119  
   120  	// Reset bundle expiry time
   121  	bundleDir := filepath.Join(c.Rootfs(), "..")
   122  	now := time.Now()
   123  	e := errors.Wrap(os.Chtimes(bundleDir, now, now), "reset bundle expiry time")
   124  	err = exterrors.Append(err, e)
   125  
   126  	// Destroy container
   127  	err = exterrors.Append(err, c.run("", "runc", "--root", c.rootDir, "delete", c.ID())) // TODO: Add --force option
   128  	return
   129  }
   130  
   131  func (c *RuncContainer) run(args ...string) (err error) {
   132  	var stderr bytes.Buffer
   133  	p := exec.Command(args[0], args[1:]...)
   134  	p.Stderr = &stderr
   135  	err = p.Run()
   136  	return errors.Wrapf(err, "exec %+v (stderr: %s)", args, stderr.String())
   137  }
   138  
   139  func (c *RuncContainer) Exec(process *specs.Process, io run.ContainerIO) (proc run.Process, err error) {
   140  	// Create container if not exists
   141  	exists, err := c.exists()
   142  	if err != nil {
   143  		return nil, errors.WithMessage(err, "exec")
   144  	}
   145  	if !exists {
   146  		if err = c.create(); err != nil {
   147  			return nil, err
   148  		}
   149  	}
   150  
   151  	// Start process
   152  	args := c.runcArgs("exec", "--cwd", process.Cwd)
   153  	if process.Terminal {
   154  		args = append(args, "-t")
   155  	}
   156  	args = append(args, "-u", fmt.Sprintf("%d:%d", process.User.UID, process.User.GID))
   157  	for _, envLine := range process.Env {
   158  		args = append(args, "-e", envLine)
   159  	}
   160  	if process.SelinuxLabel != "" {
   161  		args = append(args, "--process-label", process.SelinuxLabel)
   162  	}
   163  	if process.ApparmorProfile != "" {
   164  		args = append(args, "--apparmor", process.ApparmorProfile)
   165  	}
   166  	if process.NoNewPrivileges {
   167  		args = append(args, "--no-new-privs")
   168  	}
   169  	args = append(args, c.ID())
   170  	args = append(args, process.Args...)
   171  	p := NewRuncProcess(args, process.Terminal, io, c.debug)
   172  	return p, p.Start()
   173  }
   174  
   175  func (c *RuncContainer) create() (err error) {
   176  	c.debug.Println("Creating container")
   177  	args := c.runcCreateArgs("create", "--bundle", c.bundleDir, c.ID())
   178  	p := exec.Command(args[0], args[1:]...)
   179  	var wg sync.WaitGroup
   180  	stderr, err := p.StderrPipe()
   181  	if err != nil {
   182  		return
   183  	}
   184  	stdout, err := p.StdoutPipe()
   185  	if err != nil {
   186  		return
   187  	}
   188  	wg.Add(2)
   189  	go copyIO(stderr, os.Stderr, &wg)
   190  	go copyIO(stdout, os.Stdout, &wg)
   191  	if err = p.Run(); err == nil {
   192  		c.created = true
   193  	}
   194  	wg.Wait()
   195  	return
   196  }
   197  
   198  func copyIO(r io.ReadCloser, w io.Writer, wg *sync.WaitGroup) {
   199  	defer wg.Done()
   200  	io.Copy(w, r)
   201  	r.Close()
   202  }
   203  
   204  func (c *RuncContainer) exists() (r bool, err error) {
   205  	if c.created {
   206  		return true, nil
   207  	}
   208  	err = c.run(c.runcArgs("state", c.ID())...)
   209  	if exiterr, ok := errors.Cause(err).(*exec.ExitError); ok {
   210  		if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
   211  			switch status.ExitStatus() {
   212  			case 0:
   213  				return true, nil
   214  			case 1:
   215  				return false, nil
   216  			default:
   217  				return false, errors.New(exiterr.Error() + ", stderr: " + string(exiterr.Stderr))
   218  			}
   219  		}
   220  	}
   221  	return
   222  }