Skip to content

Commit 7414a3f

Browse files
authored
Fix import with optional identity attributes (#36887)
* tfdiags: Support null values when printing objects * Use type with optional fields when decoding identity config * Use provider returned identity when importing * Add test case for import with optional identity attrs
1 parent be242c3 commit 7414a3f

File tree

6 files changed

+110
-2
lines changed

6 files changed

+110
-2
lines changed

internal/configs/configschema/implied_type.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,14 @@ func (o *Object) ImpliedType() cty.Type {
107107
return o.specType().WithoutOptionalAttributesDeep()
108108
}
109109

110+
// ConfigType returns a cty.Type that can be used to decode a configuration
111+
// object using the receiving block schema.
112+
//
113+
// ConfigType will preserve optional attributes
114+
func (o *Object) ConfigType() cty.Type {
115+
return o.specType()
116+
}
117+
110118
// specType returns the cty.Type used for decoding a NestedType Attribute using
111119
// the receiving block schema.
112120
func (o *Object) specType() cty.Type {

internal/terraform/context_plan_import_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,9 @@ func TestContext2Plan_importIdentityModule(t *testing.T) {
19611961
State: cty.ObjectVal(map[string]cty.Value{
19621962
"id": cty.StringVal("foo"),
19631963
}),
1964+
Identity: cty.ObjectVal(map[string]cty.Value{
1965+
"name": cty.StringVal("bar"),
1966+
}),
19641967
},
19651968
},
19661969
}
@@ -2138,3 +2141,79 @@ import {
21382141
}
21392142
})
21402143
}
2144+
2145+
func TestContext2Plan_importIdentityModuleWithOptional(t *testing.T) {
2146+
p := testProvider("aws")
2147+
m := testModule(t, "import-identity-module")
2148+
2149+
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
2150+
ResourceTypes: map[string]*configschema.Block{
2151+
"aws_lb": {
2152+
Attributes: map[string]*configschema.Attribute{
2153+
"id": {
2154+
Type: cty.String,
2155+
Computed: true,
2156+
},
2157+
},
2158+
},
2159+
},
2160+
IdentityTypes: map[string]*configschema.Object{
2161+
"aws_lb": {
2162+
Attributes: map[string]*configschema.Attribute{
2163+
"name": {
2164+
Type: cty.String,
2165+
Required: true,
2166+
},
2167+
"something": {
2168+
Type: cty.Number,
2169+
Optional: true,
2170+
},
2171+
},
2172+
Nesting: configschema.NestingSingle,
2173+
},
2174+
},
2175+
})
2176+
wantIdentity := cty.ObjectVal(map[string]cty.Value{
2177+
"name": cty.StringVal("bar"),
2178+
"something": cty.NumberIntVal(42),
2179+
})
2180+
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
2181+
ImportedResources: []providers.ImportedResource{
2182+
{
2183+
TypeName: "aws_lb",
2184+
State: cty.ObjectVal(map[string]cty.Value{
2185+
"id": cty.StringVal("foo"),
2186+
}),
2187+
Identity: wantIdentity,
2188+
},
2189+
},
2190+
}
2191+
ctx := testContext2(t, &ContextOpts{
2192+
Providers: map[addrs.Provider]providers.Factory{
2193+
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
2194+
},
2195+
})
2196+
2197+
diags := ctx.Validate(m, &ValidateOpts{})
2198+
if diags.HasErrors() {
2199+
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
2200+
}
2201+
2202+
plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
2203+
if diags.HasErrors() {
2204+
t.Fatalf("unexpected errors: %s", diags.Err())
2205+
}
2206+
2207+
addr := mustResourceInstanceAddr("aws_lb.foo")
2208+
instPlan := plan.Changes.ResourceInstance(addr)
2209+
if instPlan == nil {
2210+
t.Fatalf("no plan for %s at all", addr)
2211+
}
2212+
2213+
identityMatches := instPlan.Importing.Identity.Equals(wantIdentity)
2214+
if !identityMatches.True() {
2215+
t.Errorf("identity does not match\ngot: %s\nwant: %s",
2216+
tfdiags.ObjectToString(instPlan.Importing.Identity),
2217+
tfdiags.ObjectToString(wantIdentity))
2218+
}
2219+
}

internal/terraform/eval_import.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func evaluateImportIdentityExpression(expr hcl.Expression, identity *configschem
8888
// that context.
8989
ctx = evalContextForModuleInstance(ctx, addrs.RootModuleInstance)
9090
scope := ctx.EvaluationScope(nil, nil, keyData)
91-
importIdentityVal, evalDiags := scope.EvalExpr(expr, identity.ImpliedType())
91+
importIdentityVal, evalDiags := scope.EvalExpr(expr, identity.ConfigType())
9292
if evalDiags.HasErrors() {
9393
// TODO? Do we need to improve the error message?
9494
return cty.NilVal, evalDiags

internal/terraform/node_resource_plan_instance.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,15 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext)
386386
}
387387

388388
if importing {
389-
change.Importing = &plans.Importing{Target: n.importTarget}
389+
// There is a subtle difference between the import by identity
390+
// and the import by ID. When importing by identity, we need to
391+
// make sure to use the complete identity return by the provider
392+
// instead of the (potential) incomplete one from the configuration.
393+
if n.importTarget.Type().IsObjectType() {
394+
change.Importing = &plans.Importing{Target: instanceRefreshState.Identity}
395+
} else {
396+
change.Importing = &plans.Importing{Target: n.importTarget}
397+
}
390398
}
391399

392400
// FIXME: here we udpate the change to reflect the reason for

internal/tfdiags/object.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ func ObjectToString(obj cty.Value) string {
3434
result += ","
3535
}
3636

37+
if val.IsNull() {
38+
result += fmt.Sprintf("%s=<null>", keyStr)
39+
continue
40+
}
41+
3742
switch val.Type() {
3843
case cty.Bool:
3944
result += fmt.Sprintf("%s=%t", keyStr, val.True())

internal/tfdiags/object_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ func Test_ObjectToString(t *testing.T) {
5050
}),
5151
expected: "list=[a,b,c],string=hello",
5252
},
53+
{
54+
name: "with null value",
55+
value: cty.ObjectVal(map[string]cty.Value{
56+
"string": cty.StringVal("hello"),
57+
"null": cty.NullVal(cty.String),
58+
}),
59+
expected: "null=<null>,string=hello",
60+
},
5361
}
5462

5563
for _, tc := range testCases {

0 commit comments

Comments
 (0)