Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store resource identities in state (TF-23255) #36464

Merged
merged 13 commits into from
Mar 11, 2025
11 changes: 5 additions & 6 deletions internal/backend/local/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,19 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema providers.Pro
return resp
}

rSchema, _ := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
if rSchema == nil {
rSchema = &configschema.Block{} // default schema is empty
rSchema := schema.SchemaForResourceType(addrs.ManagedResourceMode, req.TypeName)
if rSchema.Body == nil {
rSchema.Body = &configschema.Block{} // default schema is empty
}
plannedVals := map[string]cty.Value{}
for name, attrS := range rSchema.Attributes {
for name, attrS := range rSchema.Body.Attributes {
val := req.ProposedNewState.GetAttr(name)
if attrS.Computed && val.IsNull() {
val = cty.UnknownVal(attrS.Type)
}
plannedVals[name] = val
}
for name := range rSchema.BlockTypes {
for name := range rSchema.Body.BlockTypes {
// For simplicity's sake we just copy the block attributes over
// verbatim, since this package's mock providers are all relatively
// simple -- we're testing the backend, not esoteric provider features.
Expand Down Expand Up @@ -99,7 +99,6 @@ func TestLocalProvider(t *testing.T, b *Local, name string, schema providers.Pro
}

return p

}

// TestLocalSingleState is a backend implementation that wraps Local
Expand Down
12 changes: 12 additions & 0 deletions internal/builtin/providers/terraform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ func (p *Provider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}

func (p *Provider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return providers.GetResourceIdentitySchemasResponse{
IdentityTypes: map[string]providers.IdentitySchema{
"terraform_data": dataStoreResourceIdentitySchema(),
},
}
}

// ValidateProviderConfig is used to validate the configuration values.
func (p *Provider) ValidateProviderConfig(req providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
// At this moment there is nothing to configure for the terraform provider,
Expand Down Expand Up @@ -150,6 +158,10 @@ func (p *Provider) UpgradeResourceState(req providers.UpgradeResourceStateReques
return upgradeDataStoreResourceState(req)
}

func (p *Provider) UpgradeResourceIdentity(req providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse {
return upgradeDataStoreResourceIdentity(req)
}

// ReadResource refreshes a resource and returns its current state.
func (p *Provider) ReadResource(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return readDataStoreResourceState(req)
Expand Down
22 changes: 22 additions & 0 deletions internal/builtin/providers/terraform/resource_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ func dataStoreResourceSchema() providers.Schema {
"id": {Type: cty.String, Computed: true},
},
},
Identity: dataStoreResourceIdentitySchema().Body,
}
}

func dataStoreResourceIdentitySchema() providers.IdentitySchema {
return providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Description: "The unique identifier for the data store.",
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
}
}

Expand Down Expand Up @@ -55,6 +72,11 @@ func upgradeDataStoreResourceState(req providers.UpgradeResourceStateRequest) (r
return resp
}

func upgradeDataStoreResourceIdentity(providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("The builtin provider does not support provider upgrades since it has not changed the identity schema yet."))
return resp
}

func readDataStoreResourceState(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
resp.NewState = req.PriorState
return resp
Expand Down
8 changes: 4 additions & 4 deletions internal/command/jsonconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,17 +472,17 @@ func marshalResources(resources map[string]*configs.Resource, schemas *terraform
}
}

schema, schemaVer := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
v.Provider,
v.Mode,
v.Type,
)
if schema == nil {
if schema.Body == nil {
return nil, fmt.Errorf("no schema found for %s (in provider %s)", v.Addr().String(), v.Provider)
}
r.SchemaVersion = schemaVer
r.SchemaVersion = uint64(schema.Version)

r.Expressions = marshalExpressions(v.Config, schema)
r.Expressions = marshalExpressions(v.Config, schema.Body)

// Managed is populated only for Mode = addrs.ManagedResourceMode
if v.Managed != nil && len(v.Managed.Provisioners) > 0 {
Expand Down
10 changes: 5 additions & 5 deletions internal/command/jsonplan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,16 +424,16 @@ func marshalResourceChange(rc *plans.ResourceInstanceChangeSrc, schemas *terrafo
r.PreviousAddress = rc.PrevRunAddr.String()
}

schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
rc.ProviderAddr.Provider,
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
if schema == nil {
if schema.Body == nil {
return r, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider)
}

changeV, err := rc.Decode(schema.ImpliedType())
changeV, err := rc.Decode(schema.Body.ImpliedType())
if err != nil {
return r, err
}
Expand All @@ -452,7 +452,7 @@ func marshalResourceChange(rc *plans.ResourceInstanceChangeSrc, schemas *terrafo
return r, err
}
sensitivePaths := rc.BeforeSensitivePaths
sensitivePaths = append(sensitivePaths, schema.SensitivePaths(changeV.Before, nil)...)
sensitivePaths = append(sensitivePaths, schema.Body.SensitivePaths(changeV.Before, nil)...)
bs := jsonstate.SensitiveAsBool(marks.MarkPaths(changeV.Before, marks.Sensitive, sensitivePaths))
beforeSensitive, err = ctyjson.Marshal(bs, bs.Type())
if err != nil {
Expand All @@ -479,7 +479,7 @@ func marshalResourceChange(rc *plans.ResourceInstanceChangeSrc, schemas *terrafo
afterUnknown = unknownAsBool(changeV.After)
}
sensitivePaths := rc.AfterSensitivePaths
sensitivePaths = append(sensitivePaths, schema.SensitivePaths(changeV.After, nil)...)
sensitivePaths = append(sensitivePaths, schema.Body.SensitivePaths(changeV.After, nil)...)
as := jsonstate.SensitiveAsBool(marks.MarkPaths(changeV.After, marks.Sensitive, sensitivePaths))
afterSensitive, err = ctyjson.Marshal(as, as.Type())
if err != nil {
Expand Down
12 changes: 6 additions & 6 deletions internal/command/jsonplan/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,16 +195,16 @@ func marshalPlanResources(changes *plans.ChangesSrc, ris []addrs.AbsResourceInst
)
}

schema, schemaVer := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
r.ProviderAddr.Provider,
r.Addr.Resource.Resource.Mode,
resource.Type,
)
if schema == nil {
if schema.Body == nil {
return nil, fmt.Errorf("no schema found for %s", r.Addr.String())
}
resource.SchemaVersion = schemaVer
changeV, err := r.Decode(schema.ImpliedType())
resource.SchemaVersion = uint64(schema.Version)
changeV, err := r.Decode(schema.Body.ImpliedType())
if err != nil {
return nil, err
}
Expand All @@ -220,10 +220,10 @@ func marshalPlanResources(changes *plans.ChangesSrc, ris []addrs.AbsResourceInst

if changeV.After != cty.NilVal {
if changeV.After.IsWhollyKnown() {
resource.AttributeValues = marshalAttributeValues(changeV.After, schema)
resource.AttributeValues = marshalAttributeValues(changeV.After, schema.Body)
} else {
knowns := omitUnknowns(changeV.After)
resource.AttributeValues = marshalAttributeValues(knowns, schema)
resource.AttributeValues = marshalAttributeValues(knowns, schema.Body)
}
}

Expand Down
16 changes: 8 additions & 8 deletions internal/command/jsonstate/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,24 +379,24 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
)
}

schema, version := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
r.ProviderConfig.Provider,
resAddr.Mode,
resAddr.Type,
)

// It is possible that the only instance is deposed
if ri.Current != nil {
if version != ri.Current.SchemaVersion {
return nil, fmt.Errorf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, resAddr, version)
if schema.Version != int64(ri.Current.SchemaVersion) {
return nil, fmt.Errorf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, resAddr, schema.Version)
}

current.SchemaVersion = ri.Current.SchemaVersion

if schema == nil {
if schema.Body == nil {
return nil, fmt.Errorf("no schema found for %s (in provider %s)", resAddr.String(), r.ProviderConfig.Provider)
}
riObj, err := ri.Current.Decode(schema.ImpliedType())
riObj, err := ri.Current.Decode(schema)
if err != nil {
return nil, err
}
Expand All @@ -407,7 +407,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
if err != nil {
return nil, fmt.Errorf("preparing attribute values for %s: %w", current.Address, err)
}
sensitivePaths = append(sensitivePaths, schema.SensitivePaths(value, nil)...)
sensitivePaths = append(sensitivePaths, schema.Body.SensitivePaths(value, nil)...)
s := SensitiveAsBool(marks.MarkPaths(value, marks.Sensitive, sensitivePaths))
v, err := ctyjson.Marshal(s, s.Type())
if err != nil {
Expand Down Expand Up @@ -448,7 +448,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
Index: current.Index,
}

riObj, err := rios.Decode(schema.ImpliedType())
riObj, err := rios.Decode(schema)
if err != nil {
return nil, err
}
Expand All @@ -459,7 +459,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
if err != nil {
return nil, fmt.Errorf("preparing attribute values for %s: %w", current.Address, err)
}
sensitivePaths = append(sensitivePaths, schema.SensitivePaths(value, nil)...)
sensitivePaths = append(sensitivePaths, schema.Body.SensitivePaths(value, nil)...)
s := SensitiveAsBool(marks.MarkPaths(value, marks.Sensitive, sensitivePaths))
v, err := ctyjson.Marshal(s, s.Type())
if err != nil {
Expand Down
16 changes: 8 additions & 8 deletions internal/command/views/operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ func TestOperation_planNoChanges(t *testing.T) {
Type: "test_resource",
Name: "somewhere",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
addrs.NewDefaultProvider("test"),
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
rc := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: addr,
Expand Down Expand Up @@ -159,12 +159,12 @@ func TestOperation_planNoChanges(t *testing.T) {
Type: "test_resource",
Name: "somewhere",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
addrs.NewDefaultProvider("test"),
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
rc := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: addr,
Expand Down Expand Up @@ -206,12 +206,12 @@ func TestOperation_planNoChanges(t *testing.T) {
Type: "test_resource",
Name: "somewhere",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
addrs.NewDefaultProvider("test"),
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
rc := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: addr,
Expand Down Expand Up @@ -252,12 +252,12 @@ func TestOperation_planNoChanges(t *testing.T) {
Type: "test_resource",
Name: "anywhere",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
addrs.NewDefaultProvider("test"),
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
rc := &plans.ResourceInstanceChange{
Addr: addr,
PrevRunAddr: addrPrev,
Expand Down
14 changes: 14 additions & 0 deletions internal/configs/configschema/internal_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,17 @@ func (a *Attribute) internalValidate(name, prefix string) error {

return err
}

func (o *Object) InternalValidate() error {
var err error

for name, attrS := range o.Attributes {
if attrS == nil {
err = errors.Join(err, fmt.Errorf("%s: attribute schema is nil", name))
continue
}
err = errors.Join(err, attrS.internalValidate(name, ""))
}

return err
}
Loading