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

     1  package librunner
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"time"
     9  
    10  	exterrors "github.com/mgoltzsche/ctnr/pkg/errors"
    11  	"github.com/mgoltzsche/ctnr/pkg/log"
    12  	"github.com/mgoltzsche/ctnr/run"
    13  	"github.com/opencontainers/runc/libcontainer"
    14  	_ "github.com/opencontainers/runc/libcontainer/nsenter"
    15  	"github.com/opencontainers/runc/libcontainer/specconv"
    16  	"github.com/opencontainers/runtime-spec/specs-go"
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  func init() {
    21  	if len(os.Args) > 1 && os.Args[1] == "init" {
    22  		// Initializes the previously created container in this new process
    23  		runtime.GOMAXPROCS(1)
    24  		runtime.LockOSThread()
    25  		factory, _ := libcontainer.New("")
    26  		if err := factory.StartInitialization(); err != nil {
    27  			fmt.Fprintf(os.Stderr, "Error: libcontainer factory initialization: %s\n", err)
    28  			os.Exit(1)
    29  		}
    30  		panic("factory initialization should block further execution - this should never be executed")
    31  	}
    32  }
    33  
    34  type Container struct {
    35  	process        *Process
    36  	container      libcontainer.Container
    37  	destroyOnClose bool
    38  	log            log.Loggers
    39  }
    40  
    41  func LoadContainer(id string, factory libcontainer.Factory, loggers log.Loggers) (*Container, error) {
    42  	c, err := factory.Load(id)
    43  	return &Container{
    44  		container: c,
    45  		log:       loggers,
    46  	}, err
    47  }
    48  
    49  func NewContainer(cfg *run.ContainerConfig, rootless bool, factory libcontainer.Factory, loggers log.Loggers) (r *Container, err error) {
    50  	id := cfg.Id
    51  	if id == "" {
    52  		if id = cfg.Bundle.ID(); id == "" {
    53  			panic("no container ID provided and bundle ID is empty")
    54  		}
    55  	}
    56  
    57  	runtime.LockOSThread()
    58  	defer runtime.UnlockOSThread()
    59  
    60  	loggers = loggers.WithField("id", id)
    61  	loggers.Debug.Println("Creating container")
    62  
    63  	spec, err := cfg.Bundle.Spec()
    64  	if err != nil {
    65  		return nil, errors.Wrap(err, "new container")
    66  	}
    67  	if spec.Process == nil {
    68  		return nil, errors.Errorf("new container %s: bundle spec declares no process to run", id)
    69  	}
    70  	orgwd, err := os.Getwd()
    71  	if err != nil {
    72  		return nil, errors.Wrap(err, "new container")
    73  	}
    74  
    75  	// Must change to bundle dir because CreateLibcontainerConfig assumes it is in the bundle directory
    76  	if err = os.Chdir(cfg.Bundle.Dir()); err != nil {
    77  		return nil, errors.New("new container: change to bundle directory: " + err.Error())
    78  	}
    79  	defer func() {
    80  		if e := os.Chdir(orgwd); e != nil {
    81  			e = errors.New("change back from bundle to previous directory: " + e.Error())
    82  			err = exterrors.Append(err, e)
    83  		}
    84  	}()
    85  
    86  	config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
    87  		CgroupName:       id,
    88  		UseSystemdCgroup: false, // TODO: expose as option
    89  		NoPivotRoot:      cfg.NoPivotRoot,
    90  		NoNewKeyring:     cfg.NoNewKeyring,
    91  		Spec:             spec,
    92  		Rootless:         rootless,
    93  	})
    94  	if err != nil {
    95  		return nil, errors.Wrap(err, "create container config")
    96  	}
    97  	if spec.Root != nil {
    98  		if filepath.IsAbs(spec.Root.Path) {
    99  			config.Rootfs = spec.Root.Path
   100  		} else {
   101  			config.Rootfs = filepath.Join(cfg.Bundle.Dir(), spec.Root.Path)
   102  		}
   103  	}
   104  	container, err := factory.Create(id, config)
   105  	if err != nil {
   106  		return nil, errors.Wrap(err, "create container")
   107  	}
   108  
   109  	r = &Container{
   110  		container:      container,
   111  		destroyOnClose: cfg.DestroyOnClose,
   112  		log:            loggers,
   113  	}
   114  	r.process, err = NewProcess(r, spec.Process, cfg.Io, loggers)
   115  	err = errors.Wrap(err, "configure container process")
   116  	return
   117  }
   118  
   119  func (c *Container) ID() string {
   120  	return c.container.ID()
   121  }
   122  
   123  func (c *Container) Rootfs() string {
   124  	return c.container.Config().Rootfs
   125  }
   126  
   127  // Prepare and start the container process from spec and with stdio
   128  func (c *Container) Start() (err error) {
   129  	c.log.Debug.Println("Starting container")
   130  	return c.process.Start()
   131  }
   132  
   133  func (c *Container) Stop() {
   134  	c.log.Debug.Println("Stopping container")
   135  	if p := c.process; p != nil {
   136  		p.Stop()
   137  	}
   138  }
   139  
   140  func (c *Container) Exec(process *specs.Process, io run.ContainerIO) (proc run.Process, err error) {
   141  	p, err := NewProcess(c, process, io, c.log)
   142  	err = p.Start()
   143  	return p, err
   144  }
   145  
   146  // Waits for the container process to terminate and returns the process' error if any
   147  func (c *Container) Wait() (err error) {
   148  	if p := c.process; p != nil {
   149  		err = p.Wait()
   150  	}
   151  	return
   152  }
   153  
   154  func (c *Container) Destroy() (err error) {
   155  	c.log.Debug.Println("Destroying container")
   156  	cc := c.container
   157  	if cc != nil {
   158  		err = exterrors.Append(err, cc.Destroy())
   159  		c.container = nil
   160  	}
   161  	return
   162  }
   163  
   164  func (c *Container) Close() (err error) {
   165  	c.log.Debug.Println("Closing container")
   166  
   167  	// Reset bundle expiry time
   168  	bundleDir := filepath.Join(c.Rootfs(), "..")
   169  	now := time.Now()
   170  	e := errors.Wrap(os.Chtimes(bundleDir, now, now), "reset bundle expiry time")
   171  	err = exterrors.Append(err, e)
   172  
   173  	// Close process
   174  	p := c.process
   175  	if p != nil {
   176  		err = p.Close()
   177  		c.process = nil
   178  	}
   179  
   180  	// Destroy container
   181  	if c.destroyOnClose {
   182  		err = exterrors.Append(err, c.Destroy())
   183  	}
   184  
   185  	return
   186  }