Skip to content

Commit 1b693ae

Browse files
committed
Merge branch 'main' of github.com:hashicorp/terraform into liamcervante/stacks/refresh-with-normal-plan
2 parents 9df9077 + c16d466 commit 1b693ae

File tree

20 files changed

+2517
-1766
lines changed

20 files changed

+2517
-1766
lines changed

internal/command/jsonstate/state.go

+53
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,26 @@ type Resource struct {
110110

111111
// Deposed is set if the resource is deposed in terraform state.
112112
DeposedKey string `json:"deposed_key,omitempty"`
113+
114+
// The version of the resource identity schema the "identity" property
115+
// conforms to.
116+
// It's a pointer, because it should be optional, but also 0 is a valid
117+
// schema version.
118+
IdentitySchemaVersion *uint64 `json:"identity_schema_version,omitempty"`
119+
120+
// The JSON representation of the resource identity, whose structure
121+
// depends on the resource identity schema.
122+
IdentityValues IdentityValues `json:"identity,omitempty"`
113123
}
114124

115125
// AttributeValues is the JSON representation of the attribute values of the
116126
// resource, whose structure depends on the resource type schema.
117127
type AttributeValues map[string]json.RawMessage
118128

129+
// IdentityValues is the JSON representation of the identity values of the
130+
// resource, whose structure depends on the resource identity schema.
131+
type IdentityValues map[string]json.RawMessage
132+
119133
func marshalAttributeValues(value cty.Value) (unmarkedVal cty.Value, marshalledVals AttributeValues, sensitivePaths []cty.Path, err error) {
120134
// unmark our value to show all values
121135
value, sensitivePaths, err = unmarkValueForMarshaling(value)
@@ -138,6 +152,21 @@ func marshalAttributeValues(value cty.Value) (unmarkedVal cty.Value, marshalledV
138152
return value, ret, sensitivePaths, nil
139153
}
140154

155+
func marshalIdentityValues(value cty.Value) (IdentityValues, error) {
156+
if value == cty.NilVal || value.IsNull() {
157+
return nil, nil
158+
}
159+
160+
ret := make(IdentityValues)
161+
it := value.ElementIterator()
162+
for it.Next() {
163+
k, v := it.Element()
164+
vJSON, _ := ctyjson.Marshal(v, v.Type())
165+
ret[k.AsString()] = json.RawMessage(vJSON)
166+
}
167+
return ret, nil
168+
}
169+
141170
// newState() returns a minimally-initialized state
142171
func newState() *state {
143172
return &state{
@@ -396,6 +425,20 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
396425
if schema.Body == nil {
397426
return nil, fmt.Errorf("no schema found for %s (in provider %s)", resAddr.String(), r.ProviderConfig.Provider)
398427
}
428+
429+
// Check if we have an identity in the state
430+
if ri.Current.IdentityJSON != nil {
431+
if schema.IdentityVersion != int64(ri.Current.IdentitySchemaVersion) {
432+
return nil, fmt.Errorf("resource identity schema version %d for %s in state does not match version %d from the provider", ri.Current.IdentitySchemaVersion, resAddr, schema.IdentityVersion)
433+
}
434+
435+
if schema.Identity == nil {
436+
return nil, fmt.Errorf("no resource identity schema found for %s (in provider %s)", resAddr.String(), r.ProviderConfig.Provider)
437+
}
438+
439+
current.IdentitySchemaVersion = &ri.Current.IdentitySchemaVersion
440+
}
441+
399442
riObj, err := ri.Current.Decode(schema)
400443
if err != nil {
401444
return nil, err
@@ -415,6 +458,11 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
415458
}
416459
current.SensitiveValues = v
417460

461+
current.IdentityValues, err = marshalIdentityValues(riObj.Identity)
462+
if err != nil {
463+
return nil, fmt.Errorf("preparing identity values for %s: %w", current.Address, err)
464+
}
465+
418466
if len(riObj.Dependencies) > 0 {
419467
dependencies := make([]string, len(riObj.Dependencies))
420468
for i, v := range riObj.Dependencies {
@@ -467,6 +515,11 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
467515
}
468516
deposed.SensitiveValues = v
469517

518+
deposed.IdentityValues, err = marshalIdentityValues(riObj.Identity)
519+
if err != nil {
520+
return nil, fmt.Errorf("preparing identity values for %s: %w", current.Address, err)
521+
}
522+
470523
if len(riObj.Dependencies) > 0 {
471524
dependencies := make([]string, len(riObj.Dependencies))
472525
for i, v := range riObj.Dependencies {

internal/command/jsonstate/state_test.go

+129
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import (
2020
"github.com/hashicorp/terraform/internal/terraform"
2121
)
2222

23+
func ptrOf[T any](v T) *T {
24+
return &v
25+
}
26+
2327
func TestMarshalOutputs(t *testing.T) {
2428
tests := []struct {
2529
Outputs map[string]*states.OutputValue
@@ -620,6 +624,115 @@ func TestMarshalResources(t *testing.T) {
620624
},
621625
false,
622626
},
627+
"single resource with identity": {
628+
map[string]*states.Resource{
629+
"test_identity.bar": {
630+
Addr: addrs.AbsResource{
631+
Resource: addrs.Resource{
632+
Mode: addrs.ManagedResourceMode,
633+
Type: "test_identity",
634+
Name: "bar",
635+
},
636+
},
637+
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
638+
addrs.NoKey: {
639+
Current: &states.ResourceInstanceObjectSrc{
640+
Status: states.ObjectReady,
641+
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles","name":"bar"}`),
642+
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
643+
},
644+
},
645+
},
646+
ProviderConfig: addrs.AbsProviderConfig{
647+
Provider: addrs.NewDefaultProvider("test"),
648+
Module: addrs.RootModule,
649+
},
650+
},
651+
},
652+
testSchemas(),
653+
[]Resource{
654+
{
655+
Address: "test_identity.bar",
656+
Mode: "managed",
657+
Type: "test_identity",
658+
Name: "bar",
659+
Index: nil,
660+
ProviderName: "registry.terraform.io/hashicorp/test",
661+
AttributeValues: AttributeValues{
662+
"name": json.RawMessage(`"bar"`),
663+
"foozles": json.RawMessage(`"sensuzles"`),
664+
"woozles": json.RawMessage(`"confuzles"`),
665+
},
666+
SensitiveValues: json.RawMessage("{\"foozles\":true}"),
667+
IdentityValues: IdentityValues{
668+
"name": json.RawMessage(`"bar"`),
669+
"foozles": json.RawMessage(`"sensuzles"`),
670+
},
671+
IdentitySchemaVersion: ptrOf[uint64](0),
672+
},
673+
},
674+
false,
675+
},
676+
"single resource wrong identity schema": {
677+
map[string]*states.Resource{
678+
"test_identity.bar": {
679+
Addr: addrs.AbsResource{
680+
Resource: addrs.Resource{
681+
Mode: addrs.ManagedResourceMode,
682+
Type: "test_identity",
683+
Name: "bar",
684+
},
685+
},
686+
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
687+
addrs.NoKey: {
688+
Current: &states.ResourceInstanceObjectSrc{
689+
Status: states.ObjectReady,
690+
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles","name":"bar"}`),
691+
IdentitySchemaVersion: 1,
692+
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
693+
},
694+
},
695+
},
696+
ProviderConfig: addrs.AbsProviderConfig{
697+
Provider: addrs.NewDefaultProvider("test"),
698+
Module: addrs.RootModule,
699+
},
700+
},
701+
},
702+
testSchemas(),
703+
nil,
704+
true,
705+
},
706+
"single resource missing identity schema": {
707+
map[string]*states.Resource{
708+
"test_thing.bar": {
709+
Addr: addrs.AbsResource{
710+
Resource: addrs.Resource{
711+
Mode: addrs.ManagedResourceMode,
712+
Type: "test_thing",
713+
Name: "bar",
714+
},
715+
},
716+
Instances: map[addrs.InstanceKey]*states.ResourceInstance{
717+
addrs.NoKey: {
718+
Current: &states.ResourceInstanceObjectSrc{
719+
Status: states.ObjectReady,
720+
AttrsJSON: []byte(`{"woozles":"confuzles","foozles":"sensuzles"}`),
721+
IdentitySchemaVersion: 0,
722+
IdentityJSON: []byte(`{"foozles":"sensuzles","name":"bar"}`),
723+
},
724+
},
725+
},
726+
ProviderConfig: addrs.AbsProviderConfig{
727+
Provider: addrs.NewDefaultProvider("test"),
728+
Module: addrs.RootModule,
729+
},
730+
},
731+
},
732+
testSchemas(),
733+
nil,
734+
true,
735+
},
623736
}
624737

625738
for name, test := range tests {
@@ -867,6 +980,22 @@ func testSchemas() *terraform.Schemas {
867980
},
868981
},
869982
},
983+
"test_identity": {
984+
Body: &configschema.Block{
985+
Attributes: map[string]*configschema.Attribute{
986+
"name": {Type: cty.String, Required: true},
987+
"woozles": {Type: cty.String, Optional: true, Computed: true},
988+
"foozles": {Type: cty.String, Optional: true, Sensitive: true},
989+
},
990+
},
991+
Identity: &configschema.Object{
992+
Attributes: map[string]*configschema.Attribute{
993+
"name": {Type: cty.String, Required: true},
994+
"foozles": {Type: cty.String, Optional: true},
995+
},
996+
Nesting: configschema.NestingSingle,
997+
},
998+
},
870999
},
8711000
},
8721001
},

