Add --json to pulumi config get and pulumi config

This supports using `--json` to get configuration information in a
structured way.

The objects we return have the following schema:

```
{
    name: string;
    value: string?;
    secret: bool;
}
```

In the case of `pulumi config` when --show-secrets is not passed, and
there are secret values, the `value` property of the object for that
configuration value will not be set. This differs from the normal
rendering where we show `[secret]`.

Contributes To #496
This commit is contained in:
Matt Ellis 2019-01-18 13:54:56 -08:00
parent 0de4294fc7
commit 5cfd44c73a
6 changed files with 87 additions and 19 deletions

View file

@ -2,6 +2,8 @@
### Improvements
- Add `--JSON` to `pulumi config` and `pulumi config get` to request the output be in JSON.
## 0.16.11 (Released January 16th, 2019)
### Improvements

View file

@ -15,6 +15,7 @@
package cmd
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
@ -38,6 +39,7 @@ import (
func newConfigCmd() *cobra.Command {
var stack string
var showSecrets bool
var jsonOut bool
cmd := &cobra.Command{
Use: "config",
@ -56,13 +58,16 @@ func newConfigCmd() *cobra.Command {
return err
}
return listConfig(stack, showSecrets)
return listConfig(stack, showSecrets, jsonOut)
}),
}
cmd.Flags().BoolVar(
&showSecrets, "show-secrets", false,
"Show secret values when listing config instead of displaying blinded values")
cmd.Flags().BoolVarP(
&jsonOut, "json", "j", false,
"Emit output as JSON")
cmd.PersistentFlags().StringVarP(
&stack, "stack", "s", "",
"The name of the stack to operate on. Defaults to the current stack")
@ -79,6 +84,8 @@ func newConfigCmd() *cobra.Command {
}
func newConfigGetCmd(stack *string) *cobra.Command {
var jsonOut bool
getCmd := &cobra.Command{
Use: "get <key>",
Short: "Get a single configuration value",
@ -98,9 +105,12 @@ func newConfigGetCmd(stack *string) *cobra.Command {
return errors.Wrap(err, "invalid configuration key")
}
return getConfig(s, key)
return getConfig(s, key, jsonOut)
}),
}
getCmd.Flags().BoolVarP(
&jsonOut, "json", "j", false,
"Emit output as JSON")
return getCmd
}
@ -361,7 +371,16 @@ func prettyKeyForProject(k config.Key, proj *workspace.Project) string {
return fmt.Sprintf("%s:%s", k.Namespace(), k.Name())
}
func listConfig(stack backend.Stack, showSecrets bool) error {
// configValueJSON is the shape of the --json output for a configuration value. While we can add fields to this
// structure in the future, we should not change existing fields.
type configValueJSON struct {
Name string `json:"name"`
// When the value is encrypted and --show-secrets was not passed, the value will not be set.
Value *string `json:"value,omitempty"`
Secret bool `json:"secret"`
}
func listConfig(stack backend.Stack, showSecrets bool, jsonOut bool) error {
ps, err := loadProjectStack(stack)
if err != nil {
return err
@ -369,7 +388,7 @@ func listConfig(stack backend.Stack, showSecrets bool) error {
cfg := ps.Config
// By default, we will use a blinding decrypter to show '******'. If requested, display secrets in plaintext.
// By default, we will use a blinding decrypter to show "[secret]". If requested, display secrets in plaintext.
var decrypter config.Decrypter
if cfg.HasSecureValue() && showSecrets {
decrypter, err = backend.GetStackCrypter(stack)
@ -388,24 +407,55 @@ func listConfig(stack backend.Stack, showSecrets bool) error {
}
sort.Sort(keys)
rows := []cmdutil.TableRow{}
for _, key := range keys {
decrypted, err := cfg[key].Value(decrypter)
if jsonOut {
var configValues []configValueJSON
for _, key := range keys {
entry := configValueJSON{
Name: key.String(),
Secret: cfg[key].Secure(),
}
decrypted, err := cfg[key].Value(decrypter)
if err != nil {
return errors.Wrap(err, "could not decrypt configuration value")
}
entry.Value = &decrypted
// If the value was a secret value and we aren't showing secrets, then the above would have set value
// to "[secret]" which is reasonable when printing for human display, but for our JSON output, we'd rather
// just elide the value.
if cfg[key].Secure() && !showSecrets {
entry.Value = nil
}
configValues = append(configValues, entry)
}
out, err := json.MarshalIndent(configValues, "", " ")
if err != nil {
return errors.Wrap(err, "could not decrypt configuration value")
return err
}
fmt.Println(string(out))
} else {
rows := []cmdutil.TableRow{}
for _, key := range keys {
decrypted, err := cfg[key].Value(decrypter)
if err != nil {
return errors.Wrap(err, "could not decrypt configuration value")
}
rows = append(rows, cmdutil.TableRow{Columns: []string{prettyKey(key), decrypted}})
}
rows = append(rows, cmdutil.TableRow{Columns: []string{prettyKey(key), decrypted}})
cmdutil.PrintTable(cmdutil.Table{
Headers: []string{"KEY", "VALUE"},
Rows: rows,
})
}
cmdutil.PrintTable(cmdutil.Table{
Headers: []string{"KEY", "VALUE"},
Rows: rows,
})
return nil
}
func getConfig(stack backend.Stack, key config.Key) error {
func getConfig(stack backend.Stack, key config.Key, jsonOut bool) error {
ps, err := loadProjectStack(stack)
if err != nil {
return err
@ -427,7 +477,23 @@ func getConfig(stack backend.Stack, key config.Key) error {
if err != nil {
return errors.Wrap(err, "could not decrypt configuration value")
}
fmt.Printf("%v\n", raw)
if jsonOut {
value := configValueJSON{
Name: key.String(),
Value: &raw,
Secret: v.Secure(),
}
out, err := json.MarshalIndent(value, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
} else {
fmt.Printf("%v\n", raw)
}
return nil
}

View file

@ -148,7 +148,7 @@ func newLogsCmd() *cobra.Command {
&stackConfigFile, "config-file", "",
"Use the configuration values in the specified file rather than detecting the file name")
logsCmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit outputs as JSON")
&jsonOut, "json", "j", false, "Emit output as JSON")
logsCmd.PersistentFlags().BoolVarP(
&follow, "follow", "f", false,
"Follow the log stream in real time (like tail -f)")

View file

@ -87,7 +87,7 @@ func newStackLsCmd() *cobra.Command {
}),
}
cmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit outputs as JSON")
&jsonOut, "json", "j", false, "Emit output as JSON")
cmd.PersistentFlags().BoolVarP(
&allStacks, "all", "a", false, "List all stacks instead of just stacks for the current project")

View file

@ -84,7 +84,7 @@ func newStackOutputCmd() *cobra.Command {
}
cmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit outputs as JSON")
&jsonOut, "json", "j", false, "Emit output as JSON")
cmd.PersistentFlags().StringVarP(
&stackName, "stack", "s", "", "The name of the stack to operate on. Defaults to the current stack")

View file

@ -115,7 +115,7 @@ func newStackTagLsCmd(stack *string) *cobra.Command {
}
cmd.PersistentFlags().BoolVarP(
&jsonOut, "json", "j", false, "Emit stack tags as JSON")
&jsonOut, "json", "j", false, "Emit output as JSON")
return cmd
}