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