diff options
author | Fabricio Voznika <fvoznika@google.com> | 2019-04-11 17:53:24 -0700 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-04-11 17:54:34 -0700 |
commit | 546a1df7d15fd80f510d4203c5f9255bba4b4211 (patch) | |
tree | fd65630e6685d2952e6fc079bb81289d213ed728 | |
parent | 6b24f7ab0863004a30c2f1aff88440fbb4cf3b3c (diff) |
Add 'runsc do' command
It provides an easy way to run commands to quickly test gVisor.
By default it maps the host root as the container root with a
writable overlay on top (so the host root is not modified).
Example:
sudo runsc do ls -lh --color
sudo runsc do ~/src/test/my-test.sh
PiperOrigin-RevId: 243178711
Change-Id: I05f3d6ce253fe4b5f1362f4a07b5387f6ddb5dd9
-rw-r--r-- | runsc/cmd/BUILD | 1 | ||||
-rw-r--r-- | runsc/cmd/do.go | 157 | ||||
-rw-r--r-- | runsc/main.go | 7 | ||||
-rw-r--r-- | runsc/sandbox/sandbox.go | 2 |
4 files changed, 165 insertions, 2 deletions
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD index dabf18c5f..b7551a5ab 100644 --- a/runsc/cmd/BUILD +++ b/runsc/cmd/BUILD @@ -13,6 +13,7 @@ go_library( "create.go", "debug.go", "delete.go", + "do.go", "events.go", "exec.go", "gofer.go", diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go new file mode 100644 index 000000000..343461130 --- /dev/null +++ b/runsc/cmd/do.go @@ -0,0 +1,157 @@ +// Copyright 2018 Google LLC +// +// 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 ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "math/rand" + "os" + "path/filepath" + "syscall" + + "flag" + "github.com/google/subcommands" + specs "github.com/opencontainers/runtime-spec/specs-go" + "gvisor.googlesource.com/gvisor/pkg/log" + "gvisor.googlesource.com/gvisor/runsc/boot" + "gvisor.googlesource.com/gvisor/runsc/container" + "gvisor.googlesource.com/gvisor/runsc/specutils" +) + +// Do implements subcommands.Command for the "do" command. It sets up a simple +// sandbox and executes the command inside it. See Usage() for more details. +type Do struct { + root string + cwd string +} + +// Name implements subcommands.Command.Name. +func (*Do) Name() string { + return "do" +} + +// Synopsis implements subcommands.Command.Synopsis. +func (*Do) Synopsis() string { + return "Simplistic way to execute a command inside the sandbox. It's to be used for testing only." +} + +// Usage implements subcommands.Command.Usage. +func (*Do) Usage() string { + return `do [flags] <cmd> - runs a command. + +This command starts a sandbox with host filesystem mounted inside as readonly, +with a writable tmpfs overlay on top of it. The given command is executed inside +the sandbox. It's to be used to quickly test applications without having to +install or run docker. It doesn't give nearly as many options and it's to be +used for testing only. +` +} + +// SetFlags implements subcommands.Command.SetFlags. +func (c *Do) SetFlags(f *flag.FlagSet) { + f.StringVar(&c.root, "root", "/", `path to the root directory, defaults to "/"`) + f.StringVar(&c.cwd, "cwd", ".", `path to the current directory, defaults to the current directory`) +} + +// Execute implements subcommands.Command.Execute. +func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus { + if len(f.Args()) == 0 { + c.Usage() + return subcommands.ExitUsageError + } + + conf := args[0].(*boot.Config) + waitStatus := args[1].(*syscall.WaitStatus) + + // Map the entire host file system, but make it readonly with a writable + // overlay on top (ignore --overlay option). + conf.Overlay = true + + hostname, err := os.Hostname() + if err != nil { + Fatalf("Error to retrieve hostname: %v", err) + } + + absRoot, err := resolvePath(c.root) + if err != nil { + Fatalf("Error resolving root: %v", err) + } + absCwd, err := resolvePath(c.cwd) + if err != nil { + Fatalf("Error resolving current directory: %v", err) + } + + spec := &specs.Spec{ + Root: &specs.Root{ + Path: absRoot, + Readonly: true, + }, + Process: &specs.Process{ + Cwd: absCwd, + Args: f.Args(), + Env: os.Environ(), + Capabilities: specutils.AllCapabilities(), + }, + Hostname: hostname, + } + + specutils.LogSpec(spec) + + out, err := json.Marshal(spec) + if err != nil { + Fatalf("Error to marshal spec: %v", err) + } + tmpDir, err := ioutil.TempDir("", "runsc-do") + if err != nil { + Fatalf("Error to create tmp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + log.Infof("Changing configuration RootDir to %q", tmpDir) + conf.RootDir = tmpDir + + cfgPath := filepath.Join(tmpDir, "config.json") + if err := ioutil.WriteFile(cfgPath, out, 0755); err != nil { + Fatalf("Error write spec: %v", err) + } + + // No network support yet. + conf.Network = boot.NetworkNone + + id := fmt.Sprintf("runcs-do-%06d", rand.Int31n(1000000)) + ws, err := container.Run(id, spec, conf, tmpDir, "", "", "") + if err != nil { + Fatalf("running container: %v", err) + } + + *waitStatus = ws + return subcommands.ExitSuccess +} + +func resolvePath(path string) (string, error) { + var err error + path, err = filepath.Abs(path) + if err != nil { + return "", fmt.Errorf("resolving %q: %v", path, err) + } + path = filepath.Clean(path) + if err := syscall.Access(path, 0); err != nil { + return "", fmt.Errorf("unable to access %q: %v", path, err) + } + return path, nil +} diff --git a/runsc/main.go b/runsc/main.go index bbf08228c..74253a844 100644 --- a/runsc/main.go +++ b/runsc/main.go @@ -20,6 +20,7 @@ import ( "context" "fmt" "io" + "io/ioutil" "os" "path/filepath" "strings" @@ -80,6 +81,7 @@ func main() { subcommands.Register(new(cmd.Checkpoint), "") subcommands.Register(new(cmd.Create), "") subcommands.Register(new(cmd.Delete), "") + subcommands.Register(new(cmd.Do), "") subcommands.Register(new(cmd.Events), "") subcommands.Register(new(cmd.Exec), "") subcommands.Register(new(cmd.Gofer), "") @@ -168,6 +170,8 @@ func main() { log.SetLevel(log.Debug) } + subcommand := flag.CommandLine.Arg(0) + var logFile io.Writer = os.Stderr if *logFD > -1 { logFile = os.NewFile(uintptr(*logFD), "log file") @@ -180,11 +184,12 @@ func main() { cmd.Fatalf("error opening log file %q: %v", *logFilename, err) } logFile = f + } else if subcommand == "do" { + logFile = ioutil.Discard } e := newEmitter(*logFormat, logFile) - subcommand := flag.CommandLine.Arg(0) if *debugLogFD > -1 { f := os.NewFile(uintptr(*debugLogFD), "debug log file") diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index ae6375e13..92495c69e 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -104,7 +104,7 @@ func New(id string, spec *specs.Spec, conf *boot.Config, bundleDir, consoleSocke // Wait until the sandbox has booted. b := make([]byte, 1) if l, err := clientSyncFile.Read(b); err != nil || l != 1 { - return nil, fmt.Errorf("reading from the start-sync descriptor: %v", err) + return nil, fmt.Errorf("waiting for sandbox to start: %v", err) } c.Release() |