package command import ( "encoding/json" "errors" "fmt" "sort" "strconv" "strings" "sync" "time" "github.com/ghodss/yaml" "github.com/hashicorp/vault/api" "github.com/mitchellh/cli" "github.com/posener/complete" "github.com/ryanuber/columnize" ) var predictFormat complete.Predictor = complete.PredictSet("json", "yaml") func OutputSecret(ui cli.Ui, format string, secret *api.Secret) int { return outputWithFormat(ui, format, secret, secret) } func OutputList(ui cli.Ui, format string, secret *api.Secret) int { return outputWithFormat(ui, format, secret, secret.Data["keys"]) } func outputWithFormat(ui cli.Ui, format string, secret *api.Secret, data interface{}) int { // If we had a colored UI, pull out the nested ui so we don't add escape // sequences for outputting json, etc. colorUI, ok := ui.(*cli.ColoredUi) if ok { ui = colorUI.Ui } formatter, ok := Formatters[strings.ToLower(format)] if !ok { ui.Error(fmt.Sprintf("Invalid output format: %s", format)) return 1 } if err := formatter.Output(ui, secret, data); err != nil { ui.Error(fmt.Sprintf("Could not output secret: %s", err.Error())) return 1 } return 0 } type Formatter interface { Output(ui cli.Ui, secret *api.Secret, data interface{}) error } var Formatters = map[string]Formatter{ "json": JsonFormatter{}, "table": TableFormatter{}, "yaml": YamlFormatter{}, "yml": YamlFormatter{}, } // An output formatter for json output of an object type JsonFormatter struct{} func (j JsonFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { b, err := json.MarshalIndent(data, "", " ") if err != nil { return err } ui.Output(string(b)) return nil } // An output formatter for yaml output format of an object type YamlFormatter struct { } func (y YamlFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { b, err := yaml.Marshal(data) if err == nil { ui.Output(strings.TrimSpace(string(b))) } return err } // An output formatter for table output of an object type TableFormatter struct { } func (t TableFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { // TODO: this should really use reflection like the other formatters do if s, ok := data.(*api.Secret); ok { return t.OutputSecret(ui, secret, s) } if s, ok := data.([]interface{}); ok { return t.OutputList(ui, secret, s) } return errors.New("Cannot use the table formatter for this type") } func (t TableFormatter) OutputList(ui cli.Ui, secret *api.Secret, list []interface{}) error { config := columnize.DefaultConfig() config.Delim = "♨" config.Glue = "\t" config.Prefix = "" input := make([]string, 0, 5) if len(list) > 0 { input = append(input, "Keys") input = append(input, "----") keys := make([]string, 0, len(list)) for _, k := range list { keys = append(keys, k.(string)) } sort.Strings(keys) for _, k := range keys { input = append(input, fmt.Sprintf("%s", k)) } } tableOutputStr := columnize.Format(input, config) // Print the warning separately because the length of first // column in the output will be increased by the length of // the longest warning string making the output look bad. warningsInput := make([]string, 0, 5) if len(secret.Warnings) != 0 { warningsInput = append(warningsInput, "") warningsInput = append(warningsInput, "The following warnings were returned from the Vault server:") for _, warning := range secret.Warnings { warningsInput = append(warningsInput, fmt.Sprintf("* %s", warning)) } } warningsOutputStr := columnize.Format(warningsInput, config) ui.Output(fmt.Sprintf("%s\n%s", tableOutputStr, warningsOutputStr)) return nil } func (t TableFormatter) OutputSecret(ui cli.Ui, secret, s *api.Secret) error { config := columnize.DefaultConfig() config.Delim = "♨" config.Glue = "\t" config.Prefix = "" input := make([]string, 0, 5) onceHeader := &sync.Once{} headerFunc := func() { input = append(input, fmt.Sprintf("Key %s Value", config.Delim)) input = append(input, fmt.Sprintf("--- %s -----", config.Delim)) } if s.LeaseDuration > 0 { onceHeader.Do(headerFunc) if s.LeaseID != "" { input = append(input, fmt.Sprintf("lease_id %s %s", config.Delim, s.LeaseID)) input = append(input, fmt.Sprintf( "lease_duration %s %s", config.Delim, (time.Second*time.Duration(s.LeaseDuration)).String())) } else { input = append(input, fmt.Sprintf( "refresh_interval %s %s", config.Delim, (time.Second*time.Duration(s.LeaseDuration)).String())) } if s.LeaseID != "" { input = append(input, fmt.Sprintf( "lease_renewable %s %s", config.Delim, strconv.FormatBool(s.Renewable))) } } if s.Auth != nil { onceHeader.Do(headerFunc) input = append(input, fmt.Sprintf("token %s %s", config.Delim, s.Auth.ClientToken)) input = append(input, fmt.Sprintf("token_accessor %s %s", config.Delim, s.Auth.Accessor)) input = append(input, fmt.Sprintf("token_duration %s %s", config.Delim, (time.Second*time.Duration(s.Auth.LeaseDuration)).String())) input = append(input, fmt.Sprintf("token_renewable %s %v", config.Delim, s.Auth.Renewable)) input = append(input, fmt.Sprintf("token_policies %s %v", config.Delim, s.Auth.Policies)) for k, v := range s.Auth.Metadata { input = append(input, fmt.Sprintf("token_meta_%s %s %#v", k, config.Delim, v)) } } if s.WrapInfo != nil { onceHeader.Do(headerFunc) input = append(input, fmt.Sprintf("wrapping_token: %s %s", config.Delim, s.WrapInfo.Token)) input = append(input, fmt.Sprintf("wrapping_token_ttl: %s %s", config.Delim, (time.Second*time.Duration(s.WrapInfo.TTL)).String())) input = append(input, fmt.Sprintf("wrapping_token_creation_time: %s %s", config.Delim, s.WrapInfo.CreationTime.String())) input = append(input, fmt.Sprintf("wrapping_token_creation_path: %s %s", config.Delim, s.WrapInfo.CreationPath)) if s.WrapInfo.WrappedAccessor != "" { input = append(input, fmt.Sprintf("wrapped_accessor: %s %s", config.Delim, s.WrapInfo.WrappedAccessor)) } } if s.Data != nil && len(s.Data) > 0 { onceHeader.Do(headerFunc) keys := make([]string, 0, len(s.Data)) for k := range s.Data { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { input = append(input, fmt.Sprintf("%s %s %v", k, config.Delim, s.Data[k])) } } tableOutputStr := columnize.Format(input, config) // Print the warning separately because the length of first // column in the output will be increased by the length of // the longest warning string making the output look bad. warningsInput := make([]string, 0, 5) if len(s.Warnings) != 0 { warningsInput = append(warningsInput, "") warningsInput = append(warningsInput, "The following warnings were returned from the Vault server:") for _, warning := range s.Warnings { warningsInput = append(warningsInput, fmt.Sprintf("* %s", warning)) } } warningsOutputStr := columnize.Format(warningsInput, config) ui.Output(fmt.Sprintf("%s\n%s", tableOutputStr, warningsOutputStr)) return nil }