From 43d753e727c4248e8d5bf8a703b1937f75c55c76 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 18 Aug 2021 14:30:13 -0700 Subject: [PATCH] command: "terraform add" is experimental We're aware of several quirks of this command's current design, which result from some existing architectural limitations that we can't address immediately. However, we do still want to make this command available in its current capacity as an incremental improvement, so as a compromise we'll document it as experimental. Our intent here is to exclude it from the Terraform 1.0 Compatibility Promises so that we can have the space to continue to improve the design as other parts of the overall Terraform system gain new capabilities. We don't currently have any concrete plan for this command to be stabilized and subject to compatibility promises. That decision will follow from ongoing discussions with other teams whose systems may need to change in order to support the final design of "terraform add". --- internal/command/add.go | 27 +++++---- internal/command/add_test.go | 84 +++++++++++++++++++++++---- internal/command/views/add.go | 12 +++- internal/command/views/add_test.go | 28 +++++++-- website/docs/cli/commands/add.html.md | 71 +++++++++++++++++----- 5 files changed, 179 insertions(+), 43 deletions(-) diff --git a/internal/command/add.go b/internal/command/add.go index bb989e31e..a49adc2ee 100644 --- a/internal/command/add.go +++ b/internal/command/add.go @@ -248,11 +248,10 @@ func (c *AddCommand) Run(rawArgs []string) int { } diags = diags.Append(view.Resource(args.Addr, schema, localProviderConfig, stateVal)) + c.View.Diagnostics(diags) if diags.HasErrors() { - c.View.Diagnostics(diags) return 1 } - return 0 } @@ -260,21 +259,27 @@ func (c *AddCommand) Help() string { helpText := ` Usage: terraform [global options] add [options] ADDRESS - Generates a blank resource template. With no additional options, - the template will be displayed in the terminal. + Generates a blank resource template. With no additional options, Terraform + will write the result to standard output. Options: --from-state=true Fill the template with values from an existing resource. - Defaults to false. + -from-state Fill the template with values from an existing resource + instance tracked in the state. By default, Terraform will + emit only placeholder values based on the resource type. --out=string Write the template to a file. If the file already - exists, the template will be appended to the file. + -out=string Write the template to a file, instead of to standard + output. --optional=true Include optional attributes. Defaults to false. + -optional Include optional arguments. By default, the result will + include only required arguments. --provider=provider Override the configured provider for the resource. Conflicts - with -from-state + -provider=provider Override the provider configuration for the resource, + using the absolute provider configuration address syntax. + + This is incompatible with -from-state, because in that + case Terraform will use the provider configuration already + selected in the state. ` return strings.TrimSpace(helpText) } diff --git a/internal/command/add_test.go b/internal/command/add_test.go index 18661d1cc..117c55255 100644 --- a/internal/command/add_test.go +++ b/internal/command/add_test.go @@ -59,7 +59,14 @@ func TestAdd_basic(t *testing.T) { fmt.Println(output.Stderr()) t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { value = null # REQUIRED string } ` @@ -85,7 +92,14 @@ func TestAdd_basic(t *testing.T) { fmt.Println(output.Stderr()) t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { value = null # REQUIRED string } ` @@ -117,7 +131,14 @@ func TestAdd_basic(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } output := done(t) - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { ami = null # OPTIONAL string id = null # OPTIONAL string value = null # REQUIRED string @@ -145,9 +166,17 @@ func TestAdd_basic(t *testing.T) { } // The provider happycorp/test has a localname "othertest" in the provider configuration. - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { provider = othertest.alias - value = null # REQUIRED string + + value = null # REQUIRED string } ` @@ -317,7 +346,14 @@ func TestAdd(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { ami = null # OPTIONAL string disks = [{ # OPTIONAL list of object mount_point = null # REQUIRED string @@ -354,7 +390,14 @@ func TestAdd(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { value = null # REQUIRED string network_interface { # REQUIRED block } @@ -382,7 +425,14 @@ func TestAdd(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { id = null # REQUIRED string } ` @@ -410,7 +460,14 @@ func TestAdd(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { id = null # REQUIRED string } ` @@ -511,7 +568,14 @@ func TestAdd_from_state(t *testing.T) { t.Fatalf("wrong exit status. Got %d, want 0", code) } - expected := `resource "test_instance" "new" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "new" { ami = "ami-123456" disks = [ { diff --git a/internal/command/views/add.go b/internal/command/views/add.go index 4b1c96d70..233ae7f6e 100644 --- a/internal/command/views/add.go +++ b/internal/command/views/add.go @@ -39,11 +39,21 @@ type addHuman struct { func (v *addHuman) Resource(addr addrs.AbsResourceInstance, schema *configschema.Block, pc addrs.LocalProviderConfig, stateVal cty.Value) error { var buf strings.Builder + + buf.WriteString(`# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +`) + buf.WriteString(fmt.Sprintf("resource %q %q {\n", addr.Resource.Resource.Type, addr.Resource.Resource.Name)) if pc.LocalName != addr.Resource.Resource.ImpliedProvider() || pc.Alias != "" { buf.WriteString(strings.Repeat(" ", 2)) - buf.WriteString(fmt.Sprintf("provider = %s\n", pc.StringCompact())) + buf.WriteString(fmt.Sprintf("provider = %s\n\n", pc.StringCompact())) } if stateVal.RawEquals(cty.NilVal) { diff --git a/internal/command/views/add_test.go b/internal/command/views/add_test.go index 1ad0cc747..c0986e4da 100644 --- a/internal/command/views/add_test.go +++ b/internal/command/views/add_test.go @@ -27,9 +27,17 @@ func TestAddResource(t *testing.T) { t.Fatal(err.Error()) } - expected := `resource "test_instance" "foo" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "foo" { provider = mytest - ami = null # OPTIONAL string + + ami = null # OPTIONAL string disks = { # OPTIONAL object mount_point = null # OPTIONAL string size = null # OPTIONAL string @@ -66,11 +74,19 @@ func TestAddResource(t *testing.T) { t.Fatal(err.Error()) } - expected := `resource "test_instance" "foo" { + expected := `# NOTE: The "terraform add" command is currently experimental and offers only a +# starting point for your resource configuration, with some limitations. +# +# The behavior of this command may change in future based on feedback, possibly +# in incompatible ways. We don't recommend building automation around this +# command at this time. If you have feedback about this command, please open +# a feature request issue in the Terraform GitHub repository. +resource "test_instance" "foo" { provider = mytest - ami = "ami-123456789" - disks = {} # sensitive - id = null + + ami = "ami-123456789" + disks = {} # sensitive + id = null } ` output := done(t) diff --git a/website/docs/cli/commands/add.html.md b/website/docs/cli/commands/add.html.md index 711ba73d2..73364f06d 100644 --- a/website/docs/cli/commands/add.html.md +++ b/website/docs/cli/commands/add.html.md @@ -8,14 +8,34 @@ description: |- # Command: add -The `terraform add` command generates a resource configuration template with -`null` placeholder values for all attributes, unless the `-from-state` flag is -used. By default, the template only includes required resource attributes; the -`-optional` flag tells Terraform to also include any optional attributes. +The `terraform add` command generates a starting point for the configuration +of a particular resource. -When `terraform add` used with the `-from-state` will _not_ print sensitive -values. You can use `terraform show ADDRESS` to see all values, including -sensitive values, recorded in state for the given resource address. +~> **Warning:** This command is currently experimental. Its exact behavior and +command line arguments are likely to change in future releases based on +feedback. We don't recommend building automation around the current design of +this command, but it's safe to use directly in a development environment +setting. + +By default, Terraform will include only the subset of arguments that are marked +by the provider as being required, and will use `null` as a placeholder for +their values. You can then replace `null` with suitable expressions in order +to make the arguments valid. + +If you use the `-optional` option then Terraform will also include arguments +that the provider declares as optional. You can then either write a suitable +expression for each argument or remove the arguments you wish to leave unset. + +If you use the `-from-state` option then Terraform will instead generate a +configuration containing expressions which will produce the same values as +the corresponding resource instance object already tracked in the Terraform +state, if for example you've previously imported the object using +[`terraform import`](import.html). + +-> **Note:** If you use `-from-state`, the result will not include expressions +for any values which are marked as sensitive in the state. If you want to +see those, you can inspect the state data directly using +`terraform state show ADDRESS`. ## Usage @@ -27,14 +47,35 @@ already exist in the configuration. Addresses are in This command accepts the following options: -`-from-state` - populate the template with values from a resource -already in state. Sensitive values are redacted. +* `-from-state` - Fill the template with values from an existing resource + instance already tracked in the state. By default, Terraform will emit only + placeholder values based on the resource type. -`-optional` - include optional attributes in the template. +* `-optional` - Include optional arguments. By default, the result will + include only required arguments. -`-out=FILENAME` - writes the template to the given filename. If the file already -exists, the template will be added to the end of the file. +* `-out=FILENAME` - Write the template to a file, instead of to standard + output. -`-provider=provider` - override the configured provider for the resource. By -default, Terraform will use the configured provider for the given resource type, -and that is the best behavior in most cases. +* `-provider=provider` - Override the provider configuration for the resource, +using the absolute provider configuration address syntax. + + Absolute provider configuration syntax uses the full source address of + the provider, rather than a local name declared in the relevant module. + For example, to select the aliased provider configuration "us-east-1" + of the official AWS provider, use: + + ``` + -provider='provider["hashicorp/aws"].us-east-1' + ``` + + or, if you are using the Windows command prompt, use Windows-style escaping + for the quotes inside the address: + + ``` + -provider=provider[\"hashicorp/aws\"].us-east-1 + ``` + + This is incompatible with `-from-state`, because in that case Terraform + will use the provider configuration already selected in the state, which + is the provider configuration that most recently managed the object.