// dateInput retrieves a time.Time value as textual input over a // a series of messages // // Use dateInput when you need a full date and time, i.e., 1/1/1 12:00 // If you only need a time, use 'timeInput'. func dateInput(ui cli.Ui, text string) (time.Time, error) { ui.Output(text + " [date]") var ( inputErr error year, month, day, hour, min int ) if year, inputErr = intInput(ui, "Year (e.g., 2016)"); inputErr != nil { return *new(time.Time), inputErr } if month, inputErr = intInput(ui, "Month (e.g., 1 for January)"); inputErr != nil { return *new(time.Time), inputErr } if day, inputErr = intInput(ui, "Day [e.g., 1]"); inputErr != nil { return *new(time.Time), inputErr } if hour, inputErr = intInput(ui, "Hour [e.g., 13]"); inputErr != nil { return *new(time.Time), inputErr } if min, inputErr = intInput(ui, "Minute [e.g., 59]"); inputErr != nil { return *new(time.Time), inputErr } return time.Date(year, time.Month(month), day, hour, min, 0, 0, time.Local), nil }
func (c *Command) startShutdownWatcher(agent *Agent, ui cli.Ui) (graceful <-chan struct{}, forceful <-chan struct{}) { g := make(chan struct{}) f := make(chan struct{}) graceful = g forceful = f go func() { <-c.ShutdownCh c.lock.Lock() c.shuttingDown = true c.lock.Unlock() ui.Output("Gracefully shutting down agent...") go func() { if err := agent.Shutdown(); err != nil { ui.Error(fmt.Sprintf("Error: %s", err)) return } close(g) }() select { case <-g: // Gracefully shut down properly case <-c.ShutdownCh: close(f) } }() return }
func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int { ui.Error(fmt.Sprintf( "The readline library Terraform currently uses for the interactive\n" + "console is not supported by Solaris. Interactive mode is therefore\n" + "not supported on Solaris currently.")) return 1 }
func (param *Parameter) Ask(name string, ui cli.Ui) string { query := fmt.Sprintf("%s:", name) if d := param.DisplayDefault(); d != "" { query += fmt.Sprintf(" [%s]", d) } ask := param.AskFunc(ui) var result string for { input, _ := ask(query) if input == "" { result = param.Default } else { result = input } err := param.Validate(result) if err == nil { break } ui.Error(err.Error()) } return result }
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 }
func createFixture(ui cli.Ui, ownerID string, db data.DB) (fixture *models.Fixture, err error) { ui.Output("Creating a fixture") fixture = models.NewFixture() fixture.SetID(db.NewID()) fixture.OwnerId = ownerID fixture.CreatedAt = time.Now() if fixture.Name, err = stringInput(ui, "Name of the fixture:"); err != nil { return } if fixture.Label, err = boolInput(ui, "Is this a label?"); err != nil { return } if !fixture.Label { if fixture.StartTime, err = timeInput(ui, "Start time of fixture?"); err != nil { return } if fixture.EndTime, err = timeInput(ui, "End time of fixture?"); err != nil { return } } fixture.UpdatedAt = time.Now() err = db.Save(fixture) return }
func outputFormatTableList(ui cli.Ui, s *api.Secret) int { config := columnize.DefaultConfig() config.Delim = "♨" config.Glue = "\t" config.Prefix = "" input := make([]string, 0, 5) input = append(input, "Keys") keys := make([]string, 0, len(s.Data["keys"].([]interface{}))) for _, k := range s.Data["keys"].([]interface{}) { keys = append(keys, k.(string)) } sort.Strings(keys) for _, k := range keys { input = append(input, fmt.Sprintf("%s", k)) } if len(s.Warnings) != 0 { input = append(input, "") for _, warning := range s.Warnings { input = append(input, fmt.Sprintf("* %s", warning)) } } ui.Output(columnize.Format(input, config)) return 0 }
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) input = append(input, "Keys") 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)) } if len(secret.Warnings) != 0 { input = append(input, "") input = append(input, "The following warnings were returned from the Vault server:") for _, warning := range secret.Warnings { input = append(input, fmt.Sprintf("* %s", warning)) } } ui.Output(columnize.Format(input, config)) return nil }
// dumpAllocStatus is a helper to generate a more user-friendly error message // for scheduling failures, displaying a high level status of why the job // could not be scheduled out. func dumpAllocStatus(ui cli.Ui, alloc *api.Allocation, length int) { // Print filter stats ui.Output(fmt.Sprintf("Allocation %q status %q (%d/%d nodes filtered)", limit(alloc.ID, length), alloc.ClientStatus, alloc.Metrics.NodesFiltered, alloc.Metrics.NodesEvaluated)) ui.Output(formatAllocMetrics(alloc.Metrics, true, " ")) }
func outputFormatTable(ui cli.Ui, s *api.Secret, whitespace bool) int { config := columnize.DefaultConfig() config.Delim = "♨" config.Glue = "\t" config.Prefix = "" input := make([]string, 0, 5) input = append(input, fmt.Sprintf("Key %s Value", config.Delim)) if s.LeaseID != "" && s.LeaseDuration > 0 { input = append(input, fmt.Sprintf("lease_id %s %s", config.Delim, s.LeaseID)) input = append(input, fmt.Sprintf( "lease_duration %s %d", config.Delim, s.LeaseDuration)) input = append(input, fmt.Sprintf( "lease_renewable %s %s", config.Delim, strconv.FormatBool(s.Renewable))) } if s.Auth != nil { input = append(input, fmt.Sprintf("token %s %s", config.Delim, s.Auth.ClientToken)) input = append(input, fmt.Sprintf("token_duration %s %d", config.Delim, s.Auth.LeaseDuration)) 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)) } } for k, v := range s.Data { input = append(input, fmt.Sprintf("%s %s %v", k, config.Delim, v)) } ui.Output(columnize.Format(input, config)) return 0 }
// Discover plugins located on disk, and fall back on plugins baked into the // Terraform binary. // // We look in the following places for plugins: // // 1. Terraform configuration path // 2. Path where Terraform is installed // 3. Path where Terraform is invoked // // Whichever file is discoverd LAST wins. // // Finally, we look at the list of plugins compiled into Terraform. If any of // them has not been found on disk we use the internal version. This allows // users to add / replace plugins without recompiling the main binary. func (c *Config) Discover(ui cli.Ui) error { // Look in ~/.terraform.d/plugins/ dir, err := ConfigDir() if err != nil { log.Printf("[ERR] Error loading config directory: %s", err) } else { if err := c.discover(filepath.Join(dir, "plugins")); err != nil { return err } } // Next, look in the same directory as the Terraform executable, usually // /usr/local/bin. If found, this replaces what we found in the config path. exePath, err := osext.Executable() if err != nil { log.Printf("[ERR] Error loading exe directory: %s", err) } else { if err := c.discover(filepath.Dir(exePath)); err != nil { return err } } // Finally look in the cwd (where we are invoke Terraform). If found, this // replaces anything we found in the config / install paths. if err := c.discover("."); err != nil { return err } // Finally, if we have a plugin compiled into Terraform and we didn't find // a replacement on disk, we'll just use the internal version. for name, _ := range command.InternalProviders { if path, found := c.Providers[name]; found { ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+ " If you did not expect to see this message you will need to remove the old plugin.\n"+ " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) } else { cmd, err := command.BuildPluginCommandString("provider", name) if err != nil { return err } c.Providers[name] = cmd } } for name, _ := range command.InternalProvisioners { if path, found := c.Provisioners[name]; found { ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+ " If you did not expect to see this message you will need to remove the old plugin.\n"+ " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) } else { cmd, err := command.BuildPluginCommandString("provisioner", name) if err != nil { return err } c.Provisioners[name] = cmd } } return nil }
func PrintRawField(ui cli.Ui, secret *api.Secret, field string) int { var val interface{} switch { case secret.Auth != nil: switch field { case "token": val = secret.Auth.ClientToken case "token_accessor": val = secret.Auth.Accessor case "token_duration": val = secret.Auth.LeaseDuration case "token_renewable": val = secret.Auth.Renewable case "token_policies": val = secret.Auth.Policies default: val = secret.Data[field] } case secret.WrapInfo != nil: switch field { case "wrapping_token": val = secret.WrapInfo.Token case "wrapping_token_ttl": val = secret.WrapInfo.TTL case "wrapping_token_creation_time": val = secret.WrapInfo.CreationTime.Format(time.RFC3339Nano) case "wrapped_accessor": val = secret.WrapInfo.WrappedAccessor default: val = secret.Data[field] } default: switch field { case "refresh_interval": val = secret.LeaseDuration default: val = secret.Data[field] } } if val != nil { // c.Ui.Output() prints a CR character which in this case is // not desired. Since Vault CLI currently only uses BasicUi, // which writes to standard output, os.Stdout is used here to // directly print the message. If mitchellh/cli exposes method // to print without CR, this check needs to be removed. if reflect.TypeOf(ui).String() == "*cli.BasicUi" { fmt.Fprintf(os.Stdout, fmt.Sprintf("%v", val)) } else { ui.Output(fmt.Sprintf("%v", val)) } return 0 } else { ui.Error(fmt.Sprintf( "Field %s not present in secret", field)) return 1 } }
func (j JsonFormatter) Output(ui cli.Ui, secret *api.Secret, data interface{}) error { b, err := json.Marshal(data) if err == nil { var out bytes.Buffer json.Indent(&out, b, "", "\t") ui.Output(out.String()) } return err }
func outputFormatYAMLList(ui cli.Ui, s *api.Secret) int { b, err := yaml.Marshal(s.Data["keys"]) if err != nil { ui.Error(fmt.Sprintf( "Error formatting secret: %s", err)) return 1 } ui.Output(strings.TrimSpace(string(b))) return 0 }
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) input = append(input, fmt.Sprintf("Key %s Value", config.Delim)) if s.LeaseDuration > 0 { if s.LeaseID != "" { input = append(input, fmt.Sprintf("lease_id %s %s", config.Delim, s.LeaseID)) } input = append(input, fmt.Sprintf( "lease_duration %s %d", config.Delim, s.LeaseDuration)) if s.LeaseID != "" { input = append(input, fmt.Sprintf( "lease_renewable %s %s", config.Delim, strconv.FormatBool(s.Renewable))) } } if s.Auth != nil { 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 %d", config.Delim, s.Auth.LeaseDuration)) 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)) } } 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])) } if len(s.Warnings) != 0 { input = append(input, "") input = append(input, "The following warnings were returned from the Vault server:") for _, warning := range s.Warnings { input = append(input, fmt.Sprintf("* %s", warning)) } } ui.Output(columnize.Format(input, config)) return nil }
func outputWithFormat(ui cli.Ui, format string, secret *api.Secret, data interface{}) int { 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 }
func OutputList(ui cli.Ui, format string, secret *api.Secret) int { switch format { case "json": return outputFormatJSONList(ui, secret) case "yaml": return outputFormatYAMLList(ui, secret) case "table": return outputFormatTableList(ui, secret) default: ui.Error(fmt.Sprintf("Invalid output format: %s", format)) return 1 } }
func outputFormatJSONList(ui cli.Ui, s *api.Secret) int { b, err := json.Marshal(s.Data["keys"]) if err != nil { ui.Error(fmt.Sprintf( "Error formatting keys: %s", err)) return 1 } var out bytes.Buffer json.Indent(&out, b, "", "\t") ui.Output(out.String()) return 0 }
// stringListInput requests a list of a comma delimitted strings // // Use this where you need to take a list of string values, // such as the case where you want to take a list of strings // // It parses the strings based on commas, or double commas, // if the string input needs to include commas func stringListInput(ui cli.Ui, text string) ([]string, error) { in, err := ui.Ask(text + " [list,of,strings]") if err != nil { return nil, err } // if the user used double commas, single commas will be ignored if strings.Contains(in, ",,") { return strings.Split(in, ",,"), nil } return strings.Split(in, ","), nil }
// intInput requests an integer input (signed) // // Use intInput if you need to retrieve an integer. func intInput(ui cli.Ui, text string) (int, error) { for { input, err := ui.Ask(text + " [integer]:") if err != nil { return 0, err } i64, err := strconv.ParseInt(input, 10, 64) if err == nil { return int(i64), nil } out := "Invalid input, please try again. Valid integer expressions include: 1, 12, -300 etc." ui.Output(strings.TrimSpace(out)) } }
// timeInput retrieves a time.Time value, but only pays attention // to the hour and the minute components. It fills in the year 0, // month 0, day 0, second 0 and nsecond 0. It uses time.Local for // location information. // // Use timeInput if you need to retrieve a time of the form 12:45, // but if you care also abou the calendrical components, such as // the year, month and day, use 'dateInput'. func timeInput(ui cli.Ui, text string) (t time.Time, err error) { ui.Output(text + " [time]") var ( inputErr error hour, min int ) if hour, inputErr = intInput(ui, "Hour [e.g., 13]"); inputErr != nil { return *new(time.Time), inputErr } if min, inputErr = intInput(ui, "Minute [e.g., 59]"); inputErr != nil { return *new(time.Time), inputErr } return time.Date(0, 0, 0, hour, min, 0, 0, time.Local), nil }
func printFixtures(ui cli.Ui, fixtures []*models.Fixture) { if len(fixtures) == 0 { ui.Output(" -- No fixtures") return } sort.Sort(byStartTime(fixtures)) for _, f := range fixtures { var output string if f.Label { output = fmt.Sprintf("* %s [Label]", f.Name) } else { output = fmt.Sprintf(` * %s [%s - %s] `, f.Name, f.StartTime.Format("15:04"), f.EndTime.Format("15:04")) } ui.Output(strings.TrimSpace(output)) } }
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 (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int { // Configure input l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{ Prompt: "> ", InterruptPrompt: "^C", EOFPrompt: "exit", HistorySearchFold: true, })) if err != nil { c.Ui.Error(fmt.Sprintf( "Error initializing console: %s", err)) return 1 } defer l.Close() for { // Read a line line, err := l.Readline() if err == readline.ErrInterrupt { if len(line) == 0 { break } else { continue } } else if err == io.EOF { break } out, err := session.Handle(line) if err == repl.ErrSessionExit { break } if err != nil { ui.Error(err.Error()) continue } ui.Output(out) } return 0 }
func (c *ConsoleCommand) modePiped(session *repl.Session, ui cli.Ui) int { var lastResult string scanner := bufio.NewScanner(wrappedstreams.Stdin()) for scanner.Scan() { // Handle it. If there is an error exit immediately result, err := session.Handle(strings.TrimSpace(scanner.Text())) if err != nil { ui.Error(err.Error()) return 1 } // Store the last result lastResult = result } // Output the final result ui.Output(lastResult) return 0 }
// Setup is used to perform setup of several logging objects: // // * A LevelFilter is used to perform filtering by log level. // * A GatedWriter is used to buffer logs until startup UI operations are // complete. After this is flushed then logs flow directly to output // destinations. // * A LogWriter provides a mean to temporarily hook logs, such as for running // a command like "consul monitor". // * An io.Writer is provided as the sink for all logs to flow to. // // The provided ui object will get any log messages related to setting up // logging itself, and will also be hooked up to the gated logger. The final bool // parameter indicates if logging was set up successfully. func Setup(config *Config, ui cli.Ui) (*logutils.LevelFilter, *GatedWriter, *LogWriter, io.Writer, bool) { // The gated writer buffers logs at startup and holds until it's flushed. logGate := &GatedWriter{ Writer: &cli.UiWriter{ui}, } // Set up the level filter. logFilter := LevelFilter() logFilter.MinLevel = logutils.LogLevel(strings.ToUpper(config.LogLevel)) logFilter.Writer = logGate if !ValidateLevelFilter(logFilter.MinLevel, logFilter) { ui.Error(fmt.Sprintf( "Invalid log level: %s. Valid log levels are: %v", logFilter.MinLevel, logFilter.Levels)) return nil, nil, nil, nil, false } // Set up syslog if it's enabled. var syslog io.Writer if config.EnableSyslog { retries := 12 delay := 5 * time.Second for i := 0; i <= retries; i++ { l, err := gsyslog.NewLogger(gsyslog.LOG_NOTICE, config.SyslogFacility, "consul") if err != nil { ui.Error(fmt.Sprintf("Syslog setup error: %v", err)) if i == retries { timeout := time.Duration(retries) * delay ui.Error(fmt.Sprintf("Syslog setup did not succeed within timeout (%s).", timeout.String())) return nil, nil, nil, nil, false } else { ui.Error(fmt.Sprintf("Retrying syslog setup in %s...", delay.String())) time.Sleep(delay) } } else { syslog = &SyslogWrapper{l, logFilter} break } } } // Create a log writer, and wrap a logOutput around it logWriter := NewLogWriter(512) var logOutput io.Writer if syslog != nil { logOutput = io.MultiWriter(logFilter, logWriter, syslog) } else { logOutput = io.MultiWriter(logFilter, logWriter) } return logFilter, logGate, logWriter, logOutput, true }
// NewCLILogger - func NewCLILogger(level, file, context, format string, ui cli.Ui) *Logger { canWrite := true f, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { ui.Error(fmt.Sprintf("failed to open %s in append mode, not writing to file: %s", file, err)) canWrite = false } // this needs to happen somewhere else // defer f.Close() c := []string{context} l := &Logger{ Context: c, Format: format, Level: strings.ToUpper(level), File: file, _file: f, _canWrite: canWrite, _ui: ui, _buffer: []LogLine{}, } return l }
// boolInput requests a boolean input // // Use this where you need to take a boolean value. If you are // looking for a confirmation prompt, however, use 'yesNo' func boolInput(ui cli.Ui, text string) (bool, error) { for { input, err := ui.Ask(text + " [boolean]:") if err != nil { return false, err } switch input { case "yes": return true, err case "no": return false, err default: b, err := strconv.ParseBool(input) if err == nil { return b, nil } out := " Invalid input, please try again. Valid boolean expressions include: true, false, 0, 1 etc." ui.Output(strings.TrimSpace(out)) } } }
// directOutput - func directOutput(ui cli.Ui, channel, prefix, line string) { channelType := strings.ToUpper(channel) if ui == nil { fmt.Printf("%s%s", prefix, line) } else { switch channelType { case "ERROR": ui.Error(fmt.Sprintf("%s%s", prefix, line)) case "INFO": ui.Info(fmt.Sprintf("%s%s", prefix, line)) case "WARN": ui.Warn(fmt.Sprintf("%s%s", prefix, line)) default: ui.Output(fmt.Sprintf("%s%s", prefix, line)) } } }
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) input = append(input, fmt.Sprintf("Key %s Value", config.Delim)) input = append(input, fmt.Sprintf("--- %s -----", config.Delim)) if s.LeaseDuration > 0 { if s.LeaseID != "" { input = append(input, fmt.Sprintf("lease_id %s %s", config.Delim, s.LeaseID)) input = append(input, fmt.Sprintf( "lease_duration %s %d", config.Delim, s.LeaseDuration)) } else { input = append(input, fmt.Sprintf( "refresh_interval %s %d", config.Delim, s.LeaseDuration)) } if s.LeaseID != "" { input = append(input, fmt.Sprintf( "lease_renewable %s %s", config.Delim, strconv.FormatBool(s.Renewable))) } } if s.Auth != nil { 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 %d", config.Delim, s.Auth.LeaseDuration)) 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 { input = append(input, fmt.Sprintf("wrapping_token: %s %s", config.Delim, s.WrapInfo.Token)) input = append(input, fmt.Sprintf("wrapping_token_ttl: %s %d", config.Delim, s.WrapInfo.TTL)) input = append(input, fmt.Sprintf("wrapping_token_creation_time: %s %s", config.Delim, s.WrapInfo.CreationTime.String())) if s.WrapInfo.WrappedAccessor != "" { input = append(input, fmt.Sprintf("wrapped_accessor: %s %s", config.Delim, s.WrapInfo.WrappedAccessor)) } } 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 }