*: split out rkt executable, add CLI framework

In preparation for adding other top-level rkt commands (other than
"run"), break out stage0 and add a simple framework for adding
commmands.
This commit is contained in:
Jonathan Boulle
2014-11-20 13:24:04 -08:00
parent b338b4945b
commit 28fff9c99d
6 changed files with 335 additions and 65 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ export GOPATH=${GOPATH}:${PWD}/gopath
eval $(go env)
echo "Building rkt (stage0)..."
go build -o $GOBIN/rkt ${REPO_PATH}/stage0
echo "Building rkt..."
go build -o $GOBIN/rkt ${REPO_PATH}/cmd
echo "Building init (stage1)..."
go build -o $GOBIN/init ${REPO_PATH}/stage1
+127
View File
@@ -0,0 +1,127 @@
package main
import (
"flag"
"fmt"
"os"
"strings"
"text/template"
"github.com/coreos-inc/rkt/rkt"
)
var (
cmdHelp = &Command{
Name: "help",
Summary: "Show a list of commands or help for one command",
Usage: "[COMMAND]",
Description: "Show a list of commands or detailed help for one command",
Run: runHelp,
}
globalUsageTemplate *template.Template
commandUsageTemplate *template.Template
templFuncs = template.FuncMap{
"descToLines": func(s string) []string {
// trim leading/trailing whitespace and split into slice of lines
return strings.Split(strings.Trim(s, "\n\t "), "\n")
},
"printOption": func(name, defvalue, usage string) string {
prefix := "--"
if len(name) == 1 {
prefix = "-"
}
return fmt.Sprintf("\t%s%s=%s\t%s", prefix, name, defvalue, usage)
},
}
)
func init() {
globalUsageTemplate = template.Must(template.New("global_usage").Funcs(templFuncs).Parse(`
NAME:
{{printf "\t%s - %s" .Executable .Description}}
USAGE:
{{printf "\t%s" .Executable}} [global options] <command> [command options] [arguments...]
VERSION:
{{printf "\t%s" .Version}}
COMMANDS:{{range .Commands}}
{{printf "\t%s\t%s" .Name .Summary}}{{end}}
GLOBAL OPTIONS:{{range .Flags}}
{{printOption .Name .DefValue .Usage}}{{end}}
Run "{{.Executable}} help <command>" for more details on a specific command.
`[1:]))
commandUsageTemplate = template.Must(template.New("command_usage").Funcs(templFuncs).Parse(`
NAME:
{{printf "\t%s - %s" .Cmd.Name .Cmd.Summary}}
USAGE:
{{printf "\t%s %s %s" .Executable .Cmd.Name .Cmd.Usage}}
DESCRIPTION:
{{range $line := descToLines .Cmd.Description}}{{printf "\t%s" $line}}
{{end}}
{{if .CmdFlags}}OPTIONS:{{range .CmdFlags}}
{{printOption .Name .DefValue .Usage}}{{end}}
{{end}}For help on global options run "{{.Executable}} help"
`[1:]))
}
func runHelp(args []string) (exit int) {
if len(args) < 1 {
printGlobalUsage()
return
}
var cmd *Command
for _, c := range commands {
if c.Name == args[0] {
cmd = c
break
}
}
if cmd == nil {
fmt.Fprintf(os.Stderr, "Unrecognized command: %s\n", args[0])
return 1
}
printCommandUsage(cmd)
return
}
func printGlobalUsage() {
globalUsageTemplate.Execute(out, struct {
Executable string
Commands []*Command
Flags []*flag.Flag
Description string
Version string
}{
cliName,
commands,
getAllFlags(),
cliDescription,
rkt.Version,
})
out.Flush()
}
func printCommandUsage(cmd *Command) {
commandUsageTemplate.Execute(out, struct {
Executable string
Cmd *Command
CmdFlags []*flag.Flag
}{
cliName,
cmd,
getFlags(&cmd.Flags),
})
out.Flush()
}
+93
View File
@@ -0,0 +1,93 @@
package main
import (
"flag"
"fmt"
"os"
"text/tabwriter"
)
const (
cliName = "rkt"
cliDescription = "rkt, the application container runner"
)
var (
globalFlagset = flag.NewFlagSet(cliName, flag.ExitOnError)
out *tabwriter.Writer
commands []*Command
globalFlags = struct {
Dir string
Debug bool
Help bool
}{}
)
func init() {
globalFlagset.BoolVar(&globalFlags.Help, "help", false, "Print usage information and exit")
globalFlagset.BoolVar(&globalFlags.Debug, "debug", false, "Print out more debug information to stderr")
globalFlagset.StringVar(&globalFlags.Dir, "dir", "", "rocket data directory")
}
type Command struct {
Name string // Name of the Command and the string to use to invoke it
Summary string // One-sentence summary of what the Command does
Usage string // Usage options/arguments
Description string // Detailed description of command
Flags flag.FlagSet // Set of flags associated with this command
Run func(args []string) int // Run a command with the given arguments, return exit status
}
func init() {
out = new(tabwriter.Writer)
out.Init(os.Stdout, 0, 8, 1, '\t', 0)
commands = []*Command{
cmdHelp,
cmdRun,
cmdVersion,
}
}
func main() {
// parse global arguments
globalFlagset.Parse(os.Args[1:])
args := globalFlagset.Args()
if len(args) < 1 || globalFlags.Help {
args = []string{"help"}
}
var cmd *Command
// determine which Command should be run
for _, c := range commands {
if c.Name == args[0] {
cmd = c
if err := c.Flags.Parse(args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "%v", err)
os.Exit(2)
}
break
}
}
if cmd == nil {
fmt.Fprintf(os.Stderr, "%v: unknown subcommand: %q\n", cliName, args[0])
fmt.Fprintf(os.Stderr, "Run '%v help' for usage.\n", cliName)
os.Exit(2)
}
os.Exit(cmd.Run(cmd.Flags.Args()))
}
func getAllFlags() (flags []*flag.Flag) {
return getFlags(globalFlagset)
}
func getFlags(flagset *flag.FlagSet) (flags []*flag.Flag) {
flags = make([]*flag.Flag, 0)
flagset.VisitAll(func(f *flag.Flag) {
flags = append(flags, f)
})
return
}
+71
View File
@@ -0,0 +1,71 @@
package main
import (
"errors"
"fmt"
"os"
"strings"
"github.com/coreos-inc/rkt/stage0"
)
var (
flagStage1Init string
flagStage1Rootfs string
flagVolumes volumeMap
cmdRun = &Command{
Name: "run",
Summary: "Run image(s) in an application container in rocket",
Usage: "[--volume LABEL:SOURCE] IMAGE...",
Run: runRun,
}
)
func init() {
cmdRun.Flags.StringVar(&flagStage1Init, "stage1-init", "./bin/init", "path to stage1 binary")
cmdRun.Flags.StringVar(&flagStage1Rootfs, "stage1-rootfs", "./stage1-rootfs.tar.gz", "path to stage1 rootfs tarball")
cmdRun.Flags.Var(&flagVolumes, "volume", "volumes to mount into the shared container environment")
flagVolumes = volumeMap{}
}
func runRun(args []string) (exit int) {
if len(args) < 1 {
fmt.Fprintf(os.Stderr, "run: Must provide at least one image\n")
return 1
}
cfg := stage0.Config{
RktDir: globalFlags.Dir,
Debug: globalFlags.Debug,
Stage1Init: flagStage1Init,
Stage1Rootfs: flagStage1Rootfs,
Images: args,
Volumes: flagVolumes,
}
stage0.Run(cfg) // execs, never returns
return 1
}
// volumeMap implements the flag.Value interface to contain a set of mappings
// from mount label --> mount path
type volumeMap map[string]string
func (vm *volumeMap) Set(s string) error {
elems := strings.Split(s, ":")
if len(elems) != 2 {
return errors.New("volume must be of form key:path")
}
key := elems[0]
if _, ok := (*vm)[key]; ok {
return fmt.Errorf("got multiple flags for volume %q", key)
}
(*vm)[key] = elems[1]
return nil
}
func (vm *volumeMap) String() string {
var ss []string
for k, v := range *vm {
ss = append(ss, fmt.Sprintf("%s:%s", k, v))
}
return strings.Join(ss, ",")
}
+19
View File
@@ -0,0 +1,19 @@
package main
import (
"fmt"
"github.com/coreos-inc/rkt/rkt"
)
var cmdVersion = &Command{
Name: "version",
Description: "Print the version and exit",
Summary: "Print the version and exit",
Run: runVersion,
}
func runVersion(args []string) (exit int) {
fmt.Printf("rkt version %s\n", rkt.Version)
return
}
+23 -63
View File
@@ -1,4 +1,4 @@
package main
package stage0
//
// Rocket is a reference implementation of the app container specification.
@@ -29,15 +29,12 @@ import (
"compress/gzip"
"crypto/sha256"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
"syscall"
// WARNING: here be dragons
@@ -48,42 +45,27 @@ import (
"github.com/coreos-inc/rkt/rkt"
)
var (
fs = flag.NewFlagSet("rkt", flag.ExitOnError)
flagDebug bool
flagDir string
flagStage1Init string
flagStage1Rfs string
flagVolumes volumeMap
)
type Config struct {
RktDir string
Stage1Init string
Stage1Rootfs string
Debug bool
Images []string
Volumes map[string]string
}
func init() {
log.SetOutput(ioutil.Discard)
fs.BoolVar(&flagDebug, "debug", false, "output debugging log information")
fs.StringVar(&flagDir, "dir", "", "directory in which to create container filesystem")
fs.StringVar(&flagStage1Init, "stage1-init", "./bin/init", "path to stage1 binary")
fs.StringVar(&flagStage1Rfs, "stage1-rootfs", "./stage1-rootfs.tar.gz", "path to stage1 rootfs tarball")
fs.Var(&flagVolumes, "volume", "volumes to mount into the shared container environment")
flagVolumes = volumeMap{}
}
func main() {
fs.Parse(os.Args[1:])
if flagDebug {
func Run(cfg Config) {
if cfg.Debug {
log.SetOutput(os.Stderr)
}
args := fs.Args()
if len(args) < 2 || args[0] != "run" {
fmt.Fprintf(os.Stderr, "usage: rkt run [OPTION]... IMAGE...\n")
os.Exit(0)
}
images := args[1:]
dir := flagDir
if dir == "" {
log.Printf("-dir unset - using temporary directory")
if cfg.RktDir == "" {
log.Printf("rktDir unset - using temporary directory")
var err error
dir, err = ioutil.TempDir("", "rkt")
cfg.RktDir, err = ioutil.TempDir("", "rkt")
if err != nil {
log.Fatalf("error creating temporary directory: %v", err)
}
@@ -95,13 +77,16 @@ func main() {
log.Fatalf("error creating UID: %v", err)
}
// Create a directory for this container
dir := filepath.Join(cfg.RktDir, cuid.String())
// - Creating a filesystem for the container
if err := os.MkdirAll(dir, 0700); err != nil {
log.Fatalf("error creating directory: %v", err)
}
log.Printf("Writing stage1 rootfs")
fh, err := os.Open(flagStage1Rfs)
fh, err := os.Open(cfg.Stage1Rootfs)
if err != nil {
log.Fatalf("error opening stage1 rootfs: %v", err)
}
@@ -118,7 +103,7 @@ func main() {
}
log.Printf("Writing stage1 init")
in, err := os.Open(flagStage1Init)
in, err := os.Open(cfg.Stage1Init)
if err != nil {
log.Fatalf("error loading stage1 binary: %v", err)
}
@@ -156,7 +141,7 @@ func main() {
// TODO(jonboulle): clarify imagehash<->appname. Right now we have to
// unpack the entire TAF to access the manifest which contains the appname.
for _, img := range images {
for _, img := range cfg.Images {
h, err := types.NewHash(img)
if err != nil {
log.Fatalf("bad hash given: %v", err)
@@ -224,7 +209,7 @@ func main() {
}
var sVols []types.Volume
for key, path := range flagVolumes {
for key, path := range cfg.Volumes {
v := types.Volume{
Kind: "host",
Source: path,
@@ -255,8 +240,8 @@ func main() {
log.Printf("Execing stage1/init")
init := "stage1/init"
args = []string{init}
if flagDebug {
args := []string{init}
if cfg.Debug {
args = append(args, "debug")
}
if err := syscall.Exec(init, args, os.Environ()); err != nil {
@@ -269,28 +254,3 @@ func main() {
func genUID() string {
return "6733C088-A507-4694-AABF-EDBE4FC5266F"
}
// volumeMap implements the flag.Value interface to contain a set of mappings
// from mount label --> mount path
type volumeMap map[string]string
func (vm *volumeMap) Set(s string) error {
elems := strings.Split(s, ":")
if len(elems) != 2 {
return errors.New("volume must be of form key:path")
}
key := elems[0]
if _, ok := (*vm)[key]; ok {
return fmt.Errorf("got multiple flags for volume %q", key)
}
(*vm)[key] = elems[1]
return nil
}
func (vm *volumeMap) String() string {
var ss []string
for k, v := range *vm {
ss = append(ss, fmt.Sprintf("%s:%s", k, v))
}
return strings.Join(ss, ",")
}