Don't clobber /etc/docker/config.json on debian updates.

PiperOrigin-RevId: 449537553
This commit is contained in:
Zach Koopmans
2022-05-18 11:51:28 -07:00
committed by gVisor bot
parent 1ccbffc235
commit 671bd7b0f8
4 changed files with 409 additions and 53 deletions
+1 -1
View File
@@ -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
+1
View File
@@ -82,6 +82,7 @@ go_test(
"delete_test.go",
"exec_test.go",
"gofer_test.go",
"install_test.go",
"mitigate_test.go",
],
data = [
+121 -52
View File
@@ -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 {
+286
View File
@@ -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)
}
})
}
}