github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/command/views/add.go (about) 1 package views 2 3 import ( 4 "fmt" 5 "os" 6 "sort" 7 "strings" 8 9 "github.com/hashicorp/hcl/v2/hclwrite" 10 "github.com/hashicorp/terraform/internal/addrs" 11 "github.com/hashicorp/terraform/internal/command/arguments" 12 "github.com/hashicorp/terraform/internal/configs/configschema" 13 "github.com/hashicorp/terraform/internal/lang/marks" 14 "github.com/hashicorp/terraform/internal/tfdiags" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 // Add is the view interface for the "terraform add" command. 19 type Add interface { 20 Resource(addrs.AbsResourceInstance, *configschema.Block, addrs.LocalProviderConfig, cty.Value) error 21 Diagnostics(tfdiags.Diagnostics) 22 } 23 24 // NewAdd returns an initialized Validate implementation. At this time, 25 // ViewHuman is the only implemented view type. 26 func NewAdd(vt arguments.ViewType, view *View, args *arguments.Add) Add { 27 return &addHuman{ 28 view: view, 29 optional: args.Optional, 30 outPath: args.OutPath, 31 } 32 } 33 34 type addHuman struct { 35 view *View 36 optional bool 37 outPath string 38 } 39 40 func (v *addHuman) Resource(addr addrs.AbsResourceInstance, schema *configschema.Block, pc addrs.LocalProviderConfig, stateVal cty.Value) error { 41 var buf strings.Builder 42 43 buf.WriteString(`# NOTE: The "terraform add" command is currently experimental and offers only a 44 # starting point for your resource configuration, with some limitations. 45 # 46 # The behavior of this command may change in future based on feedback, possibly 47 # in incompatible ways. We don't recommend building automation around this 48 # command at this time. If you have feedback about this command, please open 49 # a feature request issue in the Terraform GitHub repository. 50 `) 51 52 buf.WriteString(fmt.Sprintf("resource %q %q {\n", addr.Resource.Resource.Type, addr.Resource.Resource.Name)) 53 54 if pc.LocalName != addr.Resource.Resource.ImpliedProvider() || pc.Alias != "" { 55 buf.WriteString(strings.Repeat(" ", 2)) 56 buf.WriteString(fmt.Sprintf("provider = %s\n\n", pc.StringCompact())) 57 } 58 59 if stateVal.RawEquals(cty.NilVal) { 60 if err := v.writeConfigAttributes(&buf, schema.Attributes, 2); err != nil { 61 return err 62 } 63 if err := v.writeConfigBlocks(&buf, schema.BlockTypes, 2); err != nil { 64 return err 65 } 66 } else { 67 if err := v.writeConfigAttributesFromExisting(&buf, stateVal, schema.Attributes, 2); err != nil { 68 return err 69 } 70 if err := v.writeConfigBlocksFromExisting(&buf, stateVal, schema.BlockTypes, 2); err != nil { 71 return err 72 } 73 } 74 75 buf.WriteString("}") 76 77 // The output better be valid HCL which can be parsed and formatted. 78 formatted := hclwrite.Format([]byte(buf.String())) 79 80 var err error 81 if v.outPath == "" { 82 _, err = v.view.streams.Println(string(formatted)) 83 return err 84 } else { 85 // The Println call above adds this final newline automatically; we add it manually here. 86 formatted = append(formatted, '\n') 87 return os.WriteFile(v.outPath, formatted, 0600) 88 } 89 } 90 91 func (v *addHuman) Diagnostics(diags tfdiags.Diagnostics) { 92 v.view.Diagnostics(diags) 93 } 94 95 func (v *addHuman) writeConfigAttributes(buf *strings.Builder, attrs map[string]*configschema.Attribute, indent int) error { 96 if len(attrs) == 0 { 97 return nil 98 } 99 100 // Get a list of sorted attribute names so the output will be consistent between runs. 101 keys := make([]string, 0, len(attrs)) 102 for k := range attrs { 103 keys = append(keys, k) 104 } 105 sort.Strings(keys) 106 107 for i := range keys { 108 name := keys[i] 109 attrS := attrs[name] 110 if attrS.NestedType != nil { 111 if err := v.writeConfigNestedTypeAttribute(buf, name, attrS, indent); err != nil { 112 return err 113 } 114 continue 115 } 116 if attrS.Required { 117 buf.WriteString(strings.Repeat(" ", indent)) 118 buf.WriteString(fmt.Sprintf("%s = ", name)) 119 tok := hclwrite.TokensForValue(attrS.EmptyValue()) 120 if _, err := tok.WriteTo(buf); err != nil { 121 return err 122 } 123 writeAttrTypeConstraint(buf, attrS) 124 } else if attrS.Optional && v.optional { 125 buf.WriteString(strings.Repeat(" ", indent)) 126 buf.WriteString(fmt.Sprintf("%s = ", name)) 127 tok := hclwrite.TokensForValue(attrS.EmptyValue()) 128 if _, err := tok.WriteTo(buf); err != nil { 129 return err 130 } 131 writeAttrTypeConstraint(buf, attrS) 132 } 133 } 134 return nil 135 } 136 137 func (v *addHuman) writeConfigAttributesFromExisting(buf *strings.Builder, stateVal cty.Value, attrs map[string]*configschema.Attribute, indent int) error { 138 if len(attrs) == 0 { 139 return nil 140 } 141 142 // Get a list of sorted attribute names so the output will be consistent between runs. 143 keys := make([]string, 0, len(attrs)) 144 for k := range attrs { 145 keys = append(keys, k) 146 } 147 sort.Strings(keys) 148 149 for i := range keys { 150 name := keys[i] 151 attrS := attrs[name] 152 if attrS.NestedType != nil { 153 if err := v.writeConfigNestedTypeAttributeFromExisting(buf, name, attrS, stateVal, indent); err != nil { 154 return err 155 } 156 continue 157 } 158 159 // Exclude computed-only attributes 160 if attrS.Required || attrS.Optional { 161 buf.WriteString(strings.Repeat(" ", indent)) 162 buf.WriteString(fmt.Sprintf("%s = ", name)) 163 164 var val cty.Value 165 if stateVal.Type().HasAttribute(name) { 166 val = stateVal.GetAttr(name) 167 } else { 168 val = attrS.EmptyValue() 169 } 170 if attrS.Sensitive || val.HasMark(marks.Sensitive) { 171 buf.WriteString("null # sensitive") 172 } else { 173 val, _ = val.Unmark() 174 tok := hclwrite.TokensForValue(val) 175 if _, err := tok.WriteTo(buf); err != nil { 176 return err 177 } 178 } 179 180 buf.WriteString("\n") 181 } 182 } 183 return nil 184 } 185 186 func (v *addHuman) writeConfigBlocks(buf *strings.Builder, blocks map[string]*configschema.NestedBlock, indent int) error { 187 if len(blocks) == 0 { 188 return nil 189 } 190 191 // Get a list of sorted block names so the output will be consistent between runs. 192 names := make([]string, 0, len(blocks)) 193 for k := range blocks { 194 names = append(names, k) 195 } 196 sort.Strings(names) 197 198 for i := range names { 199 name := names[i] 200 blockS := blocks[name] 201 if err := v.writeConfigNestedBlock(buf, name, blockS, indent); err != nil { 202 return err 203 } 204 } 205 return nil 206 } 207 208 func (v *addHuman) writeConfigNestedBlock(buf *strings.Builder, name string, schema *configschema.NestedBlock, indent int) error { 209 if !v.optional && schema.MinItems == 0 { 210 return nil 211 } 212 213 switch schema.Nesting { 214 case configschema.NestingSingle, configschema.NestingGroup: 215 buf.WriteString(strings.Repeat(" ", indent)) 216 buf.WriteString(fmt.Sprintf("%s {", name)) 217 writeBlockTypeConstraint(buf, schema) 218 if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil { 219 return err 220 } 221 if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil { 222 return err 223 } 224 buf.WriteString("}\n") 225 return nil 226 case configschema.NestingList, configschema.NestingSet: 227 buf.WriteString(strings.Repeat(" ", indent)) 228 buf.WriteString(fmt.Sprintf("%s {", name)) 229 writeBlockTypeConstraint(buf, schema) 230 if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil { 231 return err 232 } 233 if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil { 234 return err 235 } 236 buf.WriteString("}\n") 237 return nil 238 case configschema.NestingMap: 239 buf.WriteString(strings.Repeat(" ", indent)) 240 // we use an arbitrary placeholder key (block label) "key" 241 buf.WriteString(fmt.Sprintf("%s \"key\" {", name)) 242 writeBlockTypeConstraint(buf, schema) 243 if err := v.writeConfigAttributes(buf, schema.Attributes, indent+2); err != nil { 244 return err 245 } 246 if err := v.writeConfigBlocks(buf, schema.BlockTypes, indent+2); err != nil { 247 return err 248 } 249 buf.WriteString(strings.Repeat(" ", indent)) 250 buf.WriteString("}\n") 251 return nil 252 default: 253 // This should not happen, the above should be exhaustive. 254 return fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String()) 255 } 256 } 257 258 func (v *addHuman) writeConfigNestedTypeAttribute(buf *strings.Builder, name string, schema *configschema.Attribute, indent int) error { 259 if !schema.Required && !v.optional { 260 return nil 261 } 262 263 buf.WriteString(strings.Repeat(" ", indent)) 264 buf.WriteString(fmt.Sprintf("%s = ", name)) 265 266 switch schema.NestedType.Nesting { 267 case configschema.NestingSingle: 268 buf.WriteString("{") 269 writeAttrTypeConstraint(buf, schema) 270 if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+2); err != nil { 271 return err 272 } 273 buf.WriteString(strings.Repeat(" ", indent)) 274 buf.WriteString("}\n") 275 return nil 276 case configschema.NestingList, configschema.NestingSet: 277 buf.WriteString("[{") 278 writeAttrTypeConstraint(buf, schema) 279 if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+2); err != nil { 280 return err 281 } 282 buf.WriteString(strings.Repeat(" ", indent)) 283 buf.WriteString("}]\n") 284 return nil 285 case configschema.NestingMap: 286 buf.WriteString("{") 287 writeAttrTypeConstraint(buf, schema) 288 buf.WriteString(strings.Repeat(" ", indent+2)) 289 // we use an arbitrary placeholder key "key" 290 buf.WriteString("key = {\n") 291 if err := v.writeConfigAttributes(buf, schema.NestedType.Attributes, indent+4); err != nil { 292 return err 293 } 294 buf.WriteString(strings.Repeat(" ", indent+2)) 295 buf.WriteString("}\n") 296 buf.WriteString(strings.Repeat(" ", indent)) 297 buf.WriteString("}\n") 298 return nil 299 default: 300 // This should not happen, the above should be exhaustive. 301 return fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String()) 302 } 303 } 304 305 func (v *addHuman) writeConfigBlocksFromExisting(buf *strings.Builder, stateVal cty.Value, blocks map[string]*configschema.NestedBlock, indent int) error { 306 if len(blocks) == 0 { 307 return nil 308 } 309 310 // Get a list of sorted block names so the output will be consistent between runs. 311 names := make([]string, 0, len(blocks)) 312 for k := range blocks { 313 names = append(names, k) 314 } 315 sort.Strings(names) 316 317 for _, name := range names { 318 blockS := blocks[name] 319 // This shouldn't happen in real usage; state always has all values (set 320 // to null as needed), but it protects against panics in tests (and any 321 // really weird and unlikely cases). 322 if !stateVal.Type().HasAttribute(name) { 323 continue 324 } 325 blockVal := stateVal.GetAttr(name) 326 if err := v.writeConfigNestedBlockFromExisting(buf, name, blockS, blockVal, indent); err != nil { 327 return err 328 } 329 } 330 331 return nil 332 } 333 334 func (v *addHuman) writeConfigNestedTypeAttributeFromExisting(buf *strings.Builder, name string, schema *configschema.Attribute, stateVal cty.Value, indent int) error { 335 switch schema.NestedType.Nesting { 336 case configschema.NestingSingle: 337 if schema.Sensitive || stateVal.HasMark(marks.Sensitive) { 338 buf.WriteString(strings.Repeat(" ", indent)) 339 buf.WriteString(fmt.Sprintf("%s = {} # sensitive\n", name)) 340 return nil 341 } 342 buf.WriteString(strings.Repeat(" ", indent)) 343 buf.WriteString(fmt.Sprintf("%s = {\n", name)) 344 345 // This shouldn't happen in real usage; state always has all values (set 346 // to null as needed), but it protects against panics in tests (and any 347 // really weird and unlikely cases). 348 if !stateVal.Type().HasAttribute(name) { 349 return nil 350 } 351 nestedVal := stateVal.GetAttr(name) 352 if err := v.writeConfigAttributesFromExisting(buf, nestedVal, schema.NestedType.Attributes, indent+2); err != nil { 353 return err 354 } 355 buf.WriteString("}\n") 356 return nil 357 358 case configschema.NestingList, configschema.NestingSet: 359 buf.WriteString(strings.Repeat(" ", indent)) 360 buf.WriteString(fmt.Sprintf("%s = [", name)) 361 362 if schema.Sensitive || stateVal.HasMark(marks.Sensitive) { 363 buf.WriteString("] # sensitive\n") 364 return nil 365 } 366 367 buf.WriteString("\n") 368 369 listVals := ctyCollectionValues(stateVal.GetAttr(name)) 370 for i := range listVals { 371 buf.WriteString(strings.Repeat(" ", indent+2)) 372 373 // The entire element is marked. 374 if listVals[i].HasMark(marks.Sensitive) { 375 buf.WriteString("{}, # sensitive\n") 376 continue 377 } 378 379 buf.WriteString("{\n") 380 if err := v.writeConfigAttributesFromExisting(buf, listVals[i], schema.NestedType.Attributes, indent+4); err != nil { 381 return err 382 } 383 buf.WriteString(strings.Repeat(" ", indent+2)) 384 buf.WriteString("},\n") 385 } 386 buf.WriteString(strings.Repeat(" ", indent)) 387 buf.WriteString("]\n") 388 return nil 389 390 case configschema.NestingMap: 391 buf.WriteString(strings.Repeat(" ", indent)) 392 buf.WriteString(fmt.Sprintf("%s = {", name)) 393 394 if schema.Sensitive || stateVal.HasMark(marks.Sensitive) { 395 buf.WriteString(" } # sensitive\n") 396 return nil 397 } 398 399 buf.WriteString("\n") 400 401 vals := stateVal.GetAttr(name).AsValueMap() 402 keys := make([]string, 0, len(vals)) 403 for key := range vals { 404 keys = append(keys, key) 405 } 406 sort.Strings(keys) 407 for _, key := range keys { 408 buf.WriteString(strings.Repeat(" ", indent+2)) 409 buf.WriteString(fmt.Sprintf("%s = {", key)) 410 411 // This entire value is marked 412 if vals[key].HasMark(marks.Sensitive) { 413 buf.WriteString("} # sensitive\n") 414 continue 415 } 416 417 buf.WriteString("\n") 418 if err := v.writeConfigAttributesFromExisting(buf, vals[key], schema.NestedType.Attributes, indent+4); err != nil { 419 return err 420 } 421 buf.WriteString(strings.Repeat(" ", indent+2)) 422 buf.WriteString("}\n") 423 } 424 buf.WriteString(strings.Repeat(" ", indent)) 425 buf.WriteString("}\n") 426 return nil 427 428 default: 429 // This should not happen, the above should be exhaustive. 430 return fmt.Errorf("unsupported NestingMode %s", schema.NestedType.Nesting.String()) 431 } 432 } 433 434 func (v *addHuman) writeConfigNestedBlockFromExisting(buf *strings.Builder, name string, schema *configschema.NestedBlock, stateVal cty.Value, indent int) error { 435 switch schema.Nesting { 436 case configschema.NestingSingle, configschema.NestingGroup: 437 buf.WriteString(strings.Repeat(" ", indent)) 438 buf.WriteString(fmt.Sprintf("%s {", name)) 439 440 // If the entire value is marked, don't print any nested attributes 441 if stateVal.HasMark(marks.Sensitive) { 442 buf.WriteString("} # sensitive\n") 443 return nil 444 } 445 buf.WriteString("\n") 446 if err := v.writeConfigAttributesFromExisting(buf, stateVal, schema.Attributes, indent+2); err != nil { 447 return err 448 } 449 if err := v.writeConfigBlocksFromExisting(buf, stateVal, schema.BlockTypes, indent+2); err != nil { 450 return err 451 } 452 buf.WriteString("}\n") 453 return nil 454 case configschema.NestingList, configschema.NestingSet: 455 if stateVal.HasMark(marks.Sensitive) { 456 buf.WriteString(strings.Repeat(" ", indent)) 457 buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name)) 458 return nil 459 } 460 listVals := ctyCollectionValues(stateVal) 461 for i := range listVals { 462 buf.WriteString(strings.Repeat(" ", indent)) 463 buf.WriteString(fmt.Sprintf("%s {\n", name)) 464 if err := v.writeConfigAttributesFromExisting(buf, listVals[i], schema.Attributes, indent+2); err != nil { 465 return err 466 } 467 if err := v.writeConfigBlocksFromExisting(buf, listVals[i], schema.BlockTypes, indent+2); err != nil { 468 return err 469 } 470 buf.WriteString("}\n") 471 } 472 return nil 473 case configschema.NestingMap: 474 // If the entire value is marked, don't print any nested attributes 475 if stateVal.HasMark(marks.Sensitive) { 476 buf.WriteString(fmt.Sprintf("%s {} # sensitive\n", name)) 477 return nil 478 } 479 480 vals := stateVal.AsValueMap() 481 keys := make([]string, 0, len(vals)) 482 for key := range vals { 483 keys = append(keys, key) 484 } 485 sort.Strings(keys) 486 for _, key := range keys { 487 buf.WriteString(strings.Repeat(" ", indent)) 488 buf.WriteString(fmt.Sprintf("%s %q {", name, key)) 489 // This entire map element is marked 490 if vals[key].HasMark(marks.Sensitive) { 491 buf.WriteString("} # sensitive\n") 492 return nil 493 } 494 buf.WriteString("\n") 495 496 if err := v.writeConfigAttributesFromExisting(buf, vals[key], schema.Attributes, indent+2); err != nil { 497 return err 498 } 499 if err := v.writeConfigBlocksFromExisting(buf, vals[key], schema.BlockTypes, indent+2); err != nil { 500 return err 501 } 502 buf.WriteString(strings.Repeat(" ", indent)) 503 buf.WriteString("}\n") 504 } 505 return nil 506 default: 507 // This should not happen, the above should be exhaustive. 508 return fmt.Errorf("unsupported NestingMode %s", schema.Nesting.String()) 509 } 510 } 511 512 func writeAttrTypeConstraint(buf *strings.Builder, schema *configschema.Attribute) { 513 if schema.Required { 514 buf.WriteString(" # REQUIRED ") 515 } else { 516 buf.WriteString(" # OPTIONAL ") 517 } 518 519 if schema.NestedType != nil { 520 buf.WriteString(fmt.Sprintf("%s\n", schema.NestedType.ImpliedType().FriendlyName())) 521 } else { 522 buf.WriteString(fmt.Sprintf("%s\n", schema.Type.FriendlyName())) 523 } 524 } 525 526 func writeBlockTypeConstraint(buf *strings.Builder, schema *configschema.NestedBlock) { 527 if schema.MinItems > 0 { 528 buf.WriteString(" # REQUIRED block\n") 529 } else { 530 buf.WriteString(" # OPTIONAL block\n") 531 } 532 } 533 534 // copied from command/format/diff 535 func ctyCollectionValues(val cty.Value) []cty.Value { 536 if !val.IsKnown() || val.IsNull() { 537 return nil 538 } 539 540 var len int 541 if val.IsMarked() { 542 val, _ = val.Unmark() 543 len = val.LengthInt() 544 } else { 545 len = val.LengthInt() 546 } 547 548 ret := make([]cty.Value, 0, len) 549 for it := val.ElementIterator(); it.Next(); { 550 _, value := it.Element() 551 ret = append(ret, value) 552 } 553 554 return ret 555 }