Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

command: Add remote state capability to output subcommand #6665

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 2 additions & 58 deletions command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import (
"bytes"
"fmt"
"os"
"sort"
"strings"

"github.com/hashicorp/go-getter"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
)

Expand Down Expand Up @@ -250,8 +248,8 @@ func (c *ApplyCommand) Run(args []string) int {
c.Meta.StateOutPath())))
}

if !c.Destroy {
if outputs := outputsAsString(state, ctx.Module().Config().Outputs, true); outputs != "" {
if !c.Destroy && state != nil {
if outputs := allOutputsAsString(state.RootModule(), ctx.Module().Config().Outputs, true); outputs != "" {
c.Ui.Output(c.Colorize().Color(outputs))
}
}
Expand Down Expand Up @@ -376,57 +374,3 @@ Options:
`
return strings.TrimSpace(helpText)
}

func outputsAsString(state *terraform.State, schema []*config.Output, includeHeader bool) string {
if state == nil {
return ""
}

outputs := state.RootModule().Outputs
outputBuf := new(bytes.Buffer)
if len(outputs) > 0 {
schemaMap := make(map[string]*config.Output)
if schema != nil {
for _, s := range schema {
schemaMap[s.Name] = s
}
}

if includeHeader {
outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
}

// Output the outputs in alphabetical order
keyLen := 0
ks := make([]string, 0, len(outputs))
for key, _ := range outputs {
ks = append(ks, key)
if len(key) > keyLen {
keyLen = len(key)
}
}
sort.Strings(ks)

for _, k := range ks {
schema, ok := schemaMap[k]
if ok && schema.Sensitive {
outputBuf.WriteString(fmt.Sprintf("%s = <sensitive>\n", k))
continue
}

v := outputs[k]
switch typedV := v.Value.(type) {
case string:
outputBuf.WriteString(fmt.Sprintf("%s = %s\n", k, typedV))
case []interface{}:
outputBuf.WriteString(formatListOutput("", k, typedV))
outputBuf.WriteString("\n")
case map[string]interface{}:
outputBuf.WriteString(formatMapOutput("", k, typedV))
outputBuf.WriteString("\n")
}
}
}

return strings.TrimSpace(outputBuf.String())
}
191 changes: 27 additions & 164 deletions command/output.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package command

import (
"bytes"
"flag"
"fmt"
"sort"
"strconv"
"strings"
)

Expand All @@ -18,204 +15,70 @@ type OutputCommand struct {
func (c *OutputCommand) Run(args []string) int {
args = c.Meta.process(args, false)

var module string
var module, remoteBackend string
var remoteState bool
var backendConfig map[string]string
cmdFlags := flag.NewFlagSet("output", flag.ContinueOnError)
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&module, "module", "", "module")
cmdFlags.BoolVar(&remoteState, "remote", false, "remote")
cmdFlags.StringVar(&remoteBackend, "remote-backend", "atlas", "remote-backend")
cmdFlags.Var((*FlagKV)(&backendConfig), "remote-config", "remote-config")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }

if err := cmdFlags.Parse(args); err != nil {
return 1
}

args = cmdFlags.Args()
if len(args) > 2 {
c.Ui.Error(
"The output command expects exactly one argument with the name\n" +
"of an output variable or no arguments to show all outputs.\n")
cmdFlags.Usage()
name, index, err := parseOutputNameIndex(cmdFlags.Args())
if err != nil {
c.Ui.Error(err.Error())
return 1
}

name := ""
if len(args) > 0 {
name = args[0]
}

index := ""
if len(args) > 1 {
index = args[1]
}

stateStore, err := c.Meta.State()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
return 1
}

if module == "" {
module = "root"
} else {
module = "root." + module
}

// Get the proper module we want to get outputs for
modPath := strings.Split(module, ".")

state := stateStore.State()
mod := state.ModuleByPath(modPath)

if mod == nil {
c.Ui.Error(fmt.Sprintf(
"The module %s could not be found. There is nothing to output.",
module))
return 1
}

if state.Empty() || len(mod.Outputs) == 0 {
c.Ui.Error(fmt.Sprintf(
"The state file has no outputs defined. Define an output\n" +
"in your configuration with the `output` directive and re-run\n" +
"`terraform apply` for it to become available."))
return 1
}

if name == "" {
c.Ui.Output(outputsAsString(state, nil, false))
return 0
}

v, ok := mod.Outputs[name]
if !ok {
c.Ui.Error(fmt.Sprintf(
"The output variable requested could not be found in the state\n" +
"file. If you recently added this to your configuration, be\n" +
"sure to run `terraform apply`, since the state won't be updated\n" +
"with new output variables until that command is run."))
mod, err := moduleFromState(stateStore.State(), module)
if err != nil {
c.Ui.Error(err.Error())
return 1
}

switch output := v.Value.(type) {
case string:
c.Ui.Output(output)
return 0
case []interface{}:
if index == "" {
c.Ui.Output(formatListOutput("", "", output))
break
}

indexInt, err := strconv.Atoi(index)
var out string
if name != "" {
out, err = singleOutputAsString(mod, name, index)
if err != nil {
c.Ui.Error(fmt.Sprintf(
"The index %q requested is not valid for the list output\n"+
"%q - indices must be numeric, and in the range 0-%d", index, name,
len(output)-1))
break
}

if indexInt < 0 || indexInt >= len(output) {
c.Ui.Error(fmt.Sprintf(
"The index %d requested is not valid for the list output\n"+
"%q - indices must be in the range 0-%d", indexInt, name,
len(output)-1))
break
}

c.Ui.Output(fmt.Sprintf("%s", output[indexInt]))
return 0
case map[string]interface{}:
if index == "" {
c.Ui.Output(formatMapOutput("", "", output))
break
}

if value, ok := output[index]; ok {
c.Ui.Output(fmt.Sprintf("%s", value))
return 0
} else {
c.Ui.Error(err.Error())
return 1
}
default:
c.Ui.Error(fmt.Sprintf("Unknown output type: %T", v.Type))
return 1
}

return 0
}

func formatListOutput(indent, outputName string, outputList []interface{}) string {
keyIndent := ""

outputBuf := new(bytes.Buffer)

if outputName != "" {
outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
keyIndent = " "
}

for _, value := range outputList {
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
}

if outputName != "" {
if len(outputList) > 0 {
outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
} else {
outputBuf.WriteString("]")
}
}

return strings.TrimPrefix(outputBuf.String(), "\n")
}

func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
ks := make([]string, 0, len(outputMap))
for k, _ := range outputMap {
ks = append(ks, k)
}
sort.Strings(ks)

keyIndent := ""

outputBuf := new(bytes.Buffer)
if outputName != "" {
outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
keyIndent = " "
}

for _, k := range ks {
v := outputMap[k]
outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
} else {
out = allOutputsAsString(mod, nil, false)
}

if outputName != "" {
if len(outputMap) > 0 {
outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
} else {
outputBuf.WriteString("}")
}
}
c.Ui.Output(out)

return strings.TrimPrefix(outputBuf.String(), "\n")
return 0
}

func (c *OutputCommand) Help() string {
helpText := `
Usage: terraform output [options] [NAME]

Reads an output variable from a Terraform state file and prints
the value. If NAME is not specified, all outputs are printed.
Reads an output variable from a Terraform state file, or remote state,
and prints the value. If NAME is not specified, all outputs are printed.

Options:

-state=path Path to the state file to read. Defaults to
"terraform.tfstate".
-state=path Path to the state file to read. Defaults to
"terraform.tfstate".

-no-color If specified, output won't contain any color.
-no-color If specified, output won't contain any color.

-module=name If specified, returns the outputs for a
specific module
-module=name If specified, returns the outputs for a
specific module.

`
return strings.TrimSpace(helpText)
Expand Down
Loading