diff --git a/cli/kata-env.go b/cli/kata-env.go index d0daf3490f..150b51ca7f 100644 --- a/cli/kata-env.go +++ b/cli/kata-env.go @@ -6,6 +6,7 @@ package main import ( + "encoding/json" "errors" "os" "strings" @@ -328,28 +329,17 @@ func getEnvInfo(configFile string, config oci.RuntimeConfig) (env EnvInfo, err e return env, nil } -func showSettings(env EnvInfo, file *os.File) error { - encoder := toml.NewEncoder(file) - - err := encoder.Encode(env) - if err != nil { - return err - } - - return nil -} - -func handleSettings(file *os.File, metadata map[string]interface{}) error { +func handleSettings(file *os.File, c *cli.Context) error { if file == nil { return errors.New("Invalid output file specified") } - configFile, ok := metadata["configFile"].(string) + configFile, ok := c.App.Metadata["configFile"].(string) if !ok { return errors.New("cannot determine config file") } - runtimeConfig, ok := metadata["runtimeConfig"].(oci.RuntimeConfig) + runtimeConfig, ok := c.App.Metadata["runtimeConfig"].(oci.RuntimeConfig) if !ok { return errors.New("cannot determine runtime config") } @@ -359,13 +349,48 @@ func handleSettings(file *os.File, metadata map[string]interface{}) error { return err } - return showSettings(env, file) + if c.Bool("json") { + return writeJSONSettings(env, file) + } + + return writeTOMLSettings(env, file) +} + +func writeTOMLSettings(env EnvInfo, file *os.File) error { + encoder := toml.NewEncoder(file) + + err := encoder.Encode(env) + if err != nil { + return err + } + + return nil +} + +func writeJSONSettings(env EnvInfo, file *os.File) error { + encoder := json.NewEncoder(file) + + // Make it more human readable + encoder.SetIndent("", " ") + + err := encoder.Encode(env) + if err != nil { + return err + } + + return nil } var kataEnvCLICommand = cli.Command{ Name: envCmd, - Usage: "display settings", + Usage: "display settings. Default to TOML", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "json", + Usage: "Format output as JSON", + }, + }, Action: func(context *cli.Context) error { - return handleSettings(defaultOutputFile, context.App.Metadata) + return handleSettings(defaultOutputFile, context) }, } diff --git a/cli/kata-env_test.go b/cli/kata-env_test.go index d8a9100cd8..cf285c7c40 100644 --- a/cli/kata-env_test.go +++ b/cli/kata-env_test.go @@ -7,6 +7,8 @@ package main import ( "bytes" + "encoding/json" + "flag" "fmt" "io/ioutil" "os" @@ -668,7 +670,7 @@ func TestEnvGetAgentInfo(t *testing.T) { assert.Equal(t, expectedAgent, agent) } -func testEnvShowSettings(t *testing.T, tmpdir string, tmpfile *os.File) error { +func testEnvShowTOMLSettings(t *testing.T, tmpdir string, tmpfile *os.File) error { runtime := RuntimeInfo{} @@ -717,7 +719,7 @@ func testEnvShowSettings(t *testing.T, tmpdir string, tmpfile *os.File) error { Host: expectedHostDetails, } - err = showSettings(env, tmpfile) + err = writeTOMLSettings(env, tmpfile) if err != nil { return err } @@ -737,6 +739,77 @@ func testEnvShowSettings(t *testing.T, tmpdir string, tmpfile *os.File) error { return nil } +func testEnvShowJSONSettings(t *testing.T, tmpdir string, tmpfile *os.File) error { + + runtime := RuntimeInfo{} + + hypervisor := HypervisorInfo{ + Path: "/resolved/hypervisor/path", + MachineType: "hypervisor-machine-type", + } + + image := ImageInfo{ + Path: "/resolved/image/path", + } + + kernel := KernelInfo{ + Path: "/kernel/path", + Parameters: "foo=bar xyz", + } + + proxy := ProxyInfo{ + Type: "proxy-type", + Version: "proxy-version", + Path: "file:///proxy-url", + Debug: false, + } + + shim := ShimInfo{ + Type: "shim-type", + Version: "shim-version", + Path: "/resolved/shim/path", + } + + agent := AgentInfo{ + Type: "agent-type", + } + + expectedHostDetails, err := getExpectedHostDetails(tmpdir) + assert.NoError(t, err) + + env := EnvInfo{ + Runtime: runtime, + Hypervisor: hypervisor, + Image: image, + Kernel: kernel, + Proxy: proxy, + Shim: shim, + Agent: agent, + Host: expectedHostDetails, + } + + err = writeJSONSettings(env, tmpfile) + if err != nil { + return err + } + + contents, err := getFileContents(tmpfile.Name()) + assert.NoError(t, err) + + buf := new(bytes.Buffer) + encoder := json.NewEncoder(buf) + // Ensure we have the same human readable layout + encoder.SetIndent("", " ") + err = encoder.Encode(env) + assert.NoError(t, err) + + expectedContents := buf.String() + + assert.Equal(t, expectedContents, contents) + + return nil +} + func TestEnvShowSettings(t *testing.T) { tmpdir, err := ioutil.TempDir("", "") if err != nil { @@ -748,7 +821,13 @@ func TestEnvShowSettings(t *testing.T) { assert.NoError(t, err) defer os.Remove(tmpfile.Name()) - err = testEnvShowSettings(t, tmpdir, tmpfile) + err = testEnvShowTOMLSettings(t, tmpdir, tmpfile) + assert.NoError(t, err) + + // Reset the file to empty for next test + tmpfile.Truncate(0) + tmpfile.Seek(0, 0) + err = testEnvShowJSONSettings(t, tmpdir, tmpfile) assert.NoError(t, err) } @@ -761,11 +840,18 @@ func TestEnvShowSettingsInvalidFile(t *testing.T) { tmpfile, err := ioutil.TempFile("", "envShowSettings-") assert.NoError(t, err) + defer os.Remove(tmpfile.Name()) // close the file tmpfile.Close() - err = testEnvShowSettings(t, tmpdir, tmpfile) + err = testEnvShowTOMLSettings(t, tmpdir, tmpfile) + assert.Error(t, err) + + // Reset the file to empty for next test + tmpfile.Truncate(0) + tmpfile.Seek(0, 0) + err = testEnvShowJSONSettings(t, tmpdir, tmpfile) assert.Error(t, err) } @@ -782,7 +868,11 @@ func TestEnvHandleSettings(t *testing.T) { _, err = getExpectedSettings(config, tmpdir, configFile) assert.NoError(t, err) - m := map[string]interface{}{ + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ "configFile": configFile, "runtimeConfig": config, } @@ -791,7 +881,7 @@ func TestEnvHandleSettings(t *testing.T) { assert.NoError(t, err) defer os.Remove(tmpfile.Name()) - err = handleSettings(tmpfile, m) + err = handleSettings(tmpfile, ctx) assert.NoError(t, err) var env EnvInfo @@ -815,7 +905,10 @@ func TestEnvHandleSettingsInvalidShimConfig(t *testing.T) { config.ShimConfig = "invalid shim config" - m := map[string]interface{}{ + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ "configFile": configFile, "runtimeConfig": config, } @@ -824,47 +917,75 @@ func TestEnvHandleSettingsInvalidShimConfig(t *testing.T) { assert.NoError(err) defer os.Remove(tmpfile.Name()) - err = handleSettings(tmpfile, m) + err = handleSettings(tmpfile, ctx) assert.Error(err) } func TestEnvHandleSettingsInvalidParams(t *testing.T) { - err := handleSettings(nil, map[string]interface{}{}) - assert.Error(t, err) + assert := assert.New(t) + + tmpdir, err := ioutil.TempDir("", "") + assert.NoError(err) + defer os.RemoveAll(tmpdir) + + configFile, _, err := makeRuntimeConfig(tmpdir) + assert.NoError(err) + + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ + "configFile": configFile, + } + err = handleSettings(nil, ctx) + assert.Error(err) } func TestEnvHandleSettingsEmptyMap(t *testing.T) { - err := handleSettings(os.Stdout, map[string]interface{}{}) + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{} + err := handleSettings(os.Stdout, ctx) assert.Error(t, err) } func TestEnvHandleSettingsInvalidFile(t *testing.T) { - m := map[string]interface{}{ + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ "configFile": "foo", "runtimeConfig": oci.RuntimeConfig{}, } - err := handleSettings(nil, m) + err := handleSettings(nil, ctx) assert.Error(t, err) } func TestEnvHandleSettingsInvalidConfigFileType(t *testing.T) { - m := map[string]interface{}{ + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ "configFile": 123, "runtimeConfig": oci.RuntimeConfig{}, } - err := handleSettings(os.Stderr, m) + err := handleSettings(os.Stderr, ctx) assert.Error(t, err) } func TestEnvHandleSettingsInvalidRuntimeConfigType(t *testing.T) { - m := map[string]interface{}{ + app := cli.NewApp() + ctx := cli.NewContext(app, nil, nil) + app.Name = "foo" + ctx.App.Metadata = map[string]interface{}{ "configFile": "/some/where", "runtimeConfig": true, } - err := handleSettings(os.Stderr, m) + err := handleSettings(os.Stderr, ctx) assert.Error(t, err) } @@ -882,7 +1003,8 @@ func TestEnvCLIFunction(t *testing.T) { assert.NoError(t, err) app := cli.NewApp() - ctx := cli.NewContext(app, nil, nil) + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) app.Name = "foo" ctx.App.Metadata = map[string]interface{}{ @@ -906,6 +1028,12 @@ func TestEnvCLIFunction(t *testing.T) { err = fn(ctx) assert.NoError(t, err) + + set.Bool("json", true, "") + ctx = cli.NewContext(app, set, nil) + + err = fn(ctx) + assert.NoError(t, err) } func TestEnvCLIFunctionFail(t *testing.T) {