github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/command/output.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"sort"
     9  	"strings"
    10  )
    11  
    12  // OutputCommand is a Command implementation that reads an output
    13  // from a Terraform state and prints it.
    14  type OutputCommand struct {
    15  	Meta
    16  }
    17  
    18  func (c *OutputCommand) Run(args []string) int {
    19  	args, err := c.Meta.process(args, false)
    20  	if err != nil {
    21  		return 1
    22  	}
    23  
    24  	var module string
    25  	var jsonOutput bool
    26  	cmdFlags := flag.NewFlagSet("output", flag.ContinueOnError)
    27  	cmdFlags.BoolVar(&jsonOutput, "json", false, "json")
    28  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    29  	cmdFlags.StringVar(&module, "module", "", "module")
    30  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    31  	if err := cmdFlags.Parse(args); err != nil {
    32  		return 1
    33  	}
    34  
    35  	args = cmdFlags.Args()
    36  	if len(args) > 1 {
    37  		c.Ui.Error(
    38  			"The output command expects exactly one argument with the name\n" +
    39  				"of an output variable or no arguments to show all outputs.\n")
    40  		cmdFlags.Usage()
    41  		return 1
    42  	}
    43  
    44  	name := ""
    45  	if len(args) > 0 {
    46  		name = args[0]
    47  	}
    48  
    49  	// Load the backend
    50  	b, err := c.Backend(nil)
    51  	if err != nil {
    52  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
    53  		return 1
    54  	}
    55  
    56  	env := c.Workspace()
    57  
    58  	// Get the state
    59  	stateStore, err := b.State(env)
    60  	if err != nil {
    61  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    62  		return 1
    63  	}
    64  
    65  	if err := stateStore.RefreshState(); err != nil {
    66  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    67  		return 1
    68  	}
    69  
    70  	if module == "" {
    71  		module = "root"
    72  	} else {
    73  		module = "root." + module
    74  	}
    75  
    76  	// Get the proper module we want to get outputs for
    77  	modPath := strings.Split(module, ".")
    78  
    79  	state := stateStore.State()
    80  	mod := state.ModuleByPath(modPath)
    81  	if mod == nil {
    82  		c.Ui.Error(fmt.Sprintf(
    83  			"The module %s could not be found. There is nothing to output.",
    84  			module))
    85  		return 1
    86  	}
    87  
    88  	if state.Empty() || len(mod.Outputs) == 0 {
    89  		c.Ui.Error(
    90  			"The state file either has no outputs defined, or all the defined\n" +
    91  				"outputs are empty. Please define an output in your configuration\n" +
    92  				"with the `output` keyword and run `terraform refresh` for it to\n" +
    93  				"become available. If you are using interpolation, please verify\n" +
    94  				"the interpolated value is not empty. You can use the \n" +
    95  				"`terraform console` command to assist.")
    96  		return 1
    97  	}
    98  
    99  	if name == "" {
   100  		if jsonOutput {
   101  			jsonOutputs, err := json.MarshalIndent(mod.Outputs, "", "    ")
   102  			if err != nil {
   103  				return 1
   104  			}
   105  
   106  			c.Ui.Output(string(jsonOutputs))
   107  			return 0
   108  		} else {
   109  			c.Ui.Output(outputsAsString(state, modPath, nil, false))
   110  			return 0
   111  		}
   112  	}
   113  
   114  	v, ok := mod.Outputs[name]
   115  	if !ok {
   116  		c.Ui.Error(fmt.Sprintf(
   117  			"The output variable requested could not be found in the state\n" +
   118  				"file. If you recently added this to your configuration, be\n" +
   119  				"sure to run `terraform apply`, since the state won't be updated\n" +
   120  				"with new output variables until that command is run."))
   121  		return 1
   122  	}
   123  
   124  	if jsonOutput {
   125  		jsonOutputs, err := json.MarshalIndent(v, "", "    ")
   126  		if err != nil {
   127  			return 1
   128  		}
   129  
   130  		c.Ui.Output(string(jsonOutputs))
   131  	} else {
   132  		switch output := v.Value.(type) {
   133  		case string:
   134  			c.Ui.Output(output)
   135  			return 0
   136  		case []interface{}:
   137  			c.Ui.Output(formatListOutput("", "", output))
   138  			return 0
   139  		case map[string]interface{}:
   140  			c.Ui.Output(formatMapOutput("", "", output))
   141  			return 0
   142  		default:
   143  			c.Ui.Error(fmt.Sprintf("Unknown output type: %T", v.Type))
   144  			return 1
   145  		}
   146  	}
   147  
   148  	return 0
   149  }
   150  
   151  func formatNestedList(indent string, outputList []interface{}) string {
   152  	outputBuf := new(bytes.Buffer)
   153  	outputBuf.WriteString(fmt.Sprintf("%s[", indent))
   154  
   155  	lastIdx := len(outputList) - 1
   156  
   157  	for i, value := range outputList {
   158  		outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, "    ", value))
   159  		if i != lastIdx {
   160  			outputBuf.WriteString(",")
   161  		}
   162  	}
   163  
   164  	outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
   165  	return strings.TrimPrefix(outputBuf.String(), "\n")
   166  }
   167  
   168  func formatListOutput(indent, outputName string, outputList []interface{}) string {
   169  	keyIndent := ""
   170  
   171  	outputBuf := new(bytes.Buffer)
   172  
   173  	if outputName != "" {
   174  		outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
   175  		keyIndent = "    "
   176  	}
   177  
   178  	lastIdx := len(outputList) - 1
   179  
   180  	for i, value := range outputList {
   181  		switch typedValue := value.(type) {
   182  		case string:
   183  			outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
   184  		case []interface{}:
   185  			outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
   186  				formatNestedList(indent+keyIndent, typedValue)))
   187  		case map[string]interface{}:
   188  			outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
   189  				formatNestedMap(indent+keyIndent, typedValue)))
   190  		}
   191  
   192  		if lastIdx != i {
   193  			outputBuf.WriteString(",")
   194  		}
   195  	}
   196  
   197  	if outputName != "" {
   198  		if len(outputList) > 0 {
   199  			outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
   200  		} else {
   201  			outputBuf.WriteString("]")
   202  		}
   203  	}
   204  
   205  	return strings.TrimPrefix(outputBuf.String(), "\n")
   206  }
   207  
   208  func formatNestedMap(indent string, outputMap map[string]interface{}) string {
   209  	ks := make([]string, 0, len(outputMap))
   210  	for k, _ := range outputMap {
   211  		ks = append(ks, k)
   212  	}
   213  	sort.Strings(ks)
   214  
   215  	outputBuf := new(bytes.Buffer)
   216  	outputBuf.WriteString(fmt.Sprintf("%s{", indent))
   217  
   218  	lastIdx := len(outputMap) - 1
   219  	for i, k := range ks {
   220  		v := outputMap[k]
   221  		outputBuf.WriteString(fmt.Sprintf("\n%s%s = %v", indent+"    ", k, v))
   222  
   223  		if lastIdx != i {
   224  			outputBuf.WriteString(",")
   225  		}
   226  	}
   227  
   228  	outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
   229  
   230  	return strings.TrimPrefix(outputBuf.String(), "\n")
   231  }
   232  
   233  func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
   234  	ks := make([]string, 0, len(outputMap))
   235  	for k, _ := range outputMap {
   236  		ks = append(ks, k)
   237  	}
   238  	sort.Strings(ks)
   239  
   240  	keyIndent := ""
   241  
   242  	outputBuf := new(bytes.Buffer)
   243  	if outputName != "" {
   244  		outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
   245  		keyIndent = "  "
   246  	}
   247  
   248  	for _, k := range ks {
   249  		v := outputMap[k]
   250  		outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
   251  	}
   252  
   253  	if outputName != "" {
   254  		if len(outputMap) > 0 {
   255  			outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
   256  		} else {
   257  			outputBuf.WriteString("}")
   258  		}
   259  	}
   260  
   261  	return strings.TrimPrefix(outputBuf.String(), "\n")
   262  }
   263  
   264  func (c *OutputCommand) Help() string {
   265  	helpText := `
   266  Usage: terraform output [options] [NAME]
   267  
   268    Reads an output variable from a Terraform state file and prints
   269    the value. With no additional arguments, output will display all
   270    the outputs for the root module.  If NAME is not specified, all
   271    outputs are printed.
   272  
   273  Options:
   274  
   275    -state=path      Path to the state file to read. Defaults to
   276                     "terraform.tfstate".
   277  
   278    -no-color        If specified, output won't contain any color.
   279  
   280    -module=name     If specified, returns the outputs for a
   281                     specific module
   282  
   283    -json            If specified, machine readable output will be
   284                     printed in JSON format
   285  
   286  `
   287  	return strings.TrimSpace(helpText)
   288  }
   289  
   290  func (c *OutputCommand) Synopsis() string {
   291  	return "Read an output from a state file"
   292  }