gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/cmd/install.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io/ioutil"
    22  	"log"
    23  	"os"
    24  	"path"
    25  	"regexp"
    26  
    27  	"github.com/google/subcommands"
    28  	"gvisor.dev/gvisor/pkg/sentry/platform"
    29  	"gvisor.dev/gvisor/runsc/config"
    30  	"gvisor.dev/gvisor/runsc/flag"
    31  )
    32  
    33  // Install implements subcommands.Command.
    34  type Install struct {
    35  	ConfigFile     string
    36  	Runtime        string
    37  	Experimental   bool
    38  	Clobber        bool
    39  	CgroupDriver   string
    40  	executablePath string
    41  	runtimeArgs    []string
    42  }
    43  
    44  // Name implements subcommands.Command.Name.
    45  func (*Install) Name() string {
    46  	return "install"
    47  }
    48  
    49  // Synopsis implements subcommands.Command.Synopsis.
    50  func (*Install) Synopsis() string {
    51  	return "adds a runtime to docker daemon configuration"
    52  }
    53  
    54  // Usage implements subcommands.Command.Usage.
    55  func (*Install) Usage() string {
    56  	return `install [flags] <name> [-- [args...]] -- if provided, args are passed to the runtime
    57  `
    58  }
    59  
    60  // SetFlags implements subcommands.Command.SetFlags.
    61  func (i *Install) SetFlags(fs *flag.FlagSet) {
    62  	fs.StringVar(&i.ConfigFile, "config_file", "/etc/docker/daemon.json", "path to Docker daemon config file")
    63  	fs.StringVar(&i.Runtime, "runtime", "runsc", "runtime name")
    64  	fs.BoolVar(&i.Experimental, "experimental", false, "enable/disable experimental features")
    65  	fs.BoolVar(&i.Clobber, "clobber", true, "clobber existing runtime configuration")
    66  	fs.StringVar(&i.CgroupDriver, "cgroupdriver", "", "docker cgroup driver")
    67  }
    68  
    69  // Execute implements subcommands.Command.Execute.
    70  func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...any) subcommands.ExitStatus {
    71  	// Grab the name and arguments.
    72  	i.runtimeArgs = f.Args()
    73  	testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
    74  	config.RegisterFlags(testFlags)
    75  	testFlags.Parse(i.runtimeArgs)
    76  	conf, err := config.NewFromFlags(testFlags)
    77  	if err != nil {
    78  		log.Fatalf("invalid runtime arguments: %v", err)
    79  	}
    80  
    81  	// Check the platform.
    82  	p, err := platform.Lookup(conf.Platform)
    83  	if err != nil {
    84  		log.Fatalf("invalid platform: %v", err)
    85  	}
    86  	deviceFile, err := p.OpenDevice(conf.PlatformDevicePath)
    87  	if err != nil {
    88  		log.Printf("WARNING: unable to open platform, runsc may fail to start: %v", err)
    89  	}
    90  	if deviceFile != nil {
    91  		deviceFile.Close()
    92  	}
    93  
    94  	// Extract the executable.
    95  	path, err := os.Executable()
    96  	if err != nil {
    97  		log.Fatalf("Error reading current exectuable: %v", err)
    98  	}
    99  
   100  	i.executablePath = path
   101  
   102  	installRW := configReaderWriter{
   103  		read:  defaultReadConfig,
   104  		write: defaultWriteConfig,
   105  	}
   106  
   107  	if err := doInstallConfig(i, installRW); err != nil {
   108  		log.Fatalf("Install failed: %v", err)
   109  	}
   110  
   111  	// Success.
   112  	log.Print("Successfully updated config.")
   113  	return subcommands.ExitSuccess
   114  }
   115  
   116  func doInstallConfig(i *Install, rw configReaderWriter) error {
   117  	// Load the configuration file.
   118  	configBytes, err := rw.read(i.ConfigFile)
   119  	if err != nil {
   120  		return fmt.Errorf("error reading config file %q: %v", i.ConfigFile, err)
   121  	}
   122  	// Unmarshal the configuration.
   123  	c := make(map[string]any)
   124  	if len(configBytes) > 0 {
   125  		if err := json.Unmarshal(configBytes, &c); err != nil {
   126  			return err
   127  		}
   128  	}
   129  
   130  	// Add the given runtime.
   131  	var rts map[string]any
   132  	if i, ok := c["runtimes"]; ok {
   133  		rts = i.(map[string]any)
   134  	} else {
   135  		rts = make(map[string]any)
   136  		c["runtimes"] = rts
   137  	}
   138  	updateRuntime := func() {
   139  		rts[i.Runtime] = struct {
   140  			Path        string   `json:"path,omitempty"`
   141  			RuntimeArgs []string `json:"runtimeArgs,omitempty"`
   142  		}{
   143  			Path:        i.executablePath,
   144  			RuntimeArgs: i.runtimeArgs,
   145  		}
   146  	}
   147  	_, ok := rts[i.Runtime]
   148  	switch {
   149  	case !ok:
   150  		log.Printf("Runtime %s not found: adding\n", i.Runtime)
   151  		updateRuntime()
   152  	case i.Clobber:
   153  		log.Printf("Clobber is set. Overwriting runtime %s not found: adding\n", i.Runtime)
   154  		updateRuntime()
   155  	default:
   156  		log.Printf("Not overwriting runtime %s\n", i.Runtime)
   157  	}
   158  
   159  	// Set experimental if required.
   160  	if i.Experimental {
   161  		c["experimental"] = true
   162  	}
   163  
   164  	re := regexp.MustCompile(`^native.cgroupdriver=`)
   165  	// Set the cgroupdriver if required.
   166  	if i.CgroupDriver != "" {
   167  		v, ok := c["exec-opts"]
   168  		if !ok {
   169  			c["exec-opts"] = []string{fmt.Sprintf("native.cgroupdriver=%s", i.CgroupDriver)}
   170  		} else {
   171  			opts := v.([]any)
   172  			newOpts := []any{}
   173  			for _, opt := range opts {
   174  				if !i.Clobber {
   175  					newOpts = opts
   176  					break
   177  				}
   178  				o, ok := opt.(string)
   179  				if !ok {
   180  					continue
   181  				}
   182  
   183  				if !re.MatchString(o) {
   184  					newOpts = append(newOpts, o)
   185  				}
   186  			}
   187  			c["exec-opts"] = append(newOpts, fmt.Sprintf("native.cgroupdriver=%s", i.CgroupDriver))
   188  		}
   189  	}
   190  
   191  	// Write out the runtime.
   192  	if err := rw.write(c, i.ConfigFile); err != nil {
   193  		return fmt.Errorf("error writing config file %q: %v", i.ConfigFile, err)
   194  	}
   195  	return nil
   196  }
   197  
   198  // Uninstall implements subcommands.Command.
   199  type Uninstall struct {
   200  	ConfigFile string
   201  	Runtime    string
   202  }
   203  
   204  // Name implements subcommands.Command.Name.
   205  func (*Uninstall) Name() string {
   206  	return "uninstall"
   207  }
   208  
   209  // Synopsis implements subcommands.Command.Synopsis.
   210  func (*Uninstall) Synopsis() string {
   211  	return "removes a runtime from docker daemon configuration"
   212  }
   213  
   214  // Usage implements subcommands.Command.Usage.
   215  func (*Uninstall) Usage() string {
   216  	return `uninstall [flags] <name>
   217  `
   218  }
   219  
   220  // SetFlags implements subcommands.Command.SetFlags.
   221  func (u *Uninstall) SetFlags(fs *flag.FlagSet) {
   222  	fs.StringVar(&u.ConfigFile, "config_file", "/etc/docker/daemon.json", "path to Docker daemon config file")
   223  	fs.StringVar(&u.Runtime, "runtime", "runsc", "runtime name")
   224  }
   225  
   226  // Execute implements subcommands.Command.Execute.
   227  func (u *Uninstall) Execute(context.Context, *flag.FlagSet, ...any) subcommands.ExitStatus {
   228  	log.Printf("Removing runtime %q from %q.", u.Runtime, u.ConfigFile)
   229  	if err := doUninstallConfig(u, configReaderWriter{
   230  		read:  defaultReadConfig,
   231  		write: defaultWriteConfig,
   232  	}); err != nil {
   233  		log.Fatalf("Uninstall failed: %v", err)
   234  	}
   235  	return subcommands.ExitSuccess
   236  }
   237  
   238  func doUninstallConfig(u *Uninstall, rw configReaderWriter) error {
   239  	configBytes, err := rw.read(u.ConfigFile)
   240  	if err != nil {
   241  		return fmt.Errorf("error reading config file %q: %v", u.ConfigFile, err)
   242  	}
   243  
   244  	// Unmarshal the configuration.
   245  	c := make(map[string]any)
   246  	if len(configBytes) > 0 {
   247  		if err := json.Unmarshal(configBytes, &c); err != nil {
   248  			return err
   249  		}
   250  	}
   251  
   252  	var rts map[string]any
   253  	if i, ok := c["runtimes"]; ok {
   254  		rts = i.(map[string]any)
   255  	} else {
   256  		return fmt.Errorf("runtime %q not found", u.Runtime)
   257  	}
   258  	if _, ok := rts[u.Runtime]; !ok {
   259  		return fmt.Errorf("runtime %q not found", u.Runtime)
   260  	}
   261  	delete(rts, u.Runtime)
   262  
   263  	if err := rw.write(c, u.ConfigFile); err != nil {
   264  		return fmt.Errorf("error writing config file %q: %v", u.ConfigFile, err)
   265  	}
   266  	return nil
   267  }
   268  
   269  type configReaderWriter struct {
   270  	read  func(string) ([]byte, error)
   271  	write func(map[string]any, string) error
   272  }
   273  
   274  func defaultReadConfig(path string) ([]byte, error) {
   275  	// Read the configuration data.
   276  	configBytes, err := ioutil.ReadFile(path)
   277  	if err != nil && !os.IsNotExist(err) {
   278  		return nil, err
   279  	}
   280  	return configBytes, nil
   281  }
   282  
   283  func defaultWriteConfig(c map[string]any, filename string) error {
   284  	// Marshal the configuration.
   285  	b, err := json.MarshalIndent(c, "", "    ")
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	// Copy the old configuration.
   291  	old, err := ioutil.ReadFile(filename)
   292  	if err != nil {
   293  		if !os.IsNotExist(err) {
   294  			return fmt.Errorf("error reading config file %q: %v", filename, err)
   295  		}
   296  	} else {
   297  		if err := ioutil.WriteFile(filename+"~", old, 0644); err != nil {
   298  			return fmt.Errorf("error backing up config file %q: %v", filename, err)
   299  		}
   300  	}
   301  
   302  	// Make the necessary directories.
   303  	if err := os.MkdirAll(path.Dir(filename), 0755); err != nil {
   304  		return fmt.Errorf("error creating config directory for %q: %v", filename, err)
   305  	}
   306  
   307  	// Write the new configuration.
   308  	if err := ioutil.WriteFile(filename, b, 0644); err != nil {
   309  		return fmt.Errorf("error writing config file %q: %v", filename, err)
   310  	}
   311  
   312  	return nil
   313  }