mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Don't clobber /etc/docker/config.json on debian updates.
PiperOrigin-RevId: 449537553
This commit is contained in:
committed by
gVisor bot
parent
1ccbffc235
commit
671bd7b0f8
Vendored
+1
-1
@@ -20,7 +20,7 @@ fi
|
||||
|
||||
# Update docker configuration.
|
||||
if [ -f /etc/docker/daemon.json ]; then
|
||||
runsc install
|
||||
runsc install --clobber=false
|
||||
if systemctl is-active -q docker; then
|
||||
systemctl reload docker || echo "unable to reload docker; you must do so manually." >&2
|
||||
fi
|
||||
|
||||
@@ -82,6 +82,7 @@ go_test(
|
||||
"delete_test.go",
|
||||
"exec_test.go",
|
||||
"gofer_test.go",
|
||||
"install_test.go",
|
||||
"mitigate_test.go",
|
||||
],
|
||||
data = [
|
||||
|
||||
+121
-52
@@ -22,6 +22,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
"gvisor.dev/gvisor/pkg/sentry/platform"
|
||||
@@ -31,10 +32,13 @@ import (
|
||||
|
||||
// Install implements subcommands.Command.
|
||||
type Install struct {
|
||||
ConfigFile string
|
||||
Runtime string
|
||||
Experimental bool
|
||||
CgroupDriver string
|
||||
ConfigFile string
|
||||
Runtime string
|
||||
Experimental bool
|
||||
Clobber bool
|
||||
CgroupDriver string
|
||||
executablePath string
|
||||
runtimeArgs []string
|
||||
}
|
||||
|
||||
// Name implements subcommands.Command.Name.
|
||||
@@ -57,17 +61,18 @@ func (*Install) Usage() string {
|
||||
func (i *Install) SetFlags(fs *flag.FlagSet) {
|
||||
fs.StringVar(&i.ConfigFile, "config_file", "/etc/docker/daemon.json", "path to Docker daemon config file")
|
||||
fs.StringVar(&i.Runtime, "runtime", "runsc", "runtime name")
|
||||
fs.BoolVar(&i.Experimental, "experimental", false, "enable experimental features")
|
||||
fs.BoolVar(&i.Experimental, "experimental", false, "enable/disable experimental features")
|
||||
fs.BoolVar(&i.Clobber, "clobber", true, "clobber existing runtime configuration")
|
||||
fs.StringVar(&i.CgroupDriver, "cgroupdriver", "", "docker cgroup driver")
|
||||
}
|
||||
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
// Grab the name and arguments.
|
||||
runtimeArgs := f.Args()
|
||||
i.runtimeArgs = f.Args()
|
||||
testFlags := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
config.RegisterFlags(testFlags)
|
||||
testFlags.Parse(runtimeArgs)
|
||||
testFlags.Parse(i.runtimeArgs)
|
||||
conf, err := config.NewFromFlags(testFlags)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid runtime arguments: %v", err)
|
||||
@@ -92,10 +97,34 @@ func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
log.Fatalf("Error reading current exectuable: %v", err)
|
||||
}
|
||||
|
||||
i.executablePath = path
|
||||
|
||||
installRW := configReaderWriter{
|
||||
read: defaultReadConfig,
|
||||
write: defaultWriteConfig,
|
||||
}
|
||||
|
||||
if err := doInstallConfig(i, installRW); err != nil {
|
||||
log.Fatalf("Install failed: %v", err)
|
||||
}
|
||||
|
||||
// Success.
|
||||
log.Print("Successfully updated config.")
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func doInstallConfig(i *Install, rw configReaderWriter) error {
|
||||
// Load the configuration file.
|
||||
c, err := readConfig(i.ConfigFile)
|
||||
configBytes, err := rw.read(i.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading config file %q: %v", i.ConfigFile, err)
|
||||
return fmt.Errorf("error reading config file %q: %v", i.ConfigFile, err)
|
||||
}
|
||||
// Unmarshal the configuration.
|
||||
c := make(map[string]interface{})
|
||||
if len(configBytes) > 0 {
|
||||
if err := json.Unmarshal(configBytes, &c); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the given runtime.
|
||||
@@ -106,12 +135,25 @@ func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
rts = make(map[string]interface{})
|
||||
c["runtimes"] = rts
|
||||
}
|
||||
rts[i.Runtime] = struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
RuntimeArgs []string `json:"runtimeArgs,omitempty"`
|
||||
}{
|
||||
Path: path,
|
||||
RuntimeArgs: runtimeArgs,
|
||||
updateRuntime := func() {
|
||||
rts[i.Runtime] = struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
RuntimeArgs []string `json:"runtimeArgs,omitempty"`
|
||||
}{
|
||||
Path: i.executablePath,
|
||||
RuntimeArgs: i.runtimeArgs,
|
||||
}
|
||||
}
|
||||
_, ok := rts[i.Runtime]
|
||||
switch {
|
||||
case !ok:
|
||||
log.Printf("Runtime %s not found: adding\n", i.Runtime)
|
||||
updateRuntime()
|
||||
case i.Clobber:
|
||||
log.Printf("Clobber is set. Overwriting runtime %s not found: adding\n", i.Runtime)
|
||||
updateRuntime()
|
||||
default:
|
||||
log.Printf("Not overwriting runtime %s\n", i.Runtime)
|
||||
}
|
||||
|
||||
// Set experimental if required.
|
||||
@@ -119,24 +161,38 @@ func (i *Install) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{})
|
||||
c["experimental"] = true
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`^native.cgroupdriver=`)
|
||||
// Set the cgroupdriver if required.
|
||||
if i.CgroupDriver != "" {
|
||||
v, ok := c["exec-opts"]
|
||||
if ok {
|
||||
opts := v.([]interface{})
|
||||
c["exec-opts"] = append(opts, fmt.Sprintf("native.cgroupdriver=%s", i.CgroupDriver))
|
||||
} else {
|
||||
if !ok {
|
||||
c["exec-opts"] = []string{fmt.Sprintf("native.cgroupdriver=%s", i.CgroupDriver)}
|
||||
} else {
|
||||
opts := v.([]interface{})
|
||||
newOpts := []interface{}{}
|
||||
for _, opt := range opts {
|
||||
if !i.Clobber {
|
||||
newOpts = opts
|
||||
break
|
||||
}
|
||||
o, ok := opt.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if !re.MatchString(o) {
|
||||
newOpts = append(newOpts, o)
|
||||
}
|
||||
}
|
||||
c["exec-opts"] = append(newOpts, fmt.Sprintf("native.cgroupdriver=%s", i.CgroupDriver))
|
||||
}
|
||||
}
|
||||
|
||||
// Write out the runtime.
|
||||
if err := writeConfig(c, i.ConfigFile); err != nil {
|
||||
log.Fatalf("Error writing config file %q: %v", i.ConfigFile, err)
|
||||
if err := rw.write(c, i.ConfigFile); err != nil {
|
||||
return fmt.Errorf("error writing config file %q: %v", i.ConfigFile, err)
|
||||
}
|
||||
|
||||
// Success.
|
||||
log.Printf("Added runtime %q with arguments %v to %q.", i.Runtime, runtimeArgs, i.ConfigFile)
|
||||
return subcommands.ExitSuccess
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uninstall implements subcommands.Command.
|
||||
@@ -170,48 +226,61 @@ func (u *Uninstall) SetFlags(fs *flag.FlagSet) {
|
||||
// Execute implements subcommands.Command.Execute.
|
||||
func (u *Uninstall) Execute(context.Context, *flag.FlagSet, ...interface{}) subcommands.ExitStatus {
|
||||
log.Printf("Removing runtime %q from %q.", u.Runtime, u.ConfigFile)
|
||||
|
||||
c, err := readConfig(u.ConfigFile)
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading config file %q: %v", u.ConfigFile, err)
|
||||
}
|
||||
|
||||
var rts map[string]interface{}
|
||||
if i, ok := c["runtimes"]; ok {
|
||||
rts = i.(map[string]interface{})
|
||||
} else {
|
||||
log.Fatalf("runtime %q not found", u.Runtime)
|
||||
}
|
||||
if _, ok := rts[u.Runtime]; !ok {
|
||||
log.Fatalf("runtime %q not found", u.Runtime)
|
||||
}
|
||||
delete(rts, u.Runtime)
|
||||
|
||||
if err := writeConfig(c, u.ConfigFile); err != nil {
|
||||
log.Fatalf("Error writing config file %q: %v", u.ConfigFile, err)
|
||||
if err := doUninstallConfig(u, configReaderWriter{
|
||||
read: defaultReadConfig,
|
||||
write: defaultWriteConfig,
|
||||
}); err != nil {
|
||||
log.Fatalf("Uninstall failed: %v", err)
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
|
||||
func readConfig(path string) (map[string]interface{}, error) {
|
||||
// Read the configuration data.
|
||||
configBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
func doUninstallConfig(u *Uninstall, rw configReaderWriter) error {
|
||||
configBytes, err := rw.read(u.ConfigFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading config file %q: %v", u.ConfigFile, err)
|
||||
}
|
||||
|
||||
// Unmarshal the configuration.
|
||||
c := make(map[string]interface{})
|
||||
if len(configBytes) > 0 {
|
||||
if err := json.Unmarshal(configBytes, &c); err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
var rts map[string]interface{}
|
||||
if i, ok := c["runtimes"]; ok {
|
||||
rts = i.(map[string]interface{})
|
||||
} else {
|
||||
return fmt.Errorf("runtime %q not found", u.Runtime)
|
||||
}
|
||||
if _, ok := rts[u.Runtime]; !ok {
|
||||
return fmt.Errorf("runtime %q not found", u.Runtime)
|
||||
}
|
||||
delete(rts, u.Runtime)
|
||||
|
||||
if err := rw.write(c, u.ConfigFile); err != nil {
|
||||
return fmt.Errorf("error writing config file %q: %v", u.ConfigFile, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeConfig(c map[string]interface{}, filename string) error {
|
||||
type configReaderWriter struct {
|
||||
read func(string) ([]byte, error)
|
||||
write func(map[string]interface{}, string) error
|
||||
}
|
||||
|
||||
func defaultReadConfig(path string) ([]byte, error) {
|
||||
// Read the configuration data.
|
||||
configBytes, err := ioutil.ReadFile(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
return configBytes, nil
|
||||
}
|
||||
|
||||
func defaultWriteConfig(c map[string]interface{}, filename string) error {
|
||||
// Marshal the configuration.
|
||||
b, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
type runtimeDef struct {
|
||||
path string
|
||||
runtimeArgs []string
|
||||
}
|
||||
|
||||
func (r *runtimeDef) MarshalJSON() ([]byte, error) {
|
||||
args, err := json.Marshal(r.runtimeArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
str := fmt.Sprintf(`{"path": "%s", "runtimeArgs":%s}`, r.path, args)
|
||||
return []byte(str), nil
|
||||
}
|
||||
|
||||
func (r *runtimeDef) UnmarshalJSON(data []byte) error {
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal(data, &dat); err != nil {
|
||||
return err
|
||||
}
|
||||
if p, ok := dat["path"]; ok {
|
||||
r.path = p.(string)
|
||||
}
|
||||
if p, ok := dat["runtimeArgs"]; ok {
|
||||
r.runtimeArgs = p.([]string)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var defaultInput = map[string]interface{}{
|
||||
"runtimes": map[string]*runtimeDef{
|
||||
"runtime1": &runtimeDef{
|
||||
path: "runtime1_path",
|
||||
runtimeArgs: []string{"some", "args"},
|
||||
},
|
||||
"other runtime": &runtimeDef{
|
||||
path: "other_runtime_path",
|
||||
runtimeArgs: []string{"some", "other", "args"},
|
||||
},
|
||||
"myRuntime": &runtimeDef{
|
||||
path: "myRuntimePath",
|
||||
runtimeArgs: []string{"super", "cool", "args"},
|
||||
},
|
||||
},
|
||||
"exec-opts": []string{"some-cgroup-driver=something", "native.cgroupdriver=init_driver"},
|
||||
}
|
||||
|
||||
func TestInstall(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
i *Install
|
||||
input map[string]interface{}
|
||||
output map[string]interface{}
|
||||
}{
|
||||
{
|
||||
name: "clobber",
|
||||
i: &Install{
|
||||
Runtime: "myRuntime",
|
||||
Experimental: true,
|
||||
Clobber: true,
|
||||
CgroupDriver: "my_driver",
|
||||
executablePath: "some_runsc_path",
|
||||
runtimeArgs: []string{"new", "cool", "args"},
|
||||
},
|
||||
input: defaultInput,
|
||||
output: map[string]interface{}{
|
||||
"runtimes": map[string]*runtimeDef{
|
||||
"runtime1": &runtimeDef{
|
||||
path: "runtime1_path",
|
||||
runtimeArgs: []string{"some", "args"},
|
||||
},
|
||||
"other runtime": &runtimeDef{
|
||||
path: "other_runtime_path",
|
||||
runtimeArgs: []string{"some", "other", "args"},
|
||||
},
|
||||
"myRuntime": &runtimeDef{
|
||||
path: "some_runsc_path",
|
||||
runtimeArgs: []string{"new", "cool", "args"},
|
||||
},
|
||||
},
|
||||
"exec-opts": []string{"some-cgroup-driver=something", "native.cgroupdriver=my_driver"},
|
||||
"experimental": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no clobber",
|
||||
i: &Install{
|
||||
Runtime: "myRuntime",
|
||||
Experimental: true,
|
||||
Clobber: false,
|
||||
CgroupDriver: "my_driver",
|
||||
executablePath: "some_runsc_path",
|
||||
runtimeArgs: []string{"new", "cool", "args"},
|
||||
},
|
||||
input: defaultInput,
|
||||
output: map[string]interface{}{
|
||||
"runtimes": map[string]*runtimeDef{
|
||||
"runtime1": &runtimeDef{
|
||||
path: "runtime1_path",
|
||||
runtimeArgs: []string{"some", "args"},
|
||||
},
|
||||
"other runtime": &runtimeDef{
|
||||
path: "other_runtime_path",
|
||||
runtimeArgs: []string{"some", "other", "args"},
|
||||
},
|
||||
"myRuntime": &runtimeDef{
|
||||
path: "myRuntimePath",
|
||||
runtimeArgs: []string{"super", "cool", "args"},
|
||||
},
|
||||
},
|
||||
"exec-opts": []string{"some-cgroup-driver=something", "native.cgroupdriver=init_driver", "native.cgroupdriver=my_driver"},
|
||||
"experimental": true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new runtime",
|
||||
i: &Install{
|
||||
Runtime: "newRuntime",
|
||||
Experimental: true,
|
||||
executablePath: "newPath",
|
||||
runtimeArgs: []string{"new", "cool", "args"},
|
||||
},
|
||||
input: defaultInput,
|
||||
output: map[string]interface{}{
|
||||
"runtimes": map[string]*runtimeDef{
|
||||
"runtime1": &runtimeDef{
|
||||
path: "runtime1_path",
|
||||
runtimeArgs: []string{"some", "args"},
|
||||
},
|
||||
"newRuntime": &runtimeDef{
|
||||
path: "newPath",
|
||||
runtimeArgs: []string{"new", "cool", "args"},
|
||||
},
|
||||
"other runtime": &runtimeDef{
|
||||
path: "other_runtime_path",
|
||||
runtimeArgs: []string{"some", "other", "args"},
|
||||
},
|
||||
"myRuntime": &runtimeDef{
|
||||
path: "myRuntimePath",
|
||||
runtimeArgs: []string{"super", "cool", "args"},
|
||||
},
|
||||
},
|
||||
"exec-opts": []string{"some-cgroup-driver=something", "native.cgroupdriver=init_driver"},
|
||||
"experimental": true,
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
||||
mockRead := func(_ string) ([]byte, error) {
|
||||
return json.MarshalIndent(tc.input, "", " ")
|
||||
}
|
||||
|
||||
got := []byte{}
|
||||
mockWrite := func(c map[string]interface{}, _ string) error {
|
||||
res, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
got = res
|
||||
return nil
|
||||
}
|
||||
|
||||
rw := configReaderWriter{
|
||||
read: mockRead,
|
||||
write: mockWrite,
|
||||
}
|
||||
|
||||
if err := doInstallConfig(tc.i, rw); err != nil {
|
||||
t.Fatalf("Error updating config: %v", err)
|
||||
}
|
||||
|
||||
want, err := json.MarshalIndent(tc.output, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal output: %v", err)
|
||||
}
|
||||
|
||||
if res := cmp.Diff(string(want), string(got)); res != "" {
|
||||
t.Fatalf("Mismatch output (-want +got): %s", res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUninstall(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
u *Uninstall
|
||||
input map[string]interface{}
|
||||
output map[string]interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "runtime found",
|
||||
u: &Uninstall{
|
||||
Runtime: "other runtime",
|
||||
},
|
||||
input: defaultInput,
|
||||
output: map[string]interface{}{
|
||||
"runtimes": map[string]*runtimeDef{
|
||||
"runtime1": &runtimeDef{
|
||||
path: "runtime1_path",
|
||||
runtimeArgs: []string{"some", "args"},
|
||||
},
|
||||
"myRuntime": &runtimeDef{
|
||||
path: "myRuntimePath",
|
||||
runtimeArgs: []string{"super", "cool", "args"},
|
||||
},
|
||||
},
|
||||
"exec-opts": []string{"some-cgroup-driver=something", "native.cgroupdriver=init_driver"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "runtime not found",
|
||||
u: &Uninstall{
|
||||
Runtime: "not found runtime",
|
||||
},
|
||||
input: defaultInput,
|
||||
wantErr: true,
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockRead := func(_ string) ([]byte, error) {
|
||||
return json.MarshalIndent(tc.input, "", " ")
|
||||
}
|
||||
|
||||
got := []byte{}
|
||||
mockWrite := func(c map[string]interface{}, _ string) error {
|
||||
res, err := json.MarshalIndent(c, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
got = res
|
||||
return nil
|
||||
}
|
||||
|
||||
rw := configReaderWriter{
|
||||
read: mockRead,
|
||||
write: mockWrite,
|
||||
}
|
||||
|
||||
err := doUninstallConfig(tc.u, rw)
|
||||
if tc.wantErr {
|
||||
if err == nil {
|
||||
t.Fatalf("Did not get an error when expected.")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Error updating config: %v", err)
|
||||
}
|
||||
|
||||
want, err := json.MarshalIndent(tc.output, "", " ")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal output: %v", err)
|
||||
}
|
||||
if res := cmp.Diff(string(want), string(got)); res != "" {
|
||||
t.Fatalf("Mismatch output (-want +got-): %s", res)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user