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 }