internal/rpcapi/stacks.go

+34-10
Original file line numberDiff line numberDiff line change
@@ -228,18 +228,42 @@ func stackConfigMetaforProto(cfgNode *stackconfig.ConfigNode, stackAddr stackadd
228228

229229
// Currently Components are the only thing that can be removed
230230
for name, rc := range cfgNode.Stack.Removed {
231-
cProto := &stacks.FindStackConfigurationComponents_Removed{
232-
SourceAddr: rc.FinalSourceAddr.String(),
233-
ComponentAddr: stackaddrs.Config(stackAddr, stackaddrs.Component{Name: rc.FromComponent.Name}).String(),
234-
Destroy: rc.Destroy,
231+
var blocks []*stacks.FindStackConfigurationComponents_Removed_Block
232+
for _, rc := range rc {
233+
cProto := &stacks.FindStackConfigurationComponents_Removed_Block{
234+
SourceAddr: rc.FinalSourceAddr.String(),
235+
ComponentAddr: stackaddrs.Config(stackAddr, stackaddrs.Component{Name: rc.FromComponent.Name}).String(),
236+
Destroy: rc.Destroy,
237+
}
238+
switch {
239+
case rc.ForEach != nil:
240+
cProto.Instances = stacks.FindStackConfigurationComponents_FOR_EACH
241+
default:
242+
cProto.Instances = stacks.FindStackConfigurationComponents_SINGLE
243+
}
244+
blocks = append(blocks, cProto)
235245
}
236-
switch {
237-
case rc.ForEach != nil:
238-
cProto.Instances = stacks.FindStackConfigurationComponents_FOR_EACH
239-
default:
240-
cProto.Instances = stacks.FindStackConfigurationComponents_SINGLE
246+
ret.Removed[name] = &stacks.FindStackConfigurationComponents_Removed{
247+
// in order to ensure as much backwards and forwards compatibility
248+
// as possible, we're going to set the deprecated single fields
249+
// with the first run block
250+
251+
SourceAddr: rc[0].FinalSourceAddr.String(),
252+
Instances: func() stacks.FindStackConfigurationComponents_Instances {
253+
switch {
254+
case rc[0].ForEach != nil:
255+
return stacks.FindStackConfigurationComponents_FOR_EACH
256+
default:
257+
return stacks.FindStackConfigurationComponents_SINGLE
258+
}
259+
}(),
260+
ComponentAddr: stackaddrs.Config(stackAddr, stackaddrs.Component{Name: rc[0].FromComponent.Name}).String(),
261+
Destroy: rc[0].Destroy,
262+
263+
// We return all the values here:
264+
265+
Blocks: blocks,
241266
}
242-
ret.Removed[name] = cProto
243267
}
244268

245269
return ret

0 commit comments

Comments
 (0)