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 }