mirror of https://github.com/hashicorp/terraform
stacks: add support for the removed block to .tfstacks.hcl (#35669)
parent
7163c4b6d5
commit
36971f6ee8
@ -0,0 +1,137 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package stackaddrs
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// ParseRemovedFrom parses the "from" attribute of a "removed" block in a
|
||||
// configuration and returns the address of the configuration object being
|
||||
// removed.
|
||||
//
|
||||
// In addition to the address, this function also returns a traversal that
|
||||
// represents the unparsed index within the from expression. Users can
|
||||
// optionally specify a specific index of a component to target.
|
||||
func ParseRemovedFrom(expr hcl.Expression) (Component, hcl.Expression, tfdiags.Diagnostics) {
|
||||
var component Component
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
traversal, index, hclDiags := exprToComponentTraversal(expr)
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return component, index, diags
|
||||
}
|
||||
|
||||
if len(traversal) < 2 {
|
||||
return component, index, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
root, ok := traversal[0].(hcl.TraverseRoot)
|
||||
if !ok || root.Name != "component" {
|
||||
return component, index, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
name, ok := traversal[1].(hcl.TraverseAttr)
|
||||
if !ok {
|
||||
return component, index, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
component.Name = name.Name
|
||||
|
||||
return component, index, diags
|
||||
}
|
||||
|
||||
// exprToComponentTraversal converts an HCL expression into a traversal that
|
||||
// represents the component being targeted. We have to handle parsing this
|
||||
// ourselves because removed block from arguments can contain index expressions
|
||||
// which are not supported by hcl.AbsTraversalForExpr.
|
||||
func exprToComponentTraversal(expr hcl.Expression) (hcl.Traversal, hcl.Expression, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
switch e := expr.(type) {
|
||||
case *hclsyntax.IndexExpr:
|
||||
t, d := hcl.AbsTraversalForExpr(e.Collection)
|
||||
diags = diags.Extend(d)
|
||||
if d.HasErrors() {
|
||||
return nil, nil, diags
|
||||
}
|
||||
return t, e.Key, diags
|
||||
case *hclsyntax.RelativeTraversalExpr:
|
||||
|
||||
// This is an expression of the form `component.component_name[each.key].attribute`.
|
||||
// This is invalid at the moment, as we only support direct component
|
||||
// references. We'll return our own diagnostic here.
|
||||
|
||||
return nil, nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
|
||||
default:
|
||||
|
||||
// For anything else, just rely on the default traversal logic.
|
||||
|
||||
t, d := hcl.AbsTraversalForExpr(expr)
|
||||
diags = diags.Extend(d)
|
||||
if d.HasErrors() {
|
||||
return nil, nil, diags
|
||||
}
|
||||
|
||||
if len(t) < 2 {
|
||||
return nil, nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
// For now, removed blocks only support direct component references.
|
||||
// ie. you can't target a resource within a component, the next check
|
||||
// ensures this is true.
|
||||
|
||||
if len(t) > 3 {
|
||||
return nil, nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
if len(t) == 2 {
|
||||
return t, nil, diags
|
||||
}
|
||||
|
||||
if index, ok := t[2].(hcl.TraverseIndex); ok {
|
||||
return t[:2], hcl.StaticExpr(index.Key, index.SrcRange), diags
|
||||
}
|
||||
|
||||
return nil, nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,270 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package stackaddrs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty-debug/ctydebug"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
func TestParseRemovedFrom(t *testing.T) {
|
||||
|
||||
mustExpr := func(t *testing.T, expr string) hcl.Expression {
|
||||
ret, diags := hclsyntax.ParseExpression([]byte(expr), "", hcl.InitialPos)
|
||||
if diags.HasErrors() {
|
||||
t.Fatal(diags.Error())
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
tcs := []struct {
|
||||
from string
|
||||
component Component
|
||||
index cty.Value
|
||||
vars map[string]cty.Value
|
||||
diags func() tfdiags.Diagnostics
|
||||
}{
|
||||
{
|
||||
from: "component.component_name",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[0]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.NumberIntVal(0),
|
||||
},
|
||||
{
|
||||
from: "component.component_name[\"key\"]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("key"),
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.key]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("key"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("key"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.value.attribute]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("attribute"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.ObjectVal(map[string]cty.Value{
|
||||
"attribute": cty.StringVal("attribute"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.value[\"key\"]]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("key"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("key"),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.value[\"key\"].attribute]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("attribute"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.ObjectVal(map[string]cty.Value{
|
||||
"attribute": cty.StringVal("attribute"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.value[local.key]]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("key"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("key"),
|
||||
}),
|
||||
}),
|
||||
"local": cty.ObjectVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("key"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.value[local.key].attribute]",
|
||||
component: Component{
|
||||
Name: "component_name",
|
||||
},
|
||||
index: cty.StringVal("attribute"),
|
||||
vars: map[string]cty.Value{
|
||||
"each": cty.ObjectVal(map[string]cty.Value{
|
||||
"value": cty.MapVal(map[string]cty.Value{
|
||||
"key": cty.ObjectVal(map[string]cty.Value{
|
||||
"attribute": cty.StringVal("attribute"),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
"local": cty.ObjectVal(map[string]cty.Value{
|
||||
"key": cty.StringVal("key"),
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name.attribute_key",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[0].attribute_key",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[\"key\"].attribute_key",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name[each.key].attribute_key",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component.component_name.attribute_key[0]",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
{
|
||||
from: "component[0].component_name",
|
||||
diags: func() tfdiags.Diagnostics {
|
||||
var diags tfdiags.Diagnostics
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'from' attribute",
|
||||
Detail: "The 'from' attribute must designate a component that has been removed, in the form `component.component_name` or `component.component_name[\"key\"].",
|
||||
})
|
||||
return diags
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcs {
|
||||
t.Run(tc.from, func(t *testing.T) {
|
||||
expr := mustExpr(t, tc.from)
|
||||
component, index, gotDiags := ParseRemovedFrom(expr)
|
||||
|
||||
// validate the component first
|
||||
if diff := cmp.Diff(tc.component, component); len(diff) > 0 {
|
||||
t.Errorf("unexpected result\n%s", diff)
|
||||
}
|
||||
|
||||
// validate the index
|
||||
if index == nil {
|
||||
if tc.index != cty.NilVal {
|
||||
t.Errorf("expected index but got nil")
|
||||
}
|
||||
} else {
|
||||
gotIndex, indexDiags := index.Value(&hcl.EvalContext{
|
||||
Variables: tc.vars,
|
||||
})
|
||||
if len(indexDiags) > 0 {
|
||||
t.Errorf("unexpected index diagnostics: %s", indexDiags.Error())
|
||||
}
|
||||
if diff := cmp.Diff(tc.index, gotIndex, ctydebug.CmpOptions); len(diff) > 0 {
|
||||
t.Errorf("unexpected index\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
// validate the diagnostics
|
||||
|
||||
var wantDiags tfdiags.Diagnostics
|
||||
if tc.diags != nil {
|
||||
wantDiags = tc.diags()
|
||||
}
|
||||
if len(gotDiags) != len(wantDiags) {
|
||||
t.Errorf("wrong number of diagnostics")
|
||||
}
|
||||
for ix, got := range gotDiags {
|
||||
want := wantDiags[ix]
|
||||
|
||||
if want.Severity() != got.Severity() {
|
||||
t.Errorf("unexpected severity: got %s, want %s", got.Severity(), want.Severity())
|
||||
}
|
||||
if diff := cmp.Diff(want.Description(), got.Description()); len(diff) > 0 {
|
||||
t.Errorf("unexpected description\n%s", diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: BUSL-1.1
|
||||
|
||||
package stackconfig
|
||||
|
||||
import (
|
||||
"github.com/apparentlymart/go-versions/versions/constraints"
|
||||
"github.com/hashicorp/go-slug/sourceaddrs"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
||||
"github.com/hashicorp/terraform/internal/tfdiags"
|
||||
)
|
||||
|
||||
// Removed represents a component that was removed from the configuration.
|
||||
//
|
||||
// Removed blocks don't have labels associated with them, instead they have
|
||||
// a "from" attribute that points directly to the old component that was
|
||||
// removed. Removed blocks can also point to component instances specifically,
|
||||
// using an index expression. The "for_each" attribute also means that the
|
||||
// "from" attribute can't always be evaluated statically.
|
||||
//
|
||||
// Removed blocks are, therefore, represented by the FromComponent and FromIndex
|
||||
// fields, which together represent the address of the removed component. The
|
||||
// FromComponent field is the address of the component itself, and the FromIndex
|
||||
// field is the index expression that will be evaluated to determine the
|
||||
// specific instance of the component that was removed.
|
||||
//
|
||||
// FromIndex can be null if either the removed block is pointing to a component
|
||||
// that was not instanced, or is pointing to all the instances of a removed
|
||||
// component.
|
||||
//
|
||||
// For this reason, multiple Removed blocks can be associated with the same
|
||||
// FromComponent, but with different FromIndex values. When the FromIndex values
|
||||
// are evaluated, during the planning stage, we will validate that the FromIndex
|
||||
// values are unique.
|
||||
type Removed struct {
|
||||
FromComponent stackaddrs.Component
|
||||
FromIndex hcl.Expression
|
||||
|
||||
SourceAddr sourceaddrs.Source
|
||||
VersionConstraints constraints.IntersectionSpec
|
||||
SourceAddrRange, VersionConstraintsRange tfdiags.SourceRange
|
||||
|
||||
// FinalSourceAddr is populated only when a configuration is loaded
|
||||
// through [LoadConfigDir], and in that case contains the finalized
|
||||
// address produced by resolving the SourceAddr field relative to
|
||||
// the address of the file where the component was declared. This
|
||||
// is the address to use if you intend to load the component's
|
||||
// root module from a source bundle.
|
||||
FinalSourceAddr sourceaddrs.FinalSource
|
||||
|
||||
ForEach hcl.Expression
|
||||
|
||||
// ProviderConfigs describes the mapping between the static provider
|
||||
// configuration slots declared in the component's root module and the
|
||||
// dynamic provider configuration objects in scope in the calling
|
||||
// stack configuration.
|
||||
//
|
||||
// This map deals with the slight schism between the stacks language's
|
||||
// treatment of provider configurations as regular values of a special
|
||||
// data type vs. the main Terraform language's treatment of provider
|
||||
// configurations as something special passed out of band from the
|
||||
// input variables. The overall structure and the map keys are fixed
|
||||
// statically during decoding, but the final provider configuration objects
|
||||
// are determined only at runtime by normal expression evaluation.
|
||||
//
|
||||
// The keys of this map refer to provider configuration slots inside
|
||||
// the module being called, but use the local names defined in the
|
||||
// calling stack configuration. The stacks language runtime will
|
||||
// translate the caller's local names into the callee's declared provider
|
||||
// configurations by using the stack configuration's table of local
|
||||
// provider names.
|
||||
ProviderConfigs map[addrs.LocalProviderConfig]hcl.Expression
|
||||
|
||||
// Destroy controls whether this removed block will actually destroy all
|
||||
// instances of resources within this component, or just removed them from
|
||||
// the state. Defaults to true.
|
||||
Destroy bool
|
||||
|
||||
DeclRange tfdiags.SourceRange
|
||||
}
|
||||
|
||||
func decodeRemovedBlock(block *hcl.Block) (*Removed, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
ret := &Removed{
|
||||
DeclRange: tfdiags.SourceRangeFromHCL(block.DefRange),
|
||||
}
|
||||
|
||||
content, hclDiags := block.Body.Content(removedBlockSchema)
|
||||
diags = diags.Append(hclDiags)
|
||||
if hclDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// We're splitting out the component and the index now, as we can decode and
|
||||
// analyse the component now. The index might be referencing the for_each
|
||||
// variable, which we can't decode yet.
|
||||
component, index, moreDiags := stackaddrs.ParseRemovedFrom(content.Attributes["from"].Expr)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
ret.FromComponent = component
|
||||
ret.FromIndex = index
|
||||
|
||||
sourceAddr, versionConstraints, moreDiags := decodeSourceAddrArguments(
|
||||
content.Attributes["source"],
|
||||
content.Attributes["version"],
|
||||
)
|
||||
diags = diags.Append(moreDiags)
|
||||
if moreDiags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
ret.SourceAddr = sourceAddr
|
||||
ret.VersionConstraints = versionConstraints
|
||||
ret.SourceAddrRange = tfdiags.SourceRangeFromHCL(content.Attributes["source"].Range)
|
||||
if content.Attributes["version"] != nil {
|
||||
ret.VersionConstraintsRange = tfdiags.SourceRangeFromHCL(content.Attributes["version"].Range)
|
||||
}
|
||||
// Now that we've populated the mandatory source location fields we can
|
||||
// safely return a partial ret if we encounter any further errors, as
|
||||
// long as we leave the other fields either unset or in some other
|
||||
// reasonable state for careful partial analysis.
|
||||
|
||||
if attr, ok := content.Attributes["for_each"]; ok {
|
||||
ret.ForEach = attr.Expr
|
||||
}
|
||||
if attr, ok := content.Attributes["providers"]; ok {
|
||||
var providerDiags tfdiags.Diagnostics
|
||||
ret.ProviderConfigs, providerDiags = decodeProvidersAttribute(attr)
|
||||
diags = diags.Append(providerDiags)
|
||||
}
|
||||
|
||||
ret.Destroy = true // default to true
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case "lifecycle":
|
||||
lcContent, lcDiags := block.Body.Content(removedLifecycleBlockSchema)
|
||||
diags = diags.Append(lcDiags)
|
||||
|
||||
if attr, ok := lcContent.Attributes["destroy"]; ok {
|
||||
valDiags := gohcl.DecodeExpression(attr.Expr, nil, &ret.Destroy)
|
||||
diags = diags.Append(valDiags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
var removedBlockSchema = &hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{Type: "lifecycle"},
|
||||
},
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{Name: "from", Required: true},
|
||||
{Name: "source", Required: true},
|
||||
{Name: "version", Required: false},
|
||||
{Name: "for_each", Required: false},
|
||||
{Name: "providers", Required: false},
|
||||
},
|
||||
}
|
||||
|
||||
var removedLifecycleBlockSchema = &hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{Name: "destroy"},
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
variable "name" {
|
||||
type = string
|
||||
}
|
||||
|
||||
resource "null_resource" "example" {
|
||||
triggers = {
|
||||
name = var.name
|
||||
}
|
||||
}
|
||||
|
||||
output "greeting" {
|
||||
value = "Hello, ${var.name}!"
|
||||
}
|
||||
|
||||
output "resource_id" {
|
||||
value = null_resource.example.id
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
required_providers {
|
||||
null = {
|
||||
source = "hashicorp/null"
|
||||
version = "3.2.1"
|
||||
}
|
||||
}
|
||||
|
||||
provider "null" "a" {}
|
||||
|
||||
component "a" {
|
||||
source = "./"
|
||||
|
||||
inputs = {
|
||||
name = var.name
|
||||
}
|
||||
|
||||
providers = {
|
||||
null = provider.null.a
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
// This is invalid, you can't reference the whole component like this if
|
||||
// the target component is still in the config.
|
||||
from = component.a
|
||||
|
||||
source = "./"
|
||||
|
||||
providers = {
|
||||
null = provider.null.a
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue