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

* Persist resource identity in Terraform state

* make syncdeps

* Move identity schema merging closer to the protocol

* mock GetResourceIdentitySchemas

* Fix identity refresh tests

* Add more tests

* Change grcpwrap upgrade identity

* Review feedback

* Remove unnecessary version conversion

* Check if GetResourceIdentitySchemas RPC call is implemented

* Update function signature docs

* Adapt protocol changes

* Check unimplemented error for identities in GetSchema
This commit is contained in:
Daniel Banck 2025-03-11 20:58:44 +01:00 committed by GitHub
parent ec0ecca1a6
commit b2b42c0fb4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
79 changed files with 2654 additions and 630 deletions

View file

@ -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.
@ -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

View file

@ -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,
@ -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)

View file

@ -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,
},
}
}
@ -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

View file

@ -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 {

View file

@ -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
}
@ -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 {
@ -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 {

View file

@ -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
}
@ -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)
}
}

View file

@ -379,7 +379,7 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
)
}
schema, version := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
r.ProviderConfig.Provider,
resAddr.Mode,
resAddr.Type,
@ -387,16 +387,16 @@ func marshalResources(resources map[string]*states.Resource, module addrs.Module
// 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
}
@ -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 {
@ -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
}
@ -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 {

View file

@ -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,
@ -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,
@ -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,
@ -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,

View file

@ -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
}

View file

@ -327,6 +327,98 @@ func TestBlockInternalValidate(t *testing.T) {
}
}
func TestObjectInternalValidate(t *testing.T) {
tests := map[string]struct {
Object *Object
Errs []string
}{
"empty": {
&Object{},
[]string{},
},
"valid": {
&Object{
Attributes: map[string]*Attribute{
"foo": {
Type: cty.String,
Required: true,
},
"bar": {
Type: cty.String,
Optional: true,
},
},
Nesting: NestingSingle,
},
[]string{},
},
"nil": {
&Object{
Attributes: map[string]*Attribute{
"foo": nil,
},
Nesting: NestingSingle,
},
[]string{"foo: attribute schema is nil"},
},
"attribute with no flags set": {
&Object{
Attributes: map[string]*Attribute{
"foo": {
Type: cty.String,
},
},
Nesting: NestingSingle,
},
[]string{"foo: must set Optional, Required or Computed"},
},
"attribute required and optional": {
&Object{
Attributes: map[string]*Attribute{
"foo": {
Type: cty.String,
Required: true,
Optional: true,
},
},
Nesting: NestingSingle,
},
[]string{"foo: cannot set both Optional and Required"},
},
"attribute with missing type": {
&Object{
Attributes: map[string]*Attribute{
"foo": {
Optional: true,
},
},
Nesting: NestingSingle,
},
[]string{"foo: either Type or NestedType must be defined"},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
errs := joinedErrors(test.Object.InternalValidate())
if got, want := len(errs), len(test.Errs); got != want {
t.Errorf("wrong number of errors %d; want %d", got, want)
for _, err := range errs {
t.Logf("- %s", err.Error())
}
} else {
if len(errs) > 0 {
for i := range errs {
if errs[i].Error() != test.Errs[i] {
t.Errorf("wrong error: got %s, want %s", errs[i].Error(), test.Errs[i])
}
}
}
}
})
}
}
func joinedErrors(err error) []error {
if err == nil {
return nil

View file

@ -5,6 +5,7 @@ package grpcwrap
import (
"context"
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
@ -23,14 +24,16 @@ import (
// implementation.
func Provider(p providers.Interface) tfplugin5.ProviderServer {
return &provider{
provider: p,
schema: p.GetProviderSchema(),
provider: p,
schema: p.GetProviderSchema(),
identitySchemas: p.GetResourceIdentitySchemas(),
}
}
type provider struct {
provider providers.Interface
schema providers.GetProviderSchemaResponse
provider providers.Interface
schema providers.GetProviderSchemaResponse
identitySchemas providers.GetResourceIdentitySchemasResponse
}
func (p *provider) GetMetadata(_ context.Context, req *tfplugin5.GetMetadata_Request) (*tfplugin5.GetMetadata_Response, error) {
@ -66,7 +69,7 @@ func (p *provider) GetSchema(_ context.Context, req *tfplugin5.GetProviderSchema
}
for typ, dat := range p.schema.DataSources {
resp.DataSourceSchemas[typ] = &tfplugin5.Schema{
Version: dat.Version,
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
@ -540,11 +543,45 @@ func (p *provider) CallFunction(_ context.Context, req *tfplugin5.CallFunction_R
}
func (p *provider) GetResourceIdentitySchemas(_ context.Context, req *tfplugin5.GetResourceIdentitySchemas_Request) (*tfplugin5.GetResourceIdentitySchemas_Response, error) {
panic("Not implemented yet")
resp := &tfplugin5.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*tfplugin5.ResourceIdentitySchema{},
Diagnostics: []*tfplugin5.Diagnostic{},
}
for name, schema := range p.identitySchemas.IdentityTypes {
resp.IdentitySchemas[name] = convert.ResourceIdentitySchemaToProto(schema)
}
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.identitySchemas.Diagnostics)
return resp, nil
}
func (p *provider) UpgradeResourceIdentity(_ context.Context, req *tfplugin5.UpgradeResourceIdentity_Request) (*tfplugin5.UpgradeResourceIdentity_Response, error) {
panic("Not implemented yet")
resp := &tfplugin5.UpgradeResourceIdentity_Response{}
resource, ok := p.identitySchemas.IdentityTypes[req.TypeName]
if !ok {
return nil, fmt.Errorf("resource identity schema not found for type %q", req.TypeName)
}
ty := resource.Body.ImpliedType()
upgradeResp := p.provider.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{
TypeName: req.TypeName,
Version: req.Version,
RawIdentityJSON: req.RawIdentity.Json,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics)
if upgradeResp.Diagnostics.HasErrors() {
return resp, nil
}
dv, err := encodeDynamicValue(upgradeResp.UpgradedIdentity, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.UpgradedIdentity = &tfplugin5.ResourceIdentityData{
IdentityData: dv,
}
return resp, nil
}
func (p *provider) Stop(context.Context, *tfplugin5.Stop_Request) (*tfplugin5.Stop_Response, error) {

View file

@ -5,6 +5,7 @@ package grpcwrap
import (
"context"
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
@ -24,14 +25,16 @@ import (
// internal provider implementation.
func Provider6(p providers.Interface) tfplugin6.ProviderServer {
return &provider6{
provider: p,
schema: p.GetProviderSchema(),
provider: p,
schema: p.GetProviderSchema(),
identitySchemas: p.GetResourceIdentitySchemas(),
}
}
type provider6 struct {
provider providers.Interface
schema providers.GetProviderSchemaResponse
provider providers.Interface
schema providers.GetProviderSchemaResponse
identitySchemas providers.GetResourceIdentitySchemasResponse
}
func (p *provider6) GetMetadata(_ context.Context, req *tfplugin6.GetMetadata_Request) (*tfplugin6.GetMetadata_Response, error) {
@ -68,13 +71,13 @@ func (p *provider6) GetProviderSchema(_ context.Context, req *tfplugin6.GetProvi
}
for typ, dat := range p.schema.DataSources {
resp.DataSourceSchemas[typ] = &tfplugin6.Schema{
Version: dat.Version,
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
for typ, dat := range p.schema.EphemeralResourceTypes {
resp.EphemeralResourceSchemas[typ] = &tfplugin6.Schema{
Version: dat.Version,
Version: int64(dat.Version),
Block: convert.ConfigSchemaToProto(dat.Body),
}
}
@ -590,11 +593,48 @@ func (p *provider6) CallFunction(_ context.Context, req *tfplugin6.CallFunction_
}
func (p *provider6) GetResourceIdentitySchemas(_ context.Context, req *tfplugin6.GetResourceIdentitySchemas_Request) (*tfplugin6.GetResourceIdentitySchemas_Response, error) {
panic("Not implemented yet")
resp := &tfplugin6.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*tfplugin6.ResourceIdentitySchema{},
Diagnostics: []*tfplugin6.Diagnostic{},
}
for name, schema := range p.identitySchemas.IdentityTypes {
resp.IdentitySchemas[name] = convert.ResourceIdentitySchemaToProto(schema)
}
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, p.identitySchemas.Diagnostics)
return resp, nil
}
func (p *provider6) UpgradeResourceIdentity(_ context.Context, req *tfplugin6.UpgradeResourceIdentity_Request) (*tfplugin6.UpgradeResourceIdentity_Response, error) {
panic("Not implemented yet")
resp := &tfplugin6.UpgradeResourceIdentity_Response{}
resource, ok := p.identitySchemas.IdentityTypes[req.TypeName]
if !ok {
return nil, fmt.Errorf("resource identity schema not found for type %q", req.TypeName)
}
ty := resource.Body.ImpliedType()
upgradeResp := p.provider.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{
TypeName: req.TypeName,
Version: req.Version,
RawIdentityJSON: req.RawIdentity.Json,
})
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, upgradeResp.Diagnostics)
if upgradeResp.Diagnostics.HasErrors() {
return resp, nil
}
dv, err := encodeDynamicValue6(upgradeResp.UpgradedIdentity, ty)
if err != nil {
resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
return resp, nil
}
resp.UpgradedIdentity = &tfplugin6.ResourceIdentityData{
IdentityData: dv,
}
return resp, nil
}
func (p *provider6) StopProvider(context.Context, *tfplugin6.StopProvider_Request) (*tfplugin6.StopProvider_Response, error) {

View file

@ -205,8 +205,8 @@ func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstanc
return nil
}
resourceTypeSchema, _ := providerSchema.SchemaForResourceAddr(addr.Resource)
if resourceTypeSchema == nil {
resourceTypeSchema := providerSchema.SchemaForResourceAddr(addr.Resource)
if resourceTypeSchema.Body == nil {
return nil
}
@ -234,11 +234,11 @@ func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstanc
// Caller must also update "schema" if necessary.
}
traverseInBlock := func(name string) ([]hcl.Body, []hcl.Expression) {
if attr := schema.Attributes[name]; attr != nil {
if attr := schema.Body.Attributes[name]; attr != nil {
// When we reach a specific attribute we can't traverse any deeper, because attributes are the leaves of the schema.
schema = nil
schema.Body = nil
return traverseAttr(bodies, name)
} else if blockType := schema.BlockTypes[name]; blockType != nil {
} else if blockType := schema.Body.BlockTypes[name]; blockType != nil {
// We need to take a different action here depending on
// the nesting mode of the block type. Some require us
// to traverse in two steps in order to select a specific
@ -248,7 +248,7 @@ func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstanc
case configschema.NestingSingle, configschema.NestingGroup:
// There should be only zero or one blocks of this
// type, so we can traverse in only one step.
schema = &blockType.Block
schema.Body = &blockType.Block
return traverseNestedBlockSingle(bodies, name)
case configschema.NestingMap, configschema.NestingList, configschema.NestingSet:
steppingThrough = blockType
@ -258,14 +258,14 @@ func (a *Analyzer) metaReferencesResourceInstance(moduleAddr addrs.ModuleInstanc
// we add something new in future we'll bail out
// here and conservatively return everything under
// the current traversal point.
schema = nil
schema.Body = nil
return nil, nil
}
}
// We'll get here if the given name isn't in the schema at all. If so,
// there's nothing else to be done here.
schema = nil
schema.Body = nil
return nil, nil
}
Steps:
@ -281,7 +281,7 @@ Steps:
// a specific attribute) and so we'll stop early, assuming that
// any remaining steps are traversals into an attribute expression
// result.
if schema == nil {
if schema.Body == nil {
break
}
@ -299,10 +299,10 @@ Steps:
continue
}
nextStep(traverseNestedBlockMap(bodies, steppingThroughType, step.Name))
schema = &steppingThrough.Block
schema.Body = &steppingThrough.Block
default:
nextStep(traverseInBlock(step.Name))
if schema == nil {
if schema.Body == nil {
// traverseInBlock determined that we've traversed as
// deep as we can with reference to schema, so we'll
// stop here and just process whatever's selected.
@ -320,7 +320,7 @@ Steps:
continue
}
nextStep(traverseNestedBlockMap(bodies, steppingThroughType, keyVal.AsString()))
schema = &steppingThrough.Block
schema.Body = &steppingThrough.Block
case configschema.NestingList:
idxVal, err := convert.Convert(step.Key, cty.Number)
if err != nil { // Invalid traversal, so can't have any refs
@ -334,7 +334,7 @@ Steps:
continue
}
nextStep(traverseNestedBlockList(bodies, steppingThroughType, idx))
schema = &steppingThrough.Block
schema.Body = &steppingThrough.Block
default:
// Note that NestingSet ends up in here because we don't
// actually allow traversing into set-backed block types,
@ -352,7 +352,7 @@ Steps:
continue
}
nextStep(traverseInBlock(nameVal.AsString()))
if schema == nil {
if schema.Body == nil {
// traverseInBlock determined that we've traversed as
// deep as we can with reference to schema, so we'll
// stop here and just process whatever's selected.
@ -392,9 +392,9 @@ Steps:
moreRefs, _ := langrefs.ReferencesInExpr(addrs.ParseRef, expr)
refs = append(refs, moreRefs...)
}
if schema != nil {
if schema.Body != nil {
for _, body := range bodies {
moreRefs, _ := langrefs.ReferencesInBlock(addrs.ParseRef, body, schema)
moreRefs, _ := langrefs.ReferencesInBlock(addrs.ParseRef, body, schema.Body)
refs = append(refs, moreRefs...)
}
}

View file

@ -17,20 +17,14 @@ cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHOb
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go v0.110.7 h1:rJyC7nWRg2jWGZ4wSJ5nY65GTdYJkg0cd/uXb+ACI6o=
cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y=
cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -41,8 +35,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
@ -52,10 +44,6 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/apparentlymart/go-versions v1.0.2 h1:n5Gg9YvSLK8Zzpy743J7abh2jt7z7ammOQ0oTd/5oA4=
github.com/apparentlymart/go-versions v1.0.2/go.mod h1:YF5j7IQtrOAOnsGkniupEA5bfCjzd7i14yu0shZavyM=
github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@ -85,8 +73,6 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
@ -108,8 +94,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -137,28 +121,12 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc=
github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4=
github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-getter v1.7.8 h1:mshVHx1Fto0/MydBekWan5zUipGq7jO0novchgMmSiY=
github.com/hashicorp/go-getter v1.7.8/go.mod h1:2c6CboOEb9jG6YvmC9xdD+tyAFsrUaJPedwXDGr0TM4=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-slug v0.16.3 h1:pe0PMwz2UWN1168QksdW/d7u057itB2gY568iF0E2Ns=
github.com/hashicorp/go-slug v0.16.3/go.mod h1:THWVTAXwJEinbsp4/bBRcmbaO5EYNLTqxbG4tZ3gCYQ=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@ -173,13 +141,9 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -192,10 +156,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@ -213,8 +173,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -229,8 +187,6 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -238,8 +194,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -318,8 +272,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -436,8 +388,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@ -457,8 +407,6 @@ google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz513
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o=
google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -466,8 +414,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -504,12 +450,6 @@ google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -526,8 +466,6 @@ google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -538,8 +476,6 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=

View file

@ -21,6 +21,8 @@ var ignoreUnexported = cmpopts.IgnoreUnexported(
proto.Schema_Block{},
proto.Schema_NestedBlock{},
proto.Schema_Attribute{},
proto.ResourceIdentitySchema{},
proto.ResourceIdentitySchema_IdentityAttribute{},
)
func TestProtoDiagnostics(t *testing.T) {

View file

@ -90,11 +90,19 @@ func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Sch
}
// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
return providers.Schema{
// It takes an optional resource identity schema for resources that support identity.
func ProtoToProviderSchema(s *proto.Schema, id *proto.ResourceIdentitySchema) providers.Schema {
schema := providers.Schema{
Version: s.Version,
Body: ProtoToConfigSchema(s.Block),
}
if id != nil {
schema.IdentityVersion = id.Version
schema.Identity = ProtoToIdentitySchema(id.IdentityAttributes)
}
return schema
}
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
@ -188,3 +196,54 @@ func sortedKeys(m interface{}) []string {
sort.Strings(keys)
return keys
}
func ProtoToIdentitySchema(attributes []*proto.ResourceIdentitySchema_IdentityAttribute) *configschema.Object {
obj := &configschema.Object{
Attributes: make(map[string]*configschema.Attribute),
Nesting: configschema.NestingSingle,
}
for _, a := range attributes {
attr := &configschema.Attribute{
Description: a.Description,
Required: a.RequiredForImport,
Optional: a.OptionalForImport,
}
if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
panic(err)
}
obj.Attributes[a.Name] = attr
}
return obj
}
func ResourceIdentitySchemaToProto(b providers.IdentitySchema) *proto.ResourceIdentitySchema {
attrs := []*proto.ResourceIdentitySchema_IdentityAttribute{}
for _, name := range sortedKeys(b.Body.Attributes) {
a := b.Body.Attributes[name]
attr := &proto.ResourceIdentitySchema_IdentityAttribute{
Name: name,
Description: a.Description,
RequiredForImport: a.Required,
OptionalForImport: a.Optional,
}
ty, err := json.Marshal(a.Type)
if err != nil {
panic(err)
}
attr.Type = ty
attrs = append(attrs, attr)
}
return &proto.ResourceIdentitySchema{
Version: b.Version,
IdentityAttributes: attrs,
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin5"
"github.com/zclconf/go-cty/cty"
)
@ -362,3 +363,90 @@ func TestConvertProtoSchemaBlocks(t *testing.T) {
})
}
}
func TestProtoToResourceIdentitySchema(t *testing.T) {
tests := map[string]struct {
Attributes []*proto.ResourceIdentitySchema_IdentityAttribute
Want *configschema.Object
}{
"simple": {
[]*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "id",
Type: []byte(`"string"`),
RequiredForImport: true,
OptionalForImport: false,
Description: "Something",
},
},
&configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Description: "Something",
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ProtoToIdentitySchema(tc.Attributes)
if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
}
})
}
}
func TestResourceIdentitySchemaToProto(t *testing.T) {
tests := map[string]struct {
Want *proto.ResourceIdentitySchema
Schema providers.IdentitySchema
}{
"attributes": {
&proto.ResourceIdentitySchema{
Version: 1,
IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "optional",
Type: []byte(`"string"`),
OptionalForImport: true,
},
{
Name: "required",
Type: []byte(`"number"`),
RequiredForImport: true,
},
},
},
providers.IdentitySchema{
Version: 1,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"optional": {
Type: cty.String,
Optional: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ResourceIdentitySchemaToProto(tc.Schema)
if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported))
}
})
}
}

View file

@ -16,6 +16,8 @@ import (
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/zclconf/go-cty/cty/msgpack"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/logging"
@ -81,9 +83,6 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
defer p.mu.Unlock()
// check the global cache if we can
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations.
if !p.Addr.IsZero() {
if resp, ok := providers.SchemaCache.Get(p.Addr); ok && resp.ServerCapabilities.GetProviderSchemaOptional {
logger.Trace("GRPCProvider: returning cached schema", p.Addr.String())
@ -129,23 +128,38 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}
resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider)
identResp, err := p.client.GetResourceIdentitySchemas(p.ctx, new(proto.GetResourceIdentitySchemas_Request))
if err != nil {
if status.Code(err) == codes.Unimplemented {
// We don't treat this as an error if older providers don't implement this method,
// so we create an empty map for identity schemas
identResp = &proto.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*proto.ResourceIdentitySchema{},
}
} else {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
}
resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider, nil)
if protoResp.ProviderMeta == nil {
logger.Debug("No provider meta schema returned")
} else {
resp.ProviderMeta = convert.ProtoToProviderSchema(protoResp.ProviderMeta)
resp.ProviderMeta = convert.ProtoToProviderSchema(protoResp.ProviderMeta, nil)
}
for name, res := range protoResp.ResourceSchemas {
resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res)
id := identResp.IdentitySchemas[name] // We're fine if the id is not found
resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res, id)
}
for name, data := range protoResp.DataSourceSchemas {
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
resp.DataSources[name] = convert.ProtoToProviderSchema(data, nil)
}
for name, ephem := range protoResp.EphemeralResourceSchemas {
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem)
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem, nil)
}
if decls, err := convert.FunctionDeclsFromProto(protoResp.Functions); err == nil {
@ -162,9 +176,6 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
}
// set the global cache if we can
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations.
if !p.Addr.IsZero() {
providers.SchemaCache.Set(p.Addr, resp)
}
@ -176,6 +187,38 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}
func (p *GRPCProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
var resp providers.GetResourceIdentitySchemasResponse
resp.IdentityTypes = make(map[string]providers.IdentitySchema)
protoResp, err := p.client.GetResourceIdentitySchemas(p.ctx, new(proto.GetResourceIdentitySchemas_Request))
if err != nil {
if status.Code(err) == codes.Unimplemented {
// We expect no error here if older providers don't implement this method
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if resp.Diagnostics.HasErrors() {
return resp
}
for name, res := range protoResp.IdentitySchemas {
resp.IdentityTypes[name] = providers.IdentitySchema{
Version: res.Version,
Body: convert.ProtoToIdentitySchema(res.IdentityAttributes),
}
}
return resp
}
func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
logger.Trace("GRPCProvider: ValidateProviderConfig")
@ -333,6 +376,52 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ
return resp
}
func (p *GRPCProvider) UpgradeResourceIdentity(r providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
logger.Trace("GRPCProvider: UpgradeResourceIdentity")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
resSchema, ok := schema.ResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown resource identity type %q", r.TypeName))
return resp
}
protoReq := &proto.UpgradeResourceIdentity_Request{
TypeName: r.TypeName,
Version: int64(r.Version),
RawIdentity: &proto.RawState{
Json: r.RawIdentityJSON,
},
}
protoResp, err := p.client.UpgradeResourceIdentity(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
ty := resSchema.Identity.ImpliedType()
resp.UpgradedIdentity = cty.NullVal(ty)
if protoResp.UpgradedIdentity == nil {
return resp
}
identity, err := decodeDynamicValue(protoResp.UpgradedIdentity.IdentityData, ty)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.UpgradedIdentity = identity
return resp
}
func (p *GRPCProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
logger.Trace("GRPCProvider: ConfigureProvider")

View file

@ -16,6 +16,8 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
"go.uber.org/mock/gomock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
mockproto "github.com/hashicorp/terraform/internal/plugin/mock_proto"
@ -35,6 +37,13 @@ func mockProviderClient(t *testing.T) *mockproto.MockProviderClient {
gomock.Any(),
).Return(providerProtoSchema(), nil)
// GetResourceIdentitySchemas is called as part of GetSchema
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
return client
}
@ -114,6 +123,23 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
}
}
func providerResourceIdentitySchemas() *proto.GetResourceIdentitySchemas_Response {
return &proto.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*proto.ResourceIdentitySchema{
"resource": {
Version: 1,
IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "attr",
Type: []byte(`"string"`),
RequiredForImport: true,
},
},
},
},
}
}
func TestGRPCProvider_GetSchema(t *testing.T) {
p := &GRPCProvider{
client: mockProviderClient(t),
@ -196,6 +222,94 @@ func TestGRPCProvider_GetSchema_ResponseErrorDiagnostic(t *testing.T) {
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_GetSchema_IdentityError(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerProtoSchema(), nil)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, fmt.Errorf("test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetProviderSchema()
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_GetSchema_IdentityUnimplemented(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerProtoSchema(), nil)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetProviderSchema()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_GetResourceIdentitySchemas(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
p := &GRPCProvider{
client: client,
}
resp := p.GetResourceIdentitySchemas()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_GetResourceIdentitySchemas_Unimplemented(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetResourceIdentitySchemas()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_PrepareProviderConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
@ -312,6 +426,84 @@ func TestGRPCProvider_UpgradeResourceStateJSON(t *testing.T) {
}
}
func TestGRPCProvider_UpgradeResourceIdentity(t *testing.T) {
testCases := []struct {
desc string
response *proto.UpgradeResourceIdentity_Response
expectError bool
expectedValue cty.Value
}{
{
"successful upgrade",
&proto.UpgradeResourceIdentity_Response{
UpgradedIdentity: &proto.ResourceIdentityData{
IdentityData: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
},
},
false,
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar")}),
},
{
"response with error diagnostic",
&proto.UpgradeResourceIdentity_Response{
Diagnostics: []*proto.Diagnostic{
{
Severity: proto.Diagnostic_ERROR,
Summary: "test error",
Detail: "test error detail",
},
},
},
true,
cty.NilVal,
},
{
"schema mismatch",
&proto.UpgradeResourceIdentity_Response{
UpgradedIdentity: &proto.ResourceIdentityData{
IdentityData: &proto.DynamicValue{
Json: []byte(`{"attr_new":"bar"}`),
},
},
},
true,
cty.NilVal,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().UpgradeResourceIdentity(
gomock.Any(),
gomock.Any(),
).Return(tc.response, nil)
resp := p.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{
TypeName: "resource",
Version: 0,
RawIdentityJSON: []byte(`{"old_attr":"bar"}`),
})
if tc.expectError {
checkDiagsHasError(t, resp.Diagnostics)
} else {
checkDiags(t, resp.Diagnostics)
if !cmp.Equal(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty))
}
}
})
}
}
func TestGRPCProvider_Configure(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{

View file

@ -20,6 +20,8 @@ var ignoreUnexported = cmpopts.IgnoreUnexported(
proto.Schema_NestedBlock{},
proto.Schema_Attribute{},
proto.Schema_Object{},
proto.ResourceIdentitySchema{},
proto.ResourceIdentitySchema_IdentityAttribute{},
)
func TestProtoDiagnostics(t *testing.T) {

View file

@ -96,11 +96,44 @@ func protoSchemaNestedBlock(name string, b *configschema.NestedBlock) *proto.Sch
}
// ProtoToProviderSchema takes a proto.Schema and converts it to a providers.Schema.
func ProtoToProviderSchema(s *proto.Schema) providers.Schema {
return providers.Schema{
// It takes an optional resource identity schema for resources that support identity.
func ProtoToProviderSchema(s *proto.Schema, id *proto.ResourceIdentitySchema) providers.Schema {
schema := providers.Schema{
Version: s.Version,
Body: ProtoToConfigSchema(s.Block),
}
if id != nil {
schema.IdentityVersion = id.Version
schema.Identity = ProtoToIdentitySchema(id.IdentityAttributes)
}
return schema
}
func ProtoToIdentitySchema(attributes []*proto.ResourceIdentitySchema_IdentityAttribute) *configschema.Object {
obj := &configschema.Object{
Attributes: make(map[string]*configschema.Attribute),
Nesting: configschema.NestingSingle,
}
for _, a := range attributes {
attr := &configschema.Attribute{
Description: a.Description,
Required: a.RequiredForImport,
Optional: a.OptionalForImport,
}
if a.Type != nil {
if err := json.Unmarshal(a.Type, &attr.Type); err != nil {
panic(err)
}
}
obj.Attributes[a.Name] = attr
}
return obj
}
// ProtoToConfigSchema takes the GetSchcema_Block from a grpc response and converts it
@ -301,3 +334,32 @@ func configschemaObjectToProto(b *configschema.Object) *proto.Schema_Object {
Nesting: nesting,
}
}
func ResourceIdentitySchemaToProto(schema providers.IdentitySchema) *proto.ResourceIdentitySchema {
identityAttributes := []*proto.ResourceIdentitySchema_IdentityAttribute{}
for _, name := range sortedKeys(schema.Body.Attributes) {
a := schema.Body.Attributes[name]
attr := &proto.ResourceIdentitySchema_IdentityAttribute{
Name: name,
Description: a.Description,
RequiredForImport: a.Required,
OptionalForImport: a.Optional,
}
if a.Type != cty.NilType {
ty, err := json.Marshal(a.Type)
if err != nil {
panic(err)
}
attr.Type = ty
}
identityAttributes = append(identityAttributes, attr)
}
return &proto.ResourceIdentitySchema{
Version: schema.Version,
IdentityAttributes: identityAttributes,
}
}

View file

@ -9,6 +9,7 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
"github.com/zclconf/go-cty/cty"
)
@ -624,3 +625,90 @@ func TestConvertProtoSchemaBlocks(t *testing.T) {
})
}
}
func TestProtoToResourceIdentitySchema(t *testing.T) {
tests := map[string]struct {
Attributes []*proto.ResourceIdentitySchema_IdentityAttribute
Want *configschema.Object
}{
"simple": {
[]*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "id",
Type: []byte(`"string"`),
RequiredForImport: true,
OptionalForImport: false,
Description: "Something",
},
},
&configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Description: "Something",
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ProtoToIdentitySchema(tc.Attributes)
if !cmp.Equal(converted, tc.Want, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, valueComparer, equateEmpty))
}
})
}
}
func TestResourceIdentitySchemaToProto(t *testing.T) {
tests := map[string]struct {
Want *proto.ResourceIdentitySchema
Schema providers.IdentitySchema
}{
"attributes": {
&proto.ResourceIdentitySchema{
Version: 1,
IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "optional",
Type: []byte(`"string"`),
OptionalForImport: true,
},
{
Name: "required",
Type: []byte(`"number"`),
RequiredForImport: true,
},
},
},
providers.IdentitySchema{
Version: 1,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"optional": {
Type: cty.String,
Optional: true,
},
"required": {
Type: cty.Number,
Required: true,
},
},
},
},
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
converted := ResourceIdentitySchemaToProto(tc.Schema)
if !cmp.Equal(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported) {
t.Fatal(cmp.Diff(converted, tc.Want, typeComparer, equateEmpty, ignoreUnexported))
}
})
}
}

View file

@ -16,6 +16,8 @@ import (
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/zclconf/go-cty/cty/msgpack"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/logging"
@ -80,9 +82,6 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
defer p.mu.Unlock()
// check the global cache if we can
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations.
if !p.Addr.IsZero() {
if resp, ok := providers.SchemaCache.Get(p.Addr); ok && resp.ServerCapabilities.GetProviderSchemaOptional {
logger.Trace("GRPCProvider.v6: returning cached schema", p.Addr.String())
@ -129,23 +128,38 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}
resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider)
identResp, err := p.client.GetResourceIdentitySchemas(p.ctx, new(proto6.GetResourceIdentitySchemas_Request))
if err != nil {
if status.Code(err) == codes.Unimplemented {
// We don't treat this as an error if older providers don't implement this method,
// so we create an empty map for identity schemas
identResp = &proto6.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*proto6.ResourceIdentitySchema{},
}
} else {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
}
resp.Provider = convert.ProtoToProviderSchema(protoResp.Provider, nil)
if protoResp.ProviderMeta == nil {
logger.Debug("No provider meta schema returned")
} else {
resp.ProviderMeta = convert.ProtoToProviderSchema(protoResp.ProviderMeta)
resp.ProviderMeta = convert.ProtoToProviderSchema(protoResp.ProviderMeta, nil)
}
for name, res := range protoResp.ResourceSchemas {
resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res)
id := identResp.IdentitySchemas[name] // We're fine if the id is not found
resp.ResourceTypes[name] = convert.ProtoToProviderSchema(res, id)
}
for name, data := range protoResp.DataSourceSchemas {
resp.DataSources[name] = convert.ProtoToProviderSchema(data)
resp.DataSources[name] = convert.ProtoToProviderSchema(data, nil)
}
for name, ephem := range protoResp.EphemeralResourceSchemas {
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem)
resp.EphemeralResourceTypes[name] = convert.ProtoToProviderSchema(ephem, nil)
}
if decls, err := convert.FunctionDeclsFromProto(protoResp.Functions); err == nil {
@ -162,9 +176,6 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
}
// set the global cache if we can
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations.
if !p.Addr.IsZero() {
providers.SchemaCache.Set(p.Addr, resp)
}
@ -176,6 +187,40 @@ func (p *GRPCProvider) GetProviderSchema() providers.GetProviderSchemaResponse {
return resp
}
func (p *GRPCProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
logger.Trace("GRPCProvider.v6: GetResourceIdentitySchemas")
var resp providers.GetResourceIdentitySchemasResponse
resp.IdentityTypes = make(map[string]providers.IdentitySchema)
protoResp, err := p.client.GetResourceIdentitySchemas(p.ctx, new(proto6.GetResourceIdentitySchemas_Request))
if err != nil {
if status.Code(err) == codes.Unimplemented {
// We expect no error here if older providers don't implement this method
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
if resp.Diagnostics.HasErrors() {
return resp
}
for name, res := range protoResp.IdentitySchemas {
resp.IdentityTypes[name] = providers.IdentitySchema{
Version: res.Version,
Body: convert.ProtoToIdentitySchema(res.IdentityAttributes),
}
}
return resp
}
func (p *GRPCProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
logger.Trace("GRPCProvider.v6: ValidateProviderConfig")
@ -326,6 +371,52 @@ func (p *GRPCProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ
return resp
}
func (p *GRPCProvider) UpgradeResourceIdentity(r providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
logger.Trace("GRPCProvider.v6: UpgradeResourceIdentity")
schema := p.GetProviderSchema()
if schema.Diagnostics.HasErrors() {
resp.Diagnostics = schema.Diagnostics
return resp
}
resSchema, ok := schema.ResourceTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unknown resource type %q", r.TypeName))
return resp
}
protoReq := &proto6.UpgradeResourceIdentity_Request{
TypeName: r.TypeName,
Version: int64(r.Version),
RawIdentity: &proto6.RawState{
Json: r.RawIdentityJSON,
},
}
protoResp, err := p.client.UpgradeResourceIdentity(p.ctx, protoReq)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(grpcErr(err))
return resp
}
resp.Diagnostics = resp.Diagnostics.Append(convert.ProtoToDiagnostics(protoResp.Diagnostics))
ty := resSchema.Identity.ImpliedType()
resp.UpgradedIdentity = cty.NullVal(ty)
if protoResp.UpgradedIdentity == nil {
return resp
}
identity, err := decodeDynamicValue(protoResp.UpgradedIdentity.IdentityData, ty)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.UpgradedIdentity = identity
return resp
}
func (p *GRPCProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
logger.Trace("GRPCProvider.v6: ConfigureProvider")

View file

@ -17,6 +17,8 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
"go.uber.org/mock/gomock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
mockproto "github.com/hashicorp/terraform/internal/plugin6/mock_proto"
@ -42,6 +44,13 @@ func mockProviderClient(t *testing.T) *mockproto.MockProviderClient {
gomock.Any(),
).Return(providerProtoSchema(), nil)
// GetResourceIdentitySchemas is called as part of GetSchema
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
return client
}
@ -121,6 +130,23 @@ func providerProtoSchema() *proto.GetProviderSchema_Response {
}
}
func providerResourceIdentitySchemas() *proto.GetResourceIdentitySchemas_Response {
return &proto.GetResourceIdentitySchemas_Response{
IdentitySchemas: map[string]*proto.ResourceIdentitySchema{
"resource": {
Version: 1,
IdentityAttributes: []*proto.ResourceIdentitySchema_IdentityAttribute{
{
Name: "attr",
Type: []byte(`"string"`),
RequiredForImport: true,
},
},
},
},
}
}
func TestGRPCProvider_GetSchema(t *testing.T) {
p := &GRPCProvider{
client: mockProviderClient(t),
@ -203,6 +229,94 @@ func TestGRPCProvider_GetSchema_ResponseErrorDiagnostic(t *testing.T) {
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_GetSchema_IdentityError(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetProviderSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerProtoSchema(), nil)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, fmt.Errorf("test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetProviderSchema()
checkDiagsHasError(t, resp.Diagnostics)
}
func TestGRPCProvider_GetSchema_IdentityUnimplemented(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetProviderSchema(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerProtoSchema(), nil)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetProviderSchema()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_GetResourceIdentitySchemas(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(providerResourceIdentitySchemas(), nil)
p := &GRPCProvider{
client: client,
}
resp := p.GetResourceIdentitySchemas()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_GetResourceIdentitySchemas_Unimplemented(t *testing.T) {
ctrl := gomock.NewController(t)
client := mockproto.NewMockProviderClient(ctrl)
client.EXPECT().GetResourceIdentitySchemas(
gomock.Any(),
gomock.Any(),
gomock.Any(),
).Return(&proto.GetResourceIdentitySchemas_Response{}, status.Error(codes.Unimplemented, "test error"))
p := &GRPCProvider{
client: client,
}
resp := p.GetResourceIdentitySchemas()
checkDiags(t, resp.Diagnostics)
}
func TestGRPCProvider_PrepareProviderConfig(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
@ -319,6 +433,84 @@ func TestGRPCProvider_UpgradeResourceStateJSON(t *testing.T) {
}
}
func TestGRPCProvider_UpgradeResourceIdentity(t *testing.T) {
testCases := []struct {
desc string
response *proto.UpgradeResourceIdentity_Response
expectError bool
expectedValue cty.Value
}{
{
"successful upgrade",
&proto.UpgradeResourceIdentity_Response{
UpgradedIdentity: &proto.ResourceIdentityData{
IdentityData: &proto.DynamicValue{
Json: []byte(`{"attr":"bar"}`),
},
},
},
false,
cty.ObjectVal(map[string]cty.Value{"attr": cty.StringVal("bar")}),
},
{
"response with error diagnostic",
&proto.UpgradeResourceIdentity_Response{
Diagnostics: []*proto.Diagnostic{
{
Severity: proto.Diagnostic_ERROR,
Summary: "test error",
Detail: "test error detail",
},
},
},
true,
cty.NilVal,
},
{
"schema mismatch",
&proto.UpgradeResourceIdentity_Response{
UpgradedIdentity: &proto.ResourceIdentityData{
IdentityData: &proto.DynamicValue{
Json: []byte(`{"attr_new":"bar"}`),
},
},
},
true,
cty.NilVal,
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{
client: client,
}
client.EXPECT().UpgradeResourceIdentity(
gomock.Any(),
gomock.Any(),
).Return(tc.response, nil)
resp := p.UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest{
TypeName: "resource",
Version: 0,
RawIdentityJSON: []byte(`{"old_attr":"bar"}`),
})
if tc.expectError {
checkDiagsHasError(t, resp.Diagnostics)
} else {
checkDiags(t, resp.Diagnostics)
if !cmp.Equal(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty) {
t.Fatal(cmp.Diff(tc.expectedValue, resp.UpgradedIdentity, typeComparer, valueComparer, equateEmpty))
}
}
})
}
}
func TestGRPCProvider_Configure(t *testing.T) {
client := mockProviderClient(t)
p := &GRPCProvider{

View file

@ -56,7 +56,7 @@ func Provider() providers.Interface {
GetProviderSchemaOptional: true,
},
Functions: map[string]providers.FunctionDecl{
"noop": providers.FunctionDecl{
"noop": {
Parameters: []providers.FunctionParam{
{
Name: "noop",
@ -80,6 +80,25 @@ func (s simple) GetProviderSchema() providers.GetProviderSchemaResponse {
return s.schema
}
func (s simple) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return providers.GetResourceIdentitySchemasResponse{
IdentityTypes: map[string]providers.IdentitySchema{
"simple_resource": {
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
},
}
}
func (s simple) ValidateProviderConfig(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
return resp
}
@ -100,6 +119,15 @@ func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest)
return resp
}
func (p simple) UpgradeResourceIdentity(req providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
schema := p.GetResourceIdentitySchemas().IdentityTypes[req.TypeName].Body
ty := schema.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawIdentityJSON, ty)
resp.Diagnostics = resp.Diagnostics.Append(err)
resp.UpgradedIdentity = val
return resp
}
func (s simple) ConfigureProvider(providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
return resp
}

View file

@ -57,6 +57,25 @@ func (s simple) GetProviderSchema() providers.GetProviderSchemaResponse {
return s.schema
}
func (s simple) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return providers.GetResourceIdentitySchemasResponse{
IdentityTypes: map[string]providers.IdentitySchema{
"simple_resource": {
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
},
}
}
func (s simple) ValidateProviderConfig(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
return resp
}
@ -77,6 +96,15 @@ func (p simple) UpgradeResourceState(req providers.UpgradeResourceStateRequest)
return resp
}
func (p simple) UpgradeResourceIdentity(req providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
schema := p.GetResourceIdentitySchemas().IdentityTypes[req.TypeName].Body
ty := schema.ImpliedType()
val, err := ctyjson.Unmarshal(req.RawIdentityJSON, ty)
resp.Diagnostics = resp.Diagnostics.Append(err)
resp.UpgradedIdentity = val
return resp
}
func (s simple) ConfigureProvider(providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
return resp
}

View file

@ -37,7 +37,8 @@ type Mock struct {
Provider Interface
Data *configs.MockData
schema *GetProviderSchemaResponse
schema *GetProviderSchemaResponse
identitySchema *GetResourceIdentitySchemasResponse
}
func (m *Mock) GetProviderSchema() GetProviderSchemaResponse {
@ -66,6 +67,16 @@ func (m *Mock) GetProviderSchema() GetProviderSchemaResponse {
return *m.schema
}
func (m *Mock) GetResourceIdentitySchemas() GetResourceIdentitySchemasResponse {
if m.identitySchema == nil {
// Cache the schema, it's not changing.
schema := m.Provider.GetResourceIdentitySchemas()
m.identitySchema = &schema
}
return *m.identitySchema
}
func (m *Mock) ValidateProviderConfig(request ValidateProviderConfigRequest) (response ValidateProviderConfigResponse) {
// The config for the mocked providers is consistent, and validated when we
// parse the HCL directly. So we'll just make no change here.
@ -131,6 +142,40 @@ func (m *Mock) UpgradeResourceState(request UpgradeResourceStateRequest) (respon
return response
}
func (m *Mock) UpgradeResourceIdentity(request UpgradeResourceIdentityRequest) (response UpgradeResourceIdentityResponse) {
// We can't do this from a mocked provider, so we just return whatever identity
// is in the request back unchanged.
schema := m.GetProviderSchema()
response.Diagnostics = response.Diagnostics.Append(schema.Diagnostics)
if schema.Diagnostics.HasErrors() {
// We couldn't retrieve the schema for some reason, so the mock
// provider can't really function.
return response
}
resource, exists := schema.ResourceTypes[request.TypeName]
if !exists {
// This means something has gone wrong much earlier, we should have
// failed a validation somewhere if a resource type doesn't exist.
panic(fmt.Errorf("failed to retrieve identity schema for resource %s", request.TypeName))
}
schemaType := resource.Identity.ImpliedType()
value, err := ctyjson.Unmarshal(request.RawIdentityJSON, schemaType)
if err != nil {
// Generally, we shouldn't get an error here. The mocked providers are
// only used in tests, and we can't use different versions of providers
// within/between tests so the types should always match up. As such,
// we're not gonna return a super detailed error here.
response.Diagnostics = response.Diagnostics.Append(err)
return response
}
response.UpgradedIdentity = value
return response
}
func (m *Mock) ConfigureProvider(request ConfigureProviderRequest) (response ConfigureProviderResponse) {
// Do nothing here, we don't have anything to configure within the mocked
// providers. We don't want to call the original providers from here as

View file

@ -7,7 +7,6 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -17,6 +16,11 @@ type Interface interface {
// GetSchema returns the complete schema for the provider.
GetProviderSchema() GetProviderSchemaResponse
// GetResourceIdentitySchemas returns the identity schemas for all managed resources
// for the provider. Usually you don't need to call this method directly as GetProviderSchema
// will merge the identity schemas into the provider schema.
GetResourceIdentitySchemas() GetResourceIdentitySchemasResponse
// ValidateProviderConfig allows the provider to validate the configuration.
// The ValidateProviderConfigResponse.PreparedConfig field is unused. The
// final configuration is not stored in the state, and any modifications
@ -41,6 +45,12 @@ type Interface interface {
// result is used for any further processing.
UpgradeResourceState(UpgradeResourceStateRequest) UpgradeResourceStateResponse
// UpgradeResourceIdentity is called when the state loader encounters an
// instance identity whose schema version is less than the one reported by
// the currently-used version of the corresponding provider, and the upgraded
// result is used for any further processing.
UpgradeResourceIdentity(UpgradeResourceIdentityRequest) UpgradeResourceIdentityResponse
// Configure configures and initialized the provider.
ConfigureProvider(ConfigureProviderRequest) ConfigureProviderResponse
@ -98,7 +108,7 @@ type Interface interface {
// should only be used when handling a value for that method. The handling of
// of schemas in any other context should always use ProviderSchema, so that
// the in-memory representation can be more easily changed separately from the
// RCP protocol.
// RPC protocol.
type GetProviderSchemaResponse struct {
// Provider is the schema for the provider itself.
Provider Schema
@ -127,6 +137,25 @@ type GetProviderSchemaResponse struct {
ServerCapabilities ServerCapabilities
}
// GetResourceIdentitySchemasResponse is the return type for GetResourceIdentitySchemas,
// and should only be used when handling a value for that method. The handling of
// of schemas in any other context should always use ResourceIdentitySchemas, so that
// the in-memory representation can be more easily changed separately from the
// RPC protocol.
type GetResourceIdentitySchemasResponse struct {
// IdentityTypes map the resource type name to that type's identity schema.
IdentityTypes map[string]IdentitySchema
// Diagnostics contains any warnings or errors from the method call.
Diagnostics tfdiags.Diagnostics
}
type IdentitySchema struct {
Version int64
Body *configschema.Object
}
// Schema pairs a provider or resource schema with that schema's version.
// This is used to be able to upgrade the schema in UpgradeResourceState.
//
@ -136,6 +165,9 @@ type GetProviderSchemaResponse struct {
type Schema struct {
Version int64
Body *configschema.Block
IdentityVersion int64
Identity *configschema.Object
}
// ServerCapabilities allows providers to communicate extra information
@ -254,6 +286,26 @@ type UpgradeResourceStateResponse struct {
Diagnostics tfdiags.Diagnostics
}
type UpgradeResourceIdentityRequest struct {
// TypeName is the name of the resource type being upgraded
TypeName string
// Version is version of the schema that created the current identity.
Version int64
// RawIdentityJSON contains the identity that needs to be
// upgraded to match the current schema version.
RawIdentityJSON []byte
}
type UpgradeResourceIdentityResponse struct {
// UpgradedState is the newly upgraded resource identity.
UpgradedIdentity cty.Value
// Diagnostics contains any warnings or errors from the method call.
Diagnostics tfdiags.Diagnostics
}
type ConfigureProviderRequest struct {
// Terraform version is the version string from the running instance of
// terraform. Providers can use TerraformVersion to verify compatibility,
@ -344,6 +396,10 @@ type ReadResourceResponse struct {
// Deferred if present signals that the provider was not able to fully
// complete this operation and a susequent run is required.
Deferred *Deferred
// Identity is the object-typed value representing the identity of the remote
// object within Terraform.
Identity cty.Value
}
type PlanResourceChangeRequest struct {
@ -484,7 +540,7 @@ type ImportResourceStateResponse struct {
}
// ImportedResource represents an object being imported into Terraform with the
// help of a provider. An ImportedObject is a RemoteObject that has been read
// help of a provider. An ImportedResource is a RemoteObject that has been read
// by the provider's import handler but hasn't yet been committed to state.
type ImportedResource struct {
// TypeName is the name of the resource type associated with the
@ -542,24 +598,6 @@ type MoveResourceStateResponse struct {
Diagnostics tfdiags.Diagnostics
}
// AsInstanceObject converts the receiving ImportedObject into a
// ResourceInstanceObject that has status ObjectReady.
//
// The returned object does not know its own resource type, so the caller must
// retain the ResourceType value from the source object if this information is
// needed.
//
// The returned object also has no dependency addresses, but the caller may
// freely modify the direct fields of the returned object without affecting
// the receiver.
func (ir ImportedResource) AsInstanceObject() *states.ResourceInstanceObject {
return &states.ResourceInstanceObject{
Status: states.ObjectReady,
Value: ir.State,
Private: ir.Private,
}
}
type ReadDataSourceRequest struct {
// TypeName is the name of the data source type to Read.
TypeName string

View file

@ -12,13 +12,6 @@ import (
// SchemaCache is a global cache of Schemas.
// This will be accessed by both core and the provider clients to ensure that
// large schemas are stored in a single location.
//
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations. This would be better
// as a per-terraform.Context cache instead, or to have callers preload
// the schemas for the providers they intend to use and pass them in
// to terraform.NewContext so we don't need to load them at runtime.
var SchemaCache = &schemaCache{
m: make(map[addrs.Provider]ProviderSchema),
}

View file

@ -5,7 +5,6 @@ package providers
import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
)
// ProviderSchema is an overall container for all of the schemas for all
@ -14,26 +13,25 @@ import (
type ProviderSchema = GetProviderSchemaResponse
// SchemaForResourceType attempts to find a schema for the given mode and type.
// Returns nil if no such schema is available.
func (ss ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema *configschema.Block, version uint64) {
// Returns an empty schema if none is available.
func (ss ProviderSchema) SchemaForResourceType(mode addrs.ResourceMode, typeName string) (schema Schema) {
switch mode {
case addrs.ManagedResourceMode:
res := ss.ResourceTypes[typeName]
return res.Body, uint64(res.Version)
return ss.ResourceTypes[typeName]
case addrs.DataResourceMode:
// Data resources don't have schema versions right now, since state is discarded for each refresh
return ss.DataSources[typeName].Body, 0
return ss.DataSources[typeName]
case addrs.EphemeralResourceMode:
// Ephemeral resources don't have schema versions because their objects never outlive a single phase
return ss.EphemeralResourceTypes[typeName].Body, 0
return ss.EphemeralResourceTypes[typeName]
default:
// Shouldn't happen, because the above cases are comprehensive.
return nil, 0
return Schema{}
}
}
// SchemaForResourceAddr attempts to find a schema for the mode and type from
// the given resource address. Returns nil if no such schema is available.
func (ss ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema *configschema.Block, version uint64) {
// the given resource address. Returns an empty schema if none is available.
func (ss ProviderSchema) SchemaForResourceAddr(addr addrs.Resource) (schema Schema) {
return ss.SchemaForResourceType(addr.Mode, addr.Type)
}
type ResourceIdentitySchemas = GetResourceIdentitySchemasResponse

View file

@ -32,6 +32,9 @@ type MockProvider struct {
GetProviderSchemaCalled bool
GetProviderSchemaResponse *providers.GetProviderSchemaResponse
GetResourceIdentitySchemasCalled bool
GetResourceIdentitySchemasResponse *providers.GetResourceIdentitySchemasResponse
ValidateProviderConfigCalled bool
ValidateProviderConfigResponse *providers.ValidateProviderConfigResponse
ValidateProviderConfigRequest providers.ValidateProviderConfigRequest
@ -55,6 +58,12 @@ type MockProvider struct {
UpgradeResourceStateRequest providers.UpgradeResourceStateRequest
UpgradeResourceStateFn func(providers.UpgradeResourceStateRequest) providers.UpgradeResourceStateResponse
UpgradeResourceIdentityCalled bool
UpgradeResourceIdentityTypeName string
UpgradeResourceIdentityResponse *providers.UpgradeResourceIdentityResponse
UpgradeResourceIdentityRequest providers.UpgradeResourceIdentityRequest
UpgradeResourceIdentityFn func(providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse
ConfigureProviderCalled bool
ConfigureProviderResponse *providers.ConfigureProviderResponse
ConfigureProviderRequest providers.ConfigureProviderRequest
@ -142,6 +151,24 @@ func (p *MockProvider) getProviderSchema() providers.GetProviderSchemaResponse {
}
}
func (p *MockProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
p.Lock()
defer p.Unlock()
p.GetResourceIdentitySchemasCalled = true
return p.getResourceIdentitySchemas()
}
func (p *MockProvider) getResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
if p.GetResourceIdentitySchemasResponse != nil {
return *p.GetResourceIdentitySchemasResponse
}
return providers.GetResourceIdentitySchemasResponse{
IdentityTypes: map[string]providers.IdentitySchema{},
}
}
func (p *MockProvider) ValidateProviderConfig(r providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
p.Lock()
defer p.Unlock()
@ -306,6 +333,44 @@ func (p *MockProvider) UpgradeResourceState(r providers.UpgradeResourceStateRequ
return resp
}
func (p *MockProvider) UpgradeResourceIdentity(r providers.UpgradeResourceIdentityRequest) (resp providers.UpgradeResourceIdentityResponse) {
p.Lock()
defer p.Unlock()
if !p.ConfigureProviderCalled {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("Configure not called before UpgradeResourceIdentity %q", r.TypeName))
return resp
}
p.UpgradeResourceIdentityCalled = true
p.UpgradeResourceIdentityRequest = r
if p.UpgradeResourceIdentityFn != nil {
return p.UpgradeResourceIdentityFn(r)
}
if p.UpgradeResourceIdentityResponse != nil {
return *p.UpgradeResourceIdentityResponse
}
identitySchema, ok := p.getResourceIdentitySchemas().IdentityTypes[r.TypeName]
if !ok {
resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("no schema found for %q", r.TypeName))
return resp
}
identityType := identitySchema.Body.ImpliedType()
v, err := ctyjson.Unmarshal(r.RawIdentityJSON, identityType)
if err != nil {
resp.Diagnostics = resp.Diagnostics.Append(err)
return resp
}
resp.UpgradedIdentity = v
return resp
}
func (p *MockProvider) ConfigureProvider(r providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
p.Lock()
defer p.Unlock()

View file

@ -10,7 +10,6 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/ephemeral"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
@ -100,21 +99,19 @@ func (m *crossTypeMover) prepareCrossTypeMove(stmt *MoveStatement, source, targe
})
return nil, diags
}
targetResourceSchema, targetResourceSchemaVersion := targetSchema.SchemaForResourceAddr(target.Resource)
schema := targetSchema.SchemaForResourceAddr(target.Resource)
return &crossTypeMove{
targetProvider: targetProvider,
targetProviderAddr: *targetProviderAddr,
targetResourceSchema: targetResourceSchema,
targetResourceSchemaVersion: targetResourceSchemaVersion,
sourceProviderAddr: sourceProviderAddr,
targetProvider: targetProvider,
targetProviderAddr: *targetProviderAddr,
targetResourceSchema: schema,
sourceProviderAddr: sourceProviderAddr,
}, diags
}
type crossTypeMove struct {
targetProvider providers.Interface
targetProviderAddr addrs.AbsProviderConfig
targetResourceSchema *configschema.Block
targetResourceSchemaVersion uint64
targetProvider providers.Interface
targetProviderAddr addrs.AbsProviderConfig
targetResourceSchema providers.Schema
sourceProviderAddr addrs.AbsProviderConfig
}
@ -162,7 +159,7 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe
)
},
resp.TargetState,
move.targetResourceSchema,
move.targetResourceSchema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -199,7 +196,8 @@ func (move *crossTypeMove) applyCrossTypeMove(stmt *MoveStatement, source, targe
CreateBeforeDestroy: src.CreateBeforeDestroy,
}
data, err := newValue.Encode(move.targetResourceSchema.ImpliedType(), move.targetResourceSchemaVersion)
// TODO: We need to handle identity data in move scenarios.
data, err := newValue.Encode(move.targetResourceSchema)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,

View file

@ -29,6 +29,10 @@ func (provider *mockProvider) GetProviderSchema() providers.GetProviderSchemaRes
}
}
func (provider *mockProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
panic("not implemented in mock")
}
func (provider *mockProvider) ValidateProviderConfig(providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
panic("not implemented in mock")
}
@ -45,6 +49,10 @@ func (provider *mockProvider) UpgradeResourceState(providers.UpgradeResourceStat
panic("not implemented in mock")
}
func (provider *mockProvider) UpgradeResourceIdentity(providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse {
panic("not implemented in mock")
}
func (provider *mockProvider) ConfigureProvider(providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
panic("not implemented in mock")
}

View file

@ -100,10 +100,6 @@ func (cp *Plugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema
// We skip this if we have preloaded schemas because that suggests that
// our caller is not Terraform CLI and therefore it's probably inappropriate
// to assume that provider schemas are unique process-wide.
//
// FIXME: A global cache is inappropriate when Terraform Core is being
// used in a non-Terraform-CLI mode where we shouldn't assume that all
// calls share the same provider implementations.
schemas, ok := providers.SchemaCache.Get(addr)
if ok {
log.Printf("[TRACE] terraform.contextPlugins: Schema for provider %q is in the global cache", addr)
@ -141,6 +137,30 @@ func (cp *Plugins) ProviderSchema(addr addrs.Provider) (providers.ProviderSchema
if r.Version < 0 {
return resp, fmt.Errorf("provider %s has invalid negative schema version for managed resource type %q, which is a bug in the provider", addr, t)
}
// Validate resource identity schema if the resource has one
if r.Identity != nil {
if err := r.Identity.InternalValidate(); err != nil {
return resp, fmt.Errorf("provider %s has invalid identity schema for managed resource type %q, which is a bug in the provider: %q", addr, t, err)
}
if r.IdentityVersion < 0 {
return resp, fmt.Errorf("provider %s has invalid negative identity schema version for managed resource type %q, which is a bug in the provider", addr, t)
}
for attrName, attrTy := range r.Identity.ImpliedType().AttributeTypes() {
if attrTy.MapElementType() != nil {
return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, attribute %q is a map, which is not allowed in identity schemas", addr, t, attrName)
}
if attrTy.SetElementType() != nil {
return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, attribute %q is a set, which is not allowed in identity schemas", addr, t, attrName)
}
if attrTy.IsObjectType() {
return resp, fmt.Errorf("provider %s has invalid schema for managed resource type %q, attribute %q is an object, which is not allowed in identity schemas", addr, t, attrName)
}
}
}
}
for t, d := range resp.DataSources {
@ -208,20 +228,15 @@ func (cp *Plugins) ProviderConfigSchema(providerAddr addrs.Provider) (*configsch
// for the resource type of the given resource mode in that provider.
//
// ResourceTypeSchema will return an error if the provider schema lookup
// fails, but will return nil if the provider schema lookup succeeds but then
// the provider doesn't have a resource of the requested type.
//
// Managed resource types have versioned schemas, so the second return value
// is the current schema version number for the requested resource. The version
// is irrelevant for other resource modes.
func (cp *Plugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (*configschema.Block, uint64, error) {
// fails, but will return an empty schema if the provider schema lookup
// succeeds but then the provider doesn't have a resource of the requested type.
func (cp *Plugins) ResourceTypeSchema(providerAddr addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (providers.Schema, error) {
providerSchema, err := cp.ProviderSchema(providerAddr)
if err != nil {
return nil, 0, err
return providers.Schema{}, err
}
schema, version := providerSchema.SchemaForResourceType(resourceMode, resourceType)
return schema, version, nil
return providerSchema.SchemaForResourceType(resourceMode, resourceType), nil
}
// ProvisionerSchema uses a temporary instance of the provisioner with the

View file

@ -32,18 +32,18 @@ func (ss *Schemas) ProviderConfig(provider addrs.Provider) *configschema.Block {
}
// ResourceTypeConfig returns the schema for the configuration of a given
// resource type belonging to a given provider type, or nil of no such
// schema is available.
// resource type belonging to a given provider type, or an empty schema
// if no such schema is available.
//
// In many cases the provider type is inferrable from the resource type name,
// but this is not always true because users can override the provider for
// a resource using the "provider" meta-argument. Therefore it's important to
// always pass the correct provider name, even though it many cases it feels
// redundant.
func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) (block *configschema.Block, schemaVersion uint64) {
func (ss *Schemas) ResourceTypeConfig(provider addrs.Provider, resourceMode addrs.ResourceMode, resourceType string) providers.Schema {
ps := ss.ProviderSchema(provider)
if ps.ResourceTypes == nil {
return nil, 0
return providers.Schema{}
}
return ps.SchemaForResourceType(resourceMode, resourceType)
}

View file

@ -12,9 +12,9 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -36,7 +36,7 @@ type PlanProducer interface {
RequiredComponents(ctx context.Context) collections.Set[stackaddrs.AbsComponent]
// ResourceSchema returns the schema for a resource type from a provider.
ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, resourceType string) (*configschema.Block, error)
ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, resourceType string) (providers.Schema, error)
}
func FromPlan(ctx context.Context, config *configs.Config, plan *plans.Plan, refreshPlan *plans.Plan, action plans.Action, producer PlanProducer) ([]PlannedChange, tfdiags.Diagnostics) {

View file

@ -15,7 +15,6 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/planfile"
@ -364,9 +363,9 @@ type PlannedChangeResourceInstancePlanned struct {
// Schema MUST be the same schema that was used to encode the dynamic
// values inside ChangeSrc and PriorStateSrc.
//
// Can be nil if and only if ChangeSrc and PriorStateSrc are both nil
// Can be empty if and only if ChangeSrc and PriorStateSrc are both nil
// themselves.
Schema *configschema.Block
Schema providers.Schema
}
var _ PlannedChange = (*PlannedChangeResourceInstancePlanned)(nil)

View file

@ -128,7 +128,7 @@ func TestApplyDestroy(t *testing.T) {
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil, // We should be removing this from the state file.
Schema: nil,
Schema: providers.Schema{},
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),
@ -185,7 +185,7 @@ func TestApplyDestroy(t *testing.T) {
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: nil,
Schema: providers.Schema{},
NewStateSrc: nil, // deleted
},
@ -193,7 +193,7 @@ func TestApplyDestroy(t *testing.T) {
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: nil,
Schema: providers.Schema{},
NewStateSrc: nil,
},
&stackstate.AppliedChangeInputVariable{
@ -264,7 +264,7 @@ func TestApplyDestroy(t *testing.T) {
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.missing"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: nil,
Schema: providers.Schema{},
NewStateSrc: nil, // deleted
},
&stackstate.AppliedChangeResourceInstanceObject{
@ -306,13 +306,13 @@ func TestApplyDestroy(t *testing.T) {
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.data.testing_data_source.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: nil,
Schema: providers.Schema{},
NewStateSrc: nil, // deleted
},
&stackstate.AppliedChangeResourceInstanceObject{
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
Schema: nil,
Schema: providers.Schema{},
NewStateSrc: nil, // deleted
},
&stackstate.AppliedChangeInputVariable{

View file

@ -442,7 +442,7 @@ func TestApply(t *testing.T) {
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil,
Schema: nil,
Schema: providers.Schema{},
},
},
},
@ -625,7 +625,7 @@ func TestApply(t *testing.T) {
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self[\"removed\"].testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil,
Schema: nil,
Schema: providers.Schema{},
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("input"),
@ -735,7 +735,7 @@ func TestApply(t *testing.T) {
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("stack.a.component.self.testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil,
Schema: nil,
Schema: providers.Schema{},
},
},
},
@ -846,7 +846,7 @@ After applying this plan, Terraform will no longer manage these objects. You wil
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.data"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil,
Schema: nil,
Schema: providers.Schema{},
},
},
},
@ -3969,7 +3969,7 @@ func TestApplyManuallyRemovedResource(t *testing.T) {
ResourceInstanceObjectAddr: mustAbsResourceInstanceObject("component.self.testing_resource.missing"),
ProviderConfigAddr: mustDefaultRootProvider("testing"),
NewStateSrc: nil, // We should be removing this from the state file.
Schema: nil,
Schema: providers.Schema{},
},
&stackstate.AppliedChangeInputVariable{
Addr: mustStackInputVariable("id"),

View file

@ -14,12 +14,12 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
"github.com/hashicorp/terraform/internal/stacks/stackstate"
@ -734,7 +734,7 @@ func (c *ComponentInstance) CheckApply(ctx context.Context) ([]stackstate.Applie
}
// ResourceSchema implements stackplan.PlanProducer.
func (c *ComponentInstance) ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, typ string) (*configschema.Block, error) {
func (c *ComponentInstance) ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, typ string) (providers.Schema, error) {
// This should not be able to fail with an error because we should
// be retrieving the same schema that was already used to encode
// the object we're working with. The error handling here is for
@ -743,11 +743,11 @@ func (c *ComponentInstance) ResourceSchema(ctx context.Context, providerTypeAddr
providerType := c.main.ProviderType(ctx, providerTypeAddr)
providerSchema, err := providerType.Schema(ctx)
if err != nil {
return nil, err
return providers.Schema{}, err
}
ret, _ := providerSchema.SchemaForResourceType(mode, typ)
if ret == nil {
return nil, fmt.Errorf("schema does not include %v %q", mode, typ)
ret := providerSchema.SchemaForResourceType(mode, typ)
if ret.Body == nil {
return providers.Schema{}, fmt.Errorf("schema does not include %v %q", mode, typ)
}
return ret, nil
}

View file

@ -14,11 +14,11 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/promising"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
"github.com/hashicorp/terraform/internal/stacks/stackstate"
@ -323,7 +323,7 @@ func (r *RemovedInstance) RequiredComponents(ctx context.Context) collections.Se
}
// ResourceSchema implements stackplan.PlanProducer.
func (r *RemovedInstance) ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, typ string) (*configschema.Block, error) {
func (r *RemovedInstance) ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, typ string) (providers.Schema, error) {
// This should not be able to fail with an error because we should
// be retrieving the same schema that was already used to encode
// the object we're working with. The error handling here is for
@ -332,11 +332,11 @@ func (r *RemovedInstance) ResourceSchema(ctx context.Context, providerTypeAddr a
providerType := r.main.ProviderType(ctx, providerTypeAddr)
providerSchema, err := providerType.Schema(ctx)
if err != nil {
return nil, err
return providers.Schema{}, err
}
ret, _ := providerSchema.SchemaForResourceType(mode, typ)
if ret == nil {
return nil, fmt.Errorf("schema does not include %v %q", mode, typ)
ret := providerSchema.SchemaForResourceType(mode, typ)
if ret.Body == nil {
return providers.Schema{}, fmt.Errorf("schema does not include %v %q", mode, typ)
}
return ret, nil
}

View file

@ -60,6 +60,10 @@ func (p *erroredProvider) GetProviderSchema() providers.GetProviderSchemaRespons
return providers.GetProviderSchemaResponse{}
}
func (p *erroredProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return providers.GetResourceIdentitySchemasResponse{}
}
// ImportResourceState implements providers.Interface.
func (p *erroredProvider) ImportResourceState(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
var diags tfdiags.Diagnostics
@ -180,6 +184,22 @@ func (p *erroredProvider) UpgradeResourceState(req providers.UpgradeResourceStat
}
}
func (p *erroredProvider) UpgradeResourceIdentity(req providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse {
// Ideally we'd just skip this altogether and echo back what the caller
// provided, but the request is in a different serialization format than
// the response and so only the real provider can deal with this one.
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Provider configuration is invalid",
"Cannot decode the prior state for this resource instance because its provider configuration is invalid.",
nil, // nil attribute path means the overall configuration block
))
return providers.UpgradeResourceIdentityResponse{
Diagnostics: diags,
}
}
// ValidateDataResourceConfig implements providers.Interface.
func (p *erroredProvider) ValidateDataResourceConfig(req providers.ValidateDataResourceConfigRequest) providers.ValidateDataResourceConfigResponse {
// We'll just optimistically assume the configuration is valid, so that

View file

@ -33,6 +33,10 @@ func (o *offlineProvider) GetProviderSchema() providers.GetProviderSchemaRespons
return o.unconfiguredClient.GetProviderSchema()
}
func (o *offlineProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return o.unconfiguredClient.GetResourceIdentitySchemas()
}
func (o *offlineProvider) ValidateProviderConfig(request providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
@ -99,6 +103,19 @@ func (o *offlineProvider) UpgradeResourceState(request providers.UpgradeResource
}
}
func (o *offlineProvider) UpgradeResourceIdentity(request providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,
"Called UpgradeResourceIdentity on an unconfigured provider",
"Cannot upgrade the state of this resource because this provider is not configured. This is a bug in Terraform - please report it.",
nil, // nil attribute path means the overall configuration block
))
return providers.UpgradeResourceIdentityResponse{
Diagnostics: diags,
}
}
func (o *offlineProvider) ConfigureProvider(request providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.AttributeValue(

View file

@ -39,6 +39,10 @@ func (u *unknownProvider) GetProviderSchema() providers.GetProviderSchemaRespons
return u.unconfiguredClient.GetProviderSchema()
}
func (u *unknownProvider) GetResourceIdentitySchemas() providers.GetResourceIdentitySchemasResponse {
return u.unconfiguredClient.GetResourceIdentitySchemas()
}
func (u *unknownProvider) ValidateProviderConfig(request providers.ValidateProviderConfigRequest) providers.ValidateProviderConfigResponse {
// This is offline functionality, so we can hand it off to the unconfigured
// client.
@ -70,6 +74,12 @@ func (u *unknownProvider) UpgradeResourceState(request providers.UpgradeResource
return u.unconfiguredClient.UpgradeResourceState(request)
}
func (u *unknownProvider) UpgradeResourceIdentity(request providers.UpgradeResourceIdentityRequest) providers.UpgradeResourceIdentityResponse {
// This is offline functionality, so we can hand it off to the unconfigured
// client.
return u.unconfiguredClient.UpgradeResourceIdentity(request)
}
func (u *unknownProvider) ConfigureProvider(request providers.ConfigureProviderRequest) providers.ConfigureProviderResponse {
// This shouldn't be called, we don't configure an unknown provider within
// stacks and Terraform Core shouldn't call this method.

View file

@ -1081,12 +1081,24 @@ func TestPlanWithSingleResource(t *testing.T) {
// type from the real terraform.io/builtin/terraform provider
// maintained elsewhere in this codebase. If that schema changes
// in future then this should change to match it.
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"input": {Type: cty.DynamicPseudoType, Optional: true},
"output": {Type: cty.DynamicPseudoType, Computed: true},
"triggers_replace": {Type: cty.DynamicPseudoType, Optional: true},
"id": {Type: cty.String, Computed: true},
Schema: providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"input": {Type: cty.DynamicPseudoType, Optional: true},
"output": {Type: cty.DynamicPseudoType, Computed: true},
"triggers_replace": {Type: cty.DynamicPseudoType, Optional: true},
"id": {Type: cty.String, Computed: true},
},
},
Identity: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Description: "The unique identifier for the data store.",
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
},
@ -1843,7 +1855,7 @@ func TestPlanWithSensitivePropagation(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.StringVal("secret"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: []cty.Path{
cty.GetAttrPath("value"),
},
@ -2006,7 +2018,7 @@ func TestPlanWithSensitivePropagationNested(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.StringVal("secret"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: []cty.Path{
cty.GetAttrPath("value"),
},
@ -2324,7 +2336,7 @@ func TestPlanWithCheckableObjects(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("test"),
"value": cty.StringVal("bar"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
},
},
@ -2458,7 +2470,7 @@ func TestPlanWithDeferredResource(t *testing.T) {
"id": cty.StringVal("62594ae3"),
"value": cty.NullVal(cty.String),
"deferred": cty.BoolVal(true),
}), stacks_testing_provider.DeferredResourceSchema),
}), stacks_testing_provider.DeferredResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -2623,7 +2635,7 @@ func TestPlanWithDeferredComponentForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -2704,7 +2716,7 @@ func TestPlanWithDeferredComponentForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -2865,7 +2877,7 @@ func TestPlanWithDeferredComponentReferences(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -2950,7 +2962,7 @@ func TestPlanWithDeferredComponentReferences(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.StringVal("known"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
},
ProviderAddr: addrs.AbsProviderConfig{
Module: addrs.RootModule,
@ -3116,7 +3128,7 @@ func TestPlanWithDeferredEmbeddedStackForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -3266,7 +3278,7 @@ func TestPlanWithDeferredEmbeddedStackAndComponentForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.UnknownVal(cty.String),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
AfterSensitivePaths: nil,
},
},
@ -3465,7 +3477,7 @@ func TestPlanWithDeferredProviderForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.StringVal("primary"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
},
},
Schema: stacks_testing_provider.TestingResourceSchema,
@ -3537,7 +3549,7 @@ func TestPlanWithDeferredProviderForEach(t *testing.T) {
After: mustPlanDynamicValueSchema(cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
"value": cty.StringVal("secondary"),
}), stacks_testing_provider.TestingResourceSchema),
}), stacks_testing_provider.TestingResourceSchema.Body),
},
},
Schema: stacks_testing_provider.TestingResourceSchema,

View file

@ -19,42 +19,52 @@ import (
)
var (
TestingResourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
TestingResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
},
},
}
DeferredResourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"deferred": {Type: cty.Bool, Required: true},
DeferredResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"deferred": {Type: cty.Bool, Required: true},
},
},
}
FailedResourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"fail_plan": {Type: cty.Bool, Optional: true, Computed: true},
"fail_apply": {Type: cty.Bool, Optional: true, Computed: true},
FailedResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"fail_plan": {Type: cty.Bool, Optional: true, Computed: true},
"fail_apply": {Type: cty.Bool, Optional: true, Computed: true},
},
},
}
BlockedResourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"required_resources": {Type: cty.Set(cty.String), Optional: true},
BlockedResourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Optional: true, Computed: true},
"value": {Type: cty.String, Optional: true},
"required_resources": {Type: cty.Set(cty.String), Optional: true},
},
},
}
TestingDataSourceSchema = &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Required: true},
"value": {Type: cty.String, Computed: true},
TestingDataSourceSchema = providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Required: true},
"value": {Type: cty.String, Computed: true},
},
},
}
)
@ -127,21 +137,21 @@ func NewProviderWithData(t *testing.T, store *ResourceStore) *MockProvider {
},
ResourceTypes: map[string]providers.Schema{
"testing_resource": {
Body: TestingResourceSchema,
Body: TestingResourceSchema.Body,
},
"testing_deferred_resource": {
Body: DeferredResourceSchema,
Body: DeferredResourceSchema.Body,
},
"testing_failed_resource": {
Body: FailedResourceSchema,
Body: FailedResourceSchema.Body,
},
"testing_blocked_resource": {
Body: BlockedResourceSchema,
Body: BlockedResourceSchema.Body,
},
},
DataSources: map[string]providers.Schema{
"testing_data_source": {
Body: TestingDataSourceSchema,
Body: TestingDataSourceSchema.Body,
},
},
Functions: map[string]providers.FunctionDecl{

View file

@ -56,7 +56,7 @@ func (t *testingResource) Read(request providers.ReadResourceRequest, store *Res
var exists bool
response.NewState, exists = store.Get(id)
if !exists {
response.NewState = cty.NullVal(TestingResourceSchema.ImpliedType())
response.NewState = cty.NullVal(TestingResourceSchema.Body.ImpliedType())
}
return
}
@ -110,7 +110,7 @@ func (d *deferredResource) Read(request providers.ReadResourceRequest, store *Re
var exists bool
response.NewState, exists = store.Get(id)
if !exists {
response.NewState = cty.NullVal(DeferredResourceSchema.ImpliedType())
response.NewState = cty.NullVal(DeferredResourceSchema.Body.ImpliedType())
}
return
}
@ -173,7 +173,7 @@ func (f *failedResource) Read(request providers.ReadResourceRequest, store *Reso
var exists bool
response.NewState, exists = store.Get(id)
if !exists {
response.NewState = cty.NullVal(FailedResourceSchema.ImpliedType())
response.NewState = cty.NullVal(FailedResourceSchema.Body.ImpliedType())
}
return
}
@ -253,7 +253,7 @@ func (b *blockedResource) Read(request providers.ReadResourceRequest, store *Res
var exists bool
response.NewState, exists = store.Get(id)
if !exists {
response.NewState = cty.NullVal(DeferredResourceSchema.ImpliedType())
response.NewState = cty.NullVal(DeferredResourceSchema.Body.ImpliedType())
}
return
}

View file

@ -12,7 +12,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/collections"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackstate/statekeys"
@ -59,9 +59,9 @@ type AppliedChangeResourceInstanceObject struct {
PreviousResourceInstanceObjectAddr *stackaddrs.AbsResourceInstanceObject
// Schema MUST be the same schema that was used to encode the dynamic
// values inside NewStateSrc. This can be left as nil if NewStateSrc
// values inside NewStateSrc. This can be left as empty if NewStateSrc
// is nil, which represents that the object has been deleted.
Schema *configschema.Block
Schema providers.Schema
}
var _ AppliedChange = (*AppliedChangeResourceInstanceObject)(nil)
@ -147,8 +147,7 @@ func (ac *AppliedChangeResourceInstanceObject) protosForObject() ([]*stacks.Appl
// exclusively uses MessagePack encoding for dynamic
// values, and so we will need to use the ac.Schema to
// transcode the data.
ty := ac.Schema.ImpliedType()
obj, err := objSrc.Decode(ty)
obj, err := objSrc.Decode(ac.Schema)
if err != nil {
// It would be _very_ strange to get here because we should just
// be reversing the same encoding operation done earlier to
@ -156,7 +155,7 @@ func (ac *AppliedChangeResourceInstanceObject) protosForObject() ([]*stacks.Appl
return nil, nil, fmt.Errorf("cannot decode new state for %s in preparation for saving it: %w", addr, err)
}
protoValue, err := stacks.ToDynamicValue(obj.Value, ty)
protoValue, err := stacks.ToDynamicValue(obj.Value, ac.Schema.Body.ImpliedType())
if err != nil {
return nil, nil, fmt.Errorf("cannot encode new state for %s in preparation for saving it: %w", addr, err)
}

View file

@ -17,6 +17,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/plans/planproto"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/rpcapi/terraform1/stacks"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/tfstackdata1"
@ -52,15 +53,17 @@ func TestAppliedChangeAsProto(t *testing.T) {
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
},
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
"secret": {
Type: cty.String,
Sensitive: true,
Schema: providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
"secret": {
Type: cty.String,
Sensitive: true,
},
},
},
},
@ -160,15 +163,17 @@ func TestAppliedChangeAsProto(t *testing.T) {
Module: addrs.RootModule,
Provider: addrs.MustParseProviderSourceString("example.com/thingers/thingy"),
},
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
"secret": {
Type: cty.String,
Sensitive: true,
Schema: providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
"secret": {
Type: cty.String,
Sensitive: true,
},
},
},
},

View file

@ -10,9 +10,9 @@ import (
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
"github.com/hashicorp/terraform/internal/stacks/stackplan"
"github.com/hashicorp/terraform/internal/states"
@ -25,7 +25,7 @@ type StateProducer interface {
Addr() stackaddrs.AbsComponentInstance
// ResourceSchema returns the schema for a resource type from a provider.
ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, resourceType string) (*configschema.Block, error)
ResourceSchema(ctx context.Context, providerTypeAddr addrs.Provider, mode addrs.ResourceMode, resourceType string) (providers.Schema, error)
}
func FromState(ctx context.Context, state *states.State, plan *stackplan.Component, applyTimeInputs cty.Value, affectedResources addrs.Set[addrs.AbsResourceInstanceObject], producer StateProducer) ([]AppliedChange, tfdiags.Diagnostics) {
@ -37,7 +37,7 @@ func FromState(ctx context.Context, state *states.State, plan *stackplan.Compone
for _, rioAddr := range affectedResources {
os := state.ResourceInstanceObjectSrc(rioAddr)
var providerConfigAddr addrs.AbsProviderConfig
var schema *configschema.Block
var schema providers.Schema
if os != nil {
rAddr := rioAddr.ResourceInstance.ContainingResource()
rs := state.Resource(rAddr)

View file

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/lang/format"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/providers"
)
// ResourceInstanceObject is the local representation of a specific remote
@ -27,6 +28,10 @@ type ResourceInstanceObject struct {
// Terraform.
Value cty.Value
// Identity is the object-typed value representing the identity of the remote
// object within Terraform.
Identity cty.Value
// Private is an opaque value set by the provider when this object was
// last created or updated. Terraform Core does not use this value in
// any way and it is not exposed anywhere in the user interface, so
@ -52,6 +57,24 @@ type ResourceInstanceObject struct {
CreateBeforeDestroy bool
}
// NewResourceInstanceObjectFromIR converts the receiving
// ImportedResource into a ResourceInstanceObject that has status ObjectReady.
//
// The returned object does not know its own resource type, so the caller must
// retain the ResourceType value from the source object if this information is
// needed.
//
// The returned object also has no dependency addresses, but the caller may
// freely modify the direct fields of the returned object without affecting
// the receiver.
func NewResourceInstanceObjectFromIR(ir providers.ImportedResource) *ResourceInstanceObject {
return &ResourceInstanceObject{
Status: ObjectReady,
Value: ir.State,
Private: ir.Private,
}
}
// ObjectStatus represents the status of a RemoteObject.
type ObjectStatus rune
@ -80,21 +103,20 @@ const (
ObjectPlanned ObjectStatus = 'P'
)
// Encode marshals the value within the receiver to produce a
// Encode marshals values within the receiver to produce a
// ResourceInstanceObjectSrc ready to be written to a state file.
//
// The given type must be the implied type of the resource type schema, and
// the given value must conform to it. It is important to pass the schema
// type and not the object's own type so that dynamically-typed attributes
// will be stored correctly. The caller must also provide the version number
// of the schema that the given type was derived from, which will be recorded
// in the source object so it can be used to detect when schema migration is
// required on read.
// The schema must contain the resource type body, and the given value must
// conform its implied type. The schema must also contain the version number
// of the schema, which will be recorded in the source object so it can be
// used to detect when schema migration is required on read.
// The schema may also contain an resource identity schema and version number,
// which will be used to encode the resource identity.
//
// The returned object may share internal references with the receiver and
// so the caller must not mutate the receiver any further once once this
// method is called.
func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*ResourceInstanceObjectSrc, error) {
func (o *ResourceInstanceObject) Encode(schema providers.Schema) (*ResourceInstanceObjectSrc, error) {
// If it contains marks, remove these marks before traversing the
// structure with UnknownAsNull, and save the PathValueMarks
// so we can save them in state.
@ -114,11 +136,19 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res
// and raise an error about that.
val = cty.UnknownAsNull(val)
src, err := ctyjson.Marshal(val, ty)
src, err := ctyjson.Marshal(val, schema.Body.ImpliedType())
if err != nil {
return nil, err
}
var idJSON []byte
if !o.Identity.IsNull() && schema.Identity != nil {
idJSON, err = ctyjson.Marshal(o.Identity, schema.Identity.ImpliedType())
if err != nil {
return nil, err
}
}
// Dependencies are collected and merged in an unordered format (using map
// keys as a set), then later changed to a slice (in random ordering) to be
// stored in state as an array. To avoid pointless thrashing of state in
@ -132,15 +162,18 @@ func (o *ResourceInstanceObject) Encode(ty cty.Type, schemaVersion uint64) (*Res
sort.Slice(dependencies, func(i, j int) bool { return dependencies[i].String() < dependencies[j].String() })
return &ResourceInstanceObjectSrc{
SchemaVersion: schemaVersion,
AttrsJSON: src,
AttrSensitivePaths: sensitivePaths,
Private: o.Private,
Status: o.Status,
Dependencies: dependencies,
CreateBeforeDestroy: o.CreateBeforeDestroy,
SchemaVersion: uint64(schema.Version),
AttrsJSON: src,
AttrSensitivePaths: sensitivePaths,
Private: o.Private,
Status: o.Status,
Dependencies: dependencies,
CreateBeforeDestroy: o.CreateBeforeDestroy,
IdentityJSON: idJSON,
IdentitySchemaVersion: uint64(schema.IdentityVersion),
// The cached value must have all its marks since it bypasses decoding.
decodeValueCache: o.Value,
decodeValueCache: o.Value,
decodeIdentityCache: o.Identity,
}, nil
}

View file

@ -4,12 +4,15 @@
package states
import (
"fmt"
"github.com/zclconf/go-cty/cty"
ctyjson "github.com/zclconf/go-cty/cty/json"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/providers"
)
// ResourceInstanceObjectSrc is a not-fully-decoded version of
@ -40,6 +43,10 @@ type ResourceInstanceObjectSrc struct {
// schema version should be recorded in the SchemaVersion field.
AttrsJSON []byte
IdentitySchemaVersion uint64
IdentityJSON []byte
// AttrsFlat is a legacy form of attributes used in older state file
// formats, and in the new state format for objects that haven't yet been
// upgraded. This attribute is mutually exclusive with Attrs: for any
@ -66,21 +73,27 @@ type ResourceInstanceObjectSrc struct {
// decodeValueCache stored the decoded value for repeated decodings.
decodeValueCache cty.Value
// decodeIdentityCache stored the decoded identity for repeated decodings.
decodeIdentityCache cty.Value
}
// Decode unmarshals the raw representation of the object attributes. Pass the
// implied type of the corresponding resource type schema for correct operation.
// schema of the corresponding resource type for correct operation.
//
// Before calling Decode, the caller must check that the SchemaVersion field
// exactly equals the version number of the schema whose implied type is being
// passed, or else the result is undefined.
//
// If the object has an identity, the schema must also contain a resource
// identity schema for the identity to be decoded.
//
// The returned object may share internal references with the receiver and
// so the caller must not mutate the receiver any further once once this
// method is called.
func (os *ResourceInstanceObjectSrc) Decode(ty cty.Type) (*ResourceInstanceObject, error) {
func (os *ResourceInstanceObjectSrc) Decode(schema providers.Schema) (*ResourceInstanceObject, error) {
var val cty.Value
var err error
attrsTy := schema.Body.ImpliedType()
switch {
case os.decodeValueCache != cty.NilVal:
@ -88,21 +101,32 @@ func (os *ResourceInstanceObjectSrc) Decode(ty cty.Type) (*ResourceInstanceObjec
case os.AttrsFlat != nil:
// Legacy mode. We'll do our best to unpick this from the flatmap.
val, err = hcl2shim.HCL2ValueFromFlatmap(os.AttrsFlat, ty)
val, err = hcl2shim.HCL2ValueFromFlatmap(os.AttrsFlat, attrsTy)
if err != nil {
return nil, err
}
default:
val, err = ctyjson.Unmarshal(os.AttrsJSON, ty)
val, err = ctyjson.Unmarshal(os.AttrsJSON, attrsTy)
val = marks.MarkPaths(val, marks.Sensitive, os.AttrSensitivePaths)
if err != nil {
return nil, err
}
}
var identity cty.Value
if os.decodeIdentityCache != cty.NilVal {
identity = os.decodeIdentityCache
} else if os.IdentityJSON != nil {
identity, err = ctyjson.Unmarshal(os.IdentityJSON, schema.Identity.ImpliedType())
if err != nil {
return nil, fmt.Errorf("failed to decode identity schema: %s. This is most likely a bug in the Provider, providers must not change the identity schema without updating the identity schema version", err.Error())
}
}
return &ResourceInstanceObject{
Value: val,
Identity: identity,
Status: os.Status,
Dependencies: os.Dependencies,
Private: os.Private,
@ -131,3 +155,16 @@ func (os *ResourceInstanceObjectSrc) CompleteUpgrade(newAttrs cty.Value, newType
new.SchemaVersion = newSchemaVersion
return new, nil
}
func (os *ResourceInstanceObjectSrc) CompleteIdentityUpgrade(newAttrs cty.Value, schema providers.Schema) (*ResourceInstanceObjectSrc, error) {
new := os.DeepCopy()
src, err := ctyjson.Marshal(newAttrs, schema.Identity.ImpliedType())
if err != nil {
return nil, err
}
new.IdentityJSON = src
new.IdentitySchemaVersion = uint64(schema.IdentityVersion)
return new, nil
}

View file

@ -10,7 +10,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/providers"
"github.com/zclconf/go-cty/cty"
)
@ -23,6 +25,27 @@ func TestResourceInstanceObject_encode(t *testing.T) {
"sensitive_a": cty.StringVal("secret").Mark(marks.Sensitive),
"sensitive_b": cty.StringVal("secret").Mark(marks.Sensitive),
})
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.Bool,
},
"obj": {
Type: cty.Object(map[string]cty.Type{
"sensitive": cty.String,
}),
},
"sensitive_a": {
Type: cty.String,
},
"sensitive_b": {
Type: cty.String,
},
},
},
Version: 0,
}
// The in-memory order of resource dependencies is random, since they're an
// unordered set.
depsOne := []addrs.ConfigResource{
@ -72,7 +95,7 @@ func TestResourceInstanceObject_encode(t *testing.T) {
wg.Add(1)
go func() {
defer wg.Done()
rios, err := obj.Encode(value.Type(), 0)
rios, err := obj.Encode(schema)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
@ -108,12 +131,22 @@ func TestResourceInstanceObject_encodeInvalidMarks(t *testing.T) {
// value in the state.
"foo": cty.True.Mark("unsupported"),
})
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {
Type: cty.Bool,
},
},
},
Version: 0,
}
obj := &ResourceInstanceObject{
Value: value,
Status: ObjectReady,
}
_, err := obj.Encode(value.Type(), 0)
_, err := obj.Encode(schema)
if err == nil {
t.Fatalf("unexpected success; want error")
}

View file

@ -129,8 +129,9 @@ func TestStatePersist(t *testing.T) {
"attributes_flat": map[string]interface{}{
"filename": "file.txt",
},
"schema_version": 0.0,
"sensitive_attributes": []interface{}{},
"identity_schema_version": 0.0,
"schema_version": 0.0,
"sensitive_attributes": []interface{}{},
},
},
"mode": "managed",
@ -167,8 +168,9 @@ func TestStatePersist(t *testing.T) {
"attributes_flat": map[string]interface{}{
"filename": "file.txt",
},
"schema_version": 0.0,
"sensitive_attributes": []interface{}{},
"identity_schema_version": 0.0,
"schema_version": 0.0,
"sensitive_attributes": []interface{}{},
},
},
"mode": "managed",

View file

@ -142,6 +142,12 @@ func (os *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc {
copy(attrsJSON, os.AttrsJSON)
}
var identityJSON []byte
if os.IdentityJSON != nil {
identityJSON = make([]byte, len(os.IdentityJSON))
copy(identityJSON, os.IdentityJSON)
}
var sensitiveAttrPaths []cty.Path
if os.AttrSensitivePaths != nil {
sensitiveAttrPaths = make([]cty.Path, len(os.AttrSensitivePaths))
@ -163,15 +169,18 @@ func (os *ResourceInstanceObjectSrc) DeepCopy() *ResourceInstanceObjectSrc {
}
return &ResourceInstanceObjectSrc{
Status: os.Status,
SchemaVersion: os.SchemaVersion,
Private: private,
AttrsFlat: attrsFlat,
AttrsJSON: attrsJSON,
AttrSensitivePaths: sensitiveAttrPaths,
Dependencies: dependencies,
CreateBeforeDestroy: os.CreateBeforeDestroy,
decodeValueCache: os.decodeValueCache,
Status: os.Status,
SchemaVersion: os.SchemaVersion,
Private: private,
AttrsFlat: attrsFlat,
AttrsJSON: attrsJSON,
AttrSensitivePaths: sensitiveAttrPaths,
Dependencies: dependencies,
CreateBeforeDestroy: os.CreateBeforeDestroy,
decodeValueCache: os.decodeValueCache,
IdentityJSON: identityJSON,
IdentitySchemaVersion: os.IdentitySchemaVersion,
decodeIdentityCache: os.decodeIdentityCache,
}
}

View file

@ -494,6 +494,8 @@ func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstanc
PrivateRaw: privateRaw,
Dependencies: deps,
CreateBeforeDestroy: obj.CreateBeforeDestroy,
IdentitySchemaVersion: obj.IdentitySchemaVersion,
IdentityRaw: obj.IdentityJSON,
}), diags
}
@ -702,6 +704,9 @@ type instanceObjectStateV4 struct {
AttributesFlat map[string]string `json:"attributes_flat,omitempty"`
AttributeSensitivePaths json.RawMessage `json:"sensitive_attributes,omitempty"`
IdentitySchemaVersion uint64 `json:"identity_schema_version"`
IdentityRaw json.RawMessage `json:"identity,omitempty"`
PrivateRaw []byte `json:"private,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`

View file

@ -841,10 +841,20 @@ resource "test_resource" "c" {
for name, attrs := range wantResourceAttrs {
addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
r := state.ResourceInstance(addr)
rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
"value": cty.String,
"output": cty.String,
}))
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
"output": {
Type: cty.String,
},
},
},
Version: 0,
}
rd, err := r.Current.Decode(schema)
if err != nil {
t.Fatalf("error decoding test_resource.a: %s", err)
}
@ -902,10 +912,20 @@ resource "test_resource" "c" {
for name, attrs := range wantResourceAttrs {
addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
r := state.ResourceInstance(addr)
rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
"value": cty.String,
"output": cty.String,
}))
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {
Type: cty.String,
},
"output": {
Type: cty.String,
},
},
},
Version: 0,
}
rd, err := r.Current.Decode(schema)
if err != nil {
t.Fatalf("error decoding test_resource.a: %s", err)
}

View file

@ -377,24 +377,26 @@ resource "ephem_write_only" "wo" {
`,
})
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
Version: 0,
}
ephem := &testing_provider.MockProvider{
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
ResourceTypes: map[string]providers.Schema{
"ephem_write_only": {
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
},
"ephem_write_only": schema,
},
},
}
@ -457,10 +459,7 @@ resource "ephem_write_only" "wo" {
t.Fatalf("Resource instance not found")
}
attrs, err := resourceInstance.Current.Decode(cty.Object(map[string]cty.Type{
"normal": cty.String,
"write_only": cty.String,
}))
attrs, err := resourceInstance.Current.Decode(schema)
if err != nil {
t.Fatalf("Failed to decode attributes: %v", err)
}
@ -489,24 +488,25 @@ resource "ephem_write_only" "wo" {
`,
})
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
}
ephem := &testing_provider.MockProvider{
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
ResourceTypes: map[string]providers.Schema{
"ephem_write_only": {
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
},
"ephem_write_only": schema,
},
},
}
@ -585,10 +585,7 @@ resource "ephem_write_only" "wo" {
t.Fatalf("Resource instance not found")
}
attrs, err := resourceInstance.Current.Decode(cty.Object(map[string]cty.Type{
"normal": cty.String,
"write_only": cty.String,
}))
attrs, err := resourceInstance.Current.Decode(schema)
if err != nil {
t.Fatalf("Failed to decode attributes: %v", err)
}
@ -617,24 +614,25 @@ resource "ephem_write_only" "wo" {
`,
})
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
}
ephem := &testing_provider.MockProvider{
GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
ResourceTypes: map[string]providers.Schema{
"ephem_write_only": {
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"normal": {
Type: cty.String,
Required: true,
},
"write_only": {
Type: cty.String,
WriteOnly: true,
Required: true,
},
},
},
},
"ephem_write_only": schema,
},
},
}
@ -724,10 +722,7 @@ resource "ephem_write_only" "wo" {
t.Fatalf("Resource instance not found")
}
attrs, err := resourceInstance.Current.Decode(cty.Object(map[string]cty.Type{
"normal": cty.String,
"write_only": cty.String,
}))
attrs, err := resourceInstance.Current.Decode(schema)
if err != nil {
t.Fatalf("Failed to decode attributes: %v", err)
}

View file

@ -273,9 +273,9 @@ func TestContext2Apply_unstable(t *testing.T) {
Type: "test_resource",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"].Body
schema := p.GetProviderSchemaResponse.ResourceTypes["test_resource"]
rds := plan.Changes.ResourceInstance(addr)
rd, err := rds.Decode(schema.ImpliedType())
rd, err := rds.Decode(schema.Body.ImpliedType())
if err != nil {
t.Fatal(err)
}
@ -295,7 +295,7 @@ func TestContext2Apply_unstable(t *testing.T) {
t.Fatalf("wrong number of resources %d; want 1", len(mod.Resources))
}
rs, err := rss.Current.Decode(schema.ImpliedType())
rs, err := rss.Current.Decode(schema)
if err != nil {
t.Fatalf("decode error: %v", err)
}

View file

@ -864,12 +864,12 @@ func (c *Context) deferredResources(config *configs.Config, deferrals []*plans.D
for _, deferral := range deferrals {
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
deferral.Change.ProviderAddr.Provider,
deferral.Change.Addr.Resource.Resource.Mode,
deferral.Change.Addr.Resource.Resource.Type)
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
deferralSrc, err := deferral.Encode(ty)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
@ -1001,12 +1001,12 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
newIS := newState.ResourceInstance(addr)
schema, _ := schemas.ResourceTypeConfig(
schema := schemas.ResourceTypeConfig(
provider,
addr.Resource.Resource.Mode,
addr.Resource.Resource.Type,
)
if schema == nil {
if schema.Body == nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
"Missing resource schema from provider",
@ -1014,9 +1014,8 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
))
continue
}
ty := schema.ImpliedType()
oldObj, err := oldIS.Current.Decode(ty)
oldObj, err := oldIS.Current.Decode(schema)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
@ -1028,7 +1027,7 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
var newObj *states.ResourceInstanceObject
if newIS != nil && newIS.Current != nil {
newObj, err = newIS.Current.Decode(ty)
newObj, err = newIS.Current.Decode(schema)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
@ -1039,6 +1038,7 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
}
}
ty := schema.Body.ImpliedType()
var oldVal, newVal cty.Value
oldVal = oldObj.Value
if newObj != nil {

View file

@ -0,0 +1,427 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package terraform
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
"github.com/hashicorp/terraform/internal/lang/marks"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
)
func TestContext2Plan_resource_identity_refresh(t *testing.T) {
for name, tc := range map[string]struct {
StoredIdentitySchemaVersion uint64
StoredIdentityJSON []byte
IdentitySchema providers.IdentitySchema
IdentityData cty.Value
ExpectedIdentity cty.Value
ExpectedError error
ExpectUpgradeResourceIdentityCalled bool
UpgradeResourceIdentityResponse providers.UpgradeResourceIdentityResponse
}{
"no previous identity": {
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
},
"identity version mismatch": {
StoredIdentitySchemaVersion: 1,
StoredIdentityJSON: []byte(`{"id": "foo"}`),
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectedError: fmt.Errorf("Resource instance managed by newer provider version: The current state of aws_instance.web was created by a newer provider version than is currently selected. Upgrade the aws provider to work with this state."),
},
"identity type mismatch": {
StoredIdentitySchemaVersion: 0,
StoredIdentityJSON: []byte(`{"arn": "foo"}`),
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectedError: fmt.Errorf("failed to decode identity schema: unsupported attribute \"arn\". This is most likely a bug in the Provider, providers must not change the identity schema without updating the identity schema version"),
},
"identity upgrade succeeds": {
StoredIdentitySchemaVersion: 1,
StoredIdentityJSON: []byte(`{"arn": "foo"}`),
IdentitySchema: providers.IdentitySchema{
Version: 2,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
UpgradeResourceIdentityResponse: providers.UpgradeResourceIdentityResponse{
UpgradedIdentity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
},
ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectUpgradeResourceIdentityCalled: true,
},
"identity upgrade failed": {
StoredIdentitySchemaVersion: 1,
StoredIdentityJSON: []byte(`{"id": "foo"}`),
IdentitySchema: providers.IdentitySchema{
Version: 2,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"arn": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"arn": cty.StringVal("arn:foo"),
}),
UpgradeResourceIdentityResponse: providers.UpgradeResourceIdentityResponse{
UpgradedIdentity: cty.NilVal,
Diagnostics: tfdiags.Diagnostics{
tfdiags.Sourceless(tfdiags.Error, "failed to upgrade resource identity", "provider was unable to do so"),
},
},
ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{
"arn": cty.StringVal("arn:foo"),
}),
ExpectUpgradeResourceIdentityCalled: true,
ExpectedError: fmt.Errorf("failed to upgrade resource identity: provider was unable to do so"),
},
"identity sent to provider differs from returned one": {
StoredIdentitySchemaVersion: 0,
StoredIdentityJSON: []byte(`{"id": "foo"}`),
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("bar"),
}),
ExpectedIdentity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
ExpectedError: fmt.Errorf("Provider produced different identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an different identity for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."),
},
"identity with unknowns": {
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.UnknownVal(cty.String),
}),
ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an identity with unknown values for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."),
},
"identity with marks": {
IdentitySchema: providers.IdentitySchema{
Version: 0,
Body: &configschema.Object{
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityData: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("marked value").Mark(marks.Sensitive),
}),
ExpectedError: fmt.Errorf("Provider produced invalid identity: Provider \"registry.terraform.io/hashicorp/aws\" planned an identity with marks for aws_instance.web during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker."),
},
} {
t.Run(name, func(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "refresh-basic")
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Computed: true,
},
"foo": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
IdentityTypes: map[string]*configschema.Object{
"aws_instance": tc.IdentitySchema.Body,
},
IdentityTypeSchemaVersions: map[string]uint64{
"aws_instance": uint64(tc.IdentitySchema.Version),
},
})
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
root.SetResourceInstanceCurrent(
mustResourceInstanceAddr("aws_instance.web").Resource,
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`),
IdentitySchemaVersion: tc.StoredIdentitySchemaVersion,
IdentityJSON: tc.StoredIdentityJSON,
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"]
ty := schema.Body.ImpliedType()
readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty)
if err != nil {
t.Fatal(err)
}
p.ReadResourceResponse = &providers.ReadResourceResponse{
NewState: readState,
Identity: tc.IdentityData,
}
p.UpgradeResourceIdentityResponse = &tc.UpgradeResourceIdentityResponse
s, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode})
// TODO: maybe move to comparing diagnostics instead
if tc.ExpectedError != nil {
if !diags.HasErrors() {
t.Fatal("expected error, got none")
}
if diags.Err().Error() != tc.ExpectedError.Error() {
t.Fatalf("unexpected error\nwant: %v\ngot: %v", tc.ExpectedError, diags.Err())
}
return
} else {
if diags.HasErrors() {
t.Fatal(diags.Err())
}
}
if !p.ReadResourceCalled {
t.Fatal("ReadResource should be called")
}
if tc.ExpectUpgradeResourceIdentityCalled && !p.UpgradeResourceIdentityCalled {
t.Fatal("UpgradeResourceIdentity should be called")
}
mod := s.PriorState.RootModule()
fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}
newState, err := schema.Body.CoerceValue(fromState.Value)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(readState, newState, valueComparer) {
t.Fatal(cmp.Diff(readState, newState, valueComparer, equateEmpty))
}
if tc.ExpectedIdentity.Equals(fromState.Identity).False() {
t.Fatalf("wrong identity\nwant: %s\ngot: %s", tc.ExpectedIdentity.GoString(), fromState.Identity.GoString())
}
})
}
}
// This test validates if a resource identity that is deposed and will be destroyed
// can be refreshed with an identity during the plan.
func TestContext2Plan_resource_identity_refresh_destroy_deposed(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "refresh-basic")
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
ResourceTypes: map[string]*configschema.Block{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Computed: true,
},
"foo": {
Type: cty.String,
Optional: true,
Computed: true,
},
},
},
},
IdentityTypes: map[string]*configschema.Object{
"aws_instance": {
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Required: true,
},
},
Nesting: configschema.NestingSingle,
},
},
IdentityTypeSchemaVersions: map[string]uint64{
"aws_instance": 0,
},
})
state := states.NewState()
root := state.EnsureModule(addrs.RootModuleInstance)
deposedKey := states.DeposedKey("00000001")
root.SetResourceInstanceDeposed(
mustResourceInstanceAddr("aws_instance.web").Resource,
deposedKey,
&states.ResourceInstanceObjectSrc{ // no identity recorded
Status: states.ObjectReady,
AttrsJSON: []byte(`{"id":"foo","foo":"bar"}`),
},
mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
)
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"]
ty := schema.Body.ImpliedType()
readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty)
if err != nil {
t.Fatal(err)
}
p.ReadResourceResponse = &providers.ReadResourceResponse{
NewState: readState,
Identity: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
}
s, diags := ctx.Plan(m, state, &PlanOpts{Mode: plans.RefreshOnlyMode})
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if !p.ReadResourceCalled {
t.Fatal("ReadResource should be called")
}
mod := s.PriorState.RootModule()
fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Deposed[deposedKey].Decode(schema)
if err != nil {
t.Fatal(err)
}
newState, err := schema.Body.CoerceValue(fromState.Value)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(readState, newState, valueComparer) {
t.Fatal(cmp.Diff(readState, newState, valueComparer, equateEmpty))
}
expectedIdentity := cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
})
if expectedIdentity.Equals(fromState.Identity).False() {
t.Fatalf("wrong identity\nwant: %s\ngot: %s", expectedIdentity.GoString(), fromState.Identity.GoString())
}
}

View file

@ -43,8 +43,8 @@ func TestContext2Refresh(t *testing.T) {
},
})
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body
ty := schema.ImpliedType()
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"]
ty := schema.Body.ImpliedType()
readState, err := hcl2shim.HCL2ValueFromFlatmap(map[string]string{"id": "foo", "foo": "baz"}, ty)
if err != nil {
t.Fatal(err)
@ -64,12 +64,12 @@ func TestContext2Refresh(t *testing.T) {
}
mod := s.RootModule()
fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty)
fromState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}
newState, err := schema.CoerceValue(fromState.Value)
newState, err := schema.Body.CoerceValue(fromState.Value)
if err != nil {
t.Fatal(err)
}
@ -130,9 +130,7 @@ func TestContext2Refresh_dynamicAttr(t *testing.T) {
},
})
schema := p.GetProviderSchemaResponse.ResourceTypes["test_instance"].Body
ty := schema.ImpliedType()
schema := p.GetProviderSchemaResponse.ResourceTypes["test_instance"]
s, diags := ctx.Refresh(m, startingState, &PlanOpts{Mode: plans.NormalMode})
if diags.HasErrors() {
t.Fatal(diags.Err())
@ -143,7 +141,7 @@ func TestContext2Refresh_dynamicAttr(t *testing.T) {
}
mod := s.RootModule()
newState, err := mod.Resources["test_instance.foo"].Instances[addrs.NoKey].Current.Decode(ty)
newState, err := mod.Resources["test_instance.foo"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}
@ -807,10 +805,8 @@ func TestContext2Refresh_stateBasic(t *testing.T) {
},
})
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"].Body
ty := schema.ImpliedType()
readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
schema := p.GetProviderSchemaResponse.ResourceTypes["aws_instance"]
readStateVal, err := schema.Body.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}))
if err != nil {
@ -831,7 +827,7 @@ func TestContext2Refresh_stateBasic(t *testing.T) {
}
mod := s.RootModule()
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(ty)
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}
@ -889,11 +885,13 @@ func TestContext2Refresh_dataCount(t *testing.T) {
func TestContext2Refresh_dataState(t *testing.T) {
m := testModule(t, "refresh-data-resource-basic")
state := states.NewState()
schema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"inputs": {
Type: cty.Map(cty.String),
Optional: true,
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"inputs": {
Type: cty.Map(cty.String),
Optional: true,
},
},
},
}
@ -902,7 +900,7 @@ func TestContext2Refresh_dataState(t *testing.T) {
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
Provider: &configschema.Block{},
DataSources: map[string]*configschema.Block{
"null_data_source": schema,
"null_data_source": schema.Body,
},
})
@ -934,7 +932,7 @@ func TestContext2Refresh_dataState(t *testing.T) {
mod := s.RootModule()
newState, err := mod.Resources["data.null_data_source.testing"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType())
newState, err := mod.Resources["data.null_data_source.testing"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}
@ -1065,22 +1063,24 @@ func TestContext2Refresh_unknownProvider(t *testing.T) {
func TestContext2Refresh_vars(t *testing.T) {
p := testProvider("aws")
schema := &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"ami": {
Type: cty.String,
Optional: true,
},
"id": {
Type: cty.String,
Computed: true,
schema := providers.Schema{
Body: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"ami": {
Type: cty.String,
Optional: true,
},
"id": {
Type: cty.String,
Computed: true,
},
},
},
}
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&providerSchema{
Provider: &configschema.Block{},
ResourceTypes: map[string]*configschema.Block{"aws_instance": schema},
ResourceTypes: map[string]*configschema.Block{"aws_instance": schema.Body},
})
m := testModule(t, "refresh-vars")
@ -1094,7 +1094,7 @@ func TestContext2Refresh_vars(t *testing.T) {
},
})
readStateVal, err := schema.CoerceValue(cty.ObjectVal(map[string]cty.Value{
readStateVal, err := schema.Body.CoerceValue(cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}))
if err != nil {
@ -1122,7 +1122,7 @@ func TestContext2Refresh_vars(t *testing.T) {
mod := s.RootModule()
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema.ImpliedType())
newState, err := mod.Resources["aws_instance.web"].Instances[addrs.NoKey].Current.Decode(schema)
if err != nil {
t.Fatal(err)
}

View file

@ -58,6 +58,11 @@ type MockEvalContext struct {
ProviderSchemaSchema providers.ProviderSchema
ProviderSchemaError error
ResourceIdentitySchemasCalled bool
ResourceIdentitySchemasAddr addrs.AbsProviderConfig
ResourceIdentitySchemasSchemas providers.ResourceIdentitySchemas
ResourceIdentitySchemasError error
CloseProviderCalled bool
CloseProviderAddr addrs.AbsProviderConfig
CloseProviderProvider providers.Interface
@ -209,6 +214,12 @@ func (c *MockEvalContext) ProviderSchema(addr addrs.AbsProviderConfig) (provider
return c.ProviderSchemaSchema, c.ProviderSchemaError
}
func (c *MockEvalContext) ResourceIdentitySchemas(addr addrs.AbsProviderConfig) (providers.ResourceIdentitySchemas, error) {
c.ResourceIdentitySchemasCalled = true
c.ResourceIdentitySchemasAddr = addr
return c.ResourceIdentitySchemasSchemas, c.ProviderSchemaError
}
func (c *MockEvalContext) CloseProvider(addr addrs.AbsProviderConfig) error {
c.CloseProviderCalled = true
c.CloseProviderAddr = addr

View file

@ -58,5 +58,6 @@ func getProvider(ctx EvalContext, addr addrs.AbsProviderConfig) (providers.Inter
if err != nil {
return nil, providers.ProviderSchema{}, fmt.Errorf("failed to read schema for provider %s: %w", addr, err)
}
return provider, schema, nil
}

View file

@ -13,7 +13,6 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/didyoumean"
"github.com/hashicorp/terraform/internal/instances"
"github.com/hashicorp/terraform/internal/lang"
@ -21,6 +20,7 @@ import (
"github.com/hashicorp/terraform/internal/namedvals"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/deferring"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/resources/ephemeral"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -556,7 +556,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
// We need to build an abs provider address, but we can use a default
// instance since we're only interested in the schema.
schema := d.getResourceSchema(addr, config.Provider)
if schema == nil {
if schema.Body == nil {
// This shouldn't happen, since validation before we get here should've
// taken care of it, but we'll show a reasonable error message anyway.
diags = diags.Append(&hcl.Diagnostic{
@ -567,7 +567,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
})
return cty.DynamicVal, diags
}
ty := schema.ImpliedType()
ty := schema.Body.ImpliedType()
if addr.Mode == addrs.EphemeralResourceMode {
// FIXME: This does not yet work with deferrals, and it would be nice to
@ -695,7 +695,7 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
continue
}
ios, err := is.Current.Decode(ty)
ios, err := is.Current.Decode(schema)
if err != nil {
// This shouldn't happen, since by the time we get here we
// should have upgraded the state data already.
@ -896,13 +896,13 @@ func (d *evaluationStateData) getEphemeralResource(addr addrs.Resource, rng tfdi
}
}
func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.Provider) *configschema.Block {
schema, _, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr, addr.Mode, addr.Type)
func (d *evaluationStateData) getResourceSchema(addr addrs.Resource, providerAddr addrs.Provider) providers.Schema {
schema, err := d.Evaluator.Plugins.ResourceTypeSchema(providerAddr, addr.Mode, addr.Type)
if err != nil {
// We have plently other codepaths that will detect and report
// schema lookup errors before we'd reach this point, so we'll just
// treat a failure here the same as having no schema.
return nil
return providers.Schema{}
}
return schema
}

View file

@ -249,7 +249,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
}
providerFqn := modCfg.Module.ProviderForLocalConfig(cfg.ProviderConfigAddr())
schema, _, err := plugins.ResourceTypeSchema(providerFqn, addr.Mode, addr.Type)
schema, err := plugins.ResourceTypeSchema(providerFqn, addr.Mode, addr.Type)
if err != nil {
// Prior validation should've taken care of a schema lookup error,
// so we should never get here but we'll handle it here anyway for
@ -262,7 +262,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
})
}
if schema == nil {
if schema.Body == nil {
// Prior validation should've taken care of a resource block with an
// unsupported type, so we should never get here but we'll handle it
// here anyway for robustness.
@ -298,7 +298,7 @@ func staticValidateResourceReference(modCfg *configs.Config, addr addrs.Resource
// If we got this far then we'll try to validate the remaining traversal
// steps against our schema.
moreDiags := schema.StaticValidateTraversal(remain)
moreDiags := schema.Body.StaticValidateTraversal(remain)
diags = diags.Append(moreDiags)
return diags

View file

@ -491,12 +491,12 @@ func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr a
return nil, nil
}
schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(addr.Resource.ContainingResource())
if schema.Body == nil {
// Shouldn't happen since we should've failed long ago if no schema is present
return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr))
}
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion)
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema)
if n.Config != nil {
upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String())
}
@ -505,7 +505,13 @@ func (n *NodeAbstractResource) readResourceInstanceState(ctx EvalContext, addr a
return nil, diags
}
obj, err := src.Decode(schema.ImpliedType())
src, upgradeDiags = upgradeResourceIdentity(addr, provider, src, schema)
diags = diags.Append(upgradeDiags)
if diags.HasErrors() {
return nil, diags
}
obj, err := src.Decode(schema)
if err != nil {
diags = diags.Append(err)
}
@ -536,14 +542,14 @@ func (n *NodeAbstractResource) readResourceInstanceStateDeposed(ctx EvalContext,
return nil, diags
}
schema, currentVersion := (providerSchema).SchemaForResourceAddr(addr.Resource.ContainingResource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(addr.Resource.ContainingResource())
if schema.Body == nil {
// Shouldn't happen since we should've failed long ago if no schema is present
return nil, diags.Append(fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", addr))
}
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema, currentVersion)
src, upgradeDiags := upgradeResourceState(addr, provider, src, schema)
if n.Config != nil {
upgradeDiags = upgradeDiags.InConfigBody(n.Config.Config, addr.String())
}
@ -556,7 +562,13 @@ func (n *NodeAbstractResource) readResourceInstanceStateDeposed(ctx EvalContext,
return nil, diags
}
obj, err := src.Decode(schema.ImpliedType())
src, upgradeDiags = upgradeResourceIdentity(addr, provider, src, schema)
diags = diags.Append(upgradeDiags)
if diags.HasErrors() {
return nil, diags
}
obj, err := src.Decode(schema)
if err != nil {
diags = diags.Append(err)
}

View file

@ -167,8 +167,8 @@ func (n *NodeAbstractResourceInstance) readDiff(ctx EvalContext, providerSchema
changes := ctx.Changes()
addr := n.ResourceInstanceAddr()
schema, _ := providerSchema.SchemaForResourceAddr(addr.Resource.Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(addr.Resource.Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
return nil, fmt.Errorf("provider does not support resource type %q", addr.Resource.Resource.Type)
}
@ -341,15 +341,15 @@ func (n *NodeAbstractResourceInstance) writeResourceInstanceStateImpl(ctx EvalCo
log.Printf("[TRACE] %s: writing state object for %s", logFuncName, absAddr)
schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
if schema.Body == nil {
// It shouldn't be possible to get this far in any real scenario
// without a schema, but we might end up here in contrived tests that
// fail to set up their world properly.
return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
}
src, err := obj.Encode(schema.ImpliedType(), currentVersion)
src, err := obj.Encode(schema)
if err != nil {
return fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
}
@ -596,8 +596,8 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
return state, deferred, diags
}
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.Resource.ContainingResource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.Addr.Resource.ContainingResource())
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Resource.Type))
return state, deferred, diags
@ -630,6 +630,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
// to the provider so we'll just return whatever was in state.
resp = providers.ReadResourceResponse{
NewState: priorVal,
Identity: state.Identity,
}
} else {
resp = provider.ReadResource(providers.ReadResourceRequest{
@ -679,7 +680,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
))
}
for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
for _, err := range resp.NewState.Type().TestConformance(schema.Body.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid object",
@ -703,7 +704,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
)
},
resp.NewState,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -711,7 +712,12 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
return state, deferred, diags
}
newState := objchange.NormalizeObjectFromLegacySDK(resp.NewState, schema)
diags = diags.Append(n.validateIdentity(state, resp.Identity, false))
if diags.HasErrors() {
return state, deferred, diags
}
newState := objchange.NormalizeObjectFromLegacySDK(resp.NewState, schema.Body)
if !newState.RawEquals(resp.NewState) {
// We had to fix up this object in some way, and we still need to
// accept any changes for compatibility, so all we can do is log a
@ -722,6 +728,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
ret := state.DeepCopy()
ret.Value = newState
ret.Private = resp.Private
ret.Identity = resp.Identity
// Call post-refresh hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
@ -737,7 +744,7 @@ func (n *NodeAbstractResourceInstance) refresh(ctx EvalContext, deposedKey state
// the prior state. New marks may appear when the prior state was from an
// import operation, or if the provider added new marks to the schema.
ret.Value = ret.Value.MarkWithPaths(priorMarks)
if moreSensitivePaths := schema.SensitivePaths(ret.Value, nil); len(moreSensitivePaths) != 0 {
if moreSensitivePaths := schema.Body.SensitivePaths(ret.Value, nil); len(moreSensitivePaths) != 0 {
ret.Value = marks.MarkPaths(ret.Value, marks.Sensitive, moreSensitivePaths)
}
@ -763,8 +770,8 @@ func (n *NodeAbstractResourceInstance) plan(
return nil, nil, deferred, keyData, diags.Append(err)
}
schema, _ := providerSchema.SchemaForResourceAddr(resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", resource.Type))
return nil, nil, deferred, keyData, diags
@ -819,10 +826,10 @@ func (n *NodeAbstractResourceInstance) plan(
return plannedChange, currentState.DeepCopy(), deferred, keyData, diags
}
origConfigVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData)
origConfigVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, origConfigVal, schema).InConfigBody(n.Config.Config, n.Addr.String()),
validateResourceForbiddenEphemeralValues(ctx, origConfigVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
if diags.HasErrors() {
return nil, nil, deferred, keyData, diags
@ -848,10 +855,10 @@ func (n *NodeAbstractResourceInstance) plan(
// result as if the provider had marked at least one argument
// change as "requires replacement".
priorValTainted = currentState.Value
priorVal = cty.NullVal(schema.ImpliedType())
priorVal = cty.NullVal(schema.Body.ImpliedType())
}
} else {
priorVal = cty.NullVal(schema.ImpliedType())
priorVal = cty.NullVal(schema.Body.ImpliedType())
}
log.Printf("[TRACE] Re-validating config for %q", n.Addr)
@ -885,7 +892,7 @@ func (n *NodeAbstractResourceInstance) plan(
// starting values.
// Here we operate on the marked values, so as to revert any changes to the
// marks as well as the value.
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(priorVal, origConfigVal, schema)
configValIgnored, ignoreChangeDiags := n.processIgnoreChanges(priorVal, origConfigVal, schema.Body)
diags = diags.Append(ignoreChangeDiags)
if ignoreChangeDiags.HasErrors() {
return nil, nil, deferred, keyData, diags
@ -897,7 +904,7 @@ func (n *NodeAbstractResourceInstance) plan(
unmarkedConfigVal, unmarkedPaths := configValIgnored.UnmarkDeepWithPaths()
unmarkedPriorVal, _ := priorVal.UnmarkDeepWithPaths()
proposedNewVal := objchange.ProposedNew(schema, unmarkedPriorVal, unmarkedConfigVal)
proposedNewVal := objchange.ProposedNew(schema.Body, unmarkedPriorVal, unmarkedConfigVal)
// Call pre-diff hook
diags = diags.Append(ctx.Hook(func(h Hook) (HookAction, error) {
@ -919,7 +926,7 @@ func (n *NodeAbstractResourceInstance) plan(
Value: n.override.Values,
Range: n.override.Range,
ComputedAsUnknown: !n.override.UseForPlan,
}, schema)
}, schema.Body)
resp = providers.PlanResourceChangeResponse{
PlannedState: override,
Diagnostics: overrideDiags,
@ -980,7 +987,7 @@ func (n *NodeAbstractResourceInstance) plan(
)
},
plannedNewVal,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -992,7 +999,7 @@ func (n *NodeAbstractResourceInstance) plan(
// here, since that allows the provider to do special logic like a
// DiffSuppressFunc, but we still require that the provider produces
// a value whose type conforms to the schema.
for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
for _, err := range plannedNewVal.Type().TestConformance(schema.Body.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid plan",
@ -1007,7 +1014,7 @@ func (n *NodeAbstractResourceInstance) plan(
return nil, nil, deferred, keyData, diags
}
if errs := objchange.AssertPlanValid(schema, unmarkedPriorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if errs := objchange.AssertPlanValid(schema.Body, unmarkedPriorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if resp.LegacyTypeSystem {
// The shimming of the old type system in the legacy SDK is not precise
// enough to pass this consistency check, so we'll give it a pass here,
@ -1069,11 +1076,11 @@ func (n *NodeAbstractResourceInstance) plan(
unmarkedPaths = marks.RemoveAll(unmarkedPaths, marks.Ephemeral)
plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths)
if sensitivePaths := schema.SensitivePaths(plannedNewVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(plannedNewVal, nil); len(sensitivePaths) != 0 {
plannedNewVal = marks.MarkPaths(plannedNewVal, marks.Sensitive, sensitivePaths)
}
writeOnlyPaths := schema.WriteOnlyPaths(plannedNewVal, nil)
writeOnlyPaths := schema.Body.WriteOnlyPaths(plannedNewVal, nil)
reqRep, reqRepDiags := getRequiredReplaces(unmarkedPriorVal, unmarkedPlannedNewVal, writeOnlyPaths, resp.RequiresReplace, n.ResolvedProvider.Provider, n.Addr)
diags = diags.Append(reqRepDiags)
@ -1096,7 +1103,7 @@ func (n *NodeAbstractResourceInstance) plan(
// The resulting change should show any computed attributes changing
// from known prior values to unknown values, unless the provider is
// able to predict new values for any of these computed attributes.
nullPriorVal := cty.NullVal(schema.ImpliedType())
nullPriorVal := cty.NullVal(schema.Body.ImpliedType())
// Since there is no prior state to compare after replacement, we need
// a new unmarked config from our original with no ignored values.
@ -1106,7 +1113,7 @@ func (n *NodeAbstractResourceInstance) plan(
}
// create a new proposed value from the null state and the config
proposedNewVal = objchange.ProposedNew(schema, nullPriorVal, unmarkedConfigVal)
proposedNewVal = objchange.ProposedNew(schema.Body, nullPriorVal, unmarkedConfigVal)
if n.override != nil {
// In this case, we are always creating the resource so we don't
@ -1115,7 +1122,7 @@ func (n *NodeAbstractResourceInstance) plan(
Value: n.override.Values,
Range: n.override.Range,
ComputedAsUnknown: !n.override.UseForPlan,
}, schema)
}, schema.Body)
resp = providers.PlanResourceChangeResponse{
PlannedState: override,
Diagnostics: overrideDiags,
@ -1158,7 +1165,7 @@ func (n *NodeAbstractResourceInstance) plan(
plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths)
}
for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
for _, err := range plannedNewVal.Type().TestConformance(schema.Body.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid plan",
@ -1182,7 +1189,7 @@ func (n *NodeAbstractResourceInstance) plan(
)
},
plannedNewVal,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -1544,8 +1551,8 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
if diags.HasErrors() {
return newVal, deferred, diags
}
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type))
return newVal, deferred, diags
@ -1590,7 +1597,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
Value: n.override.Values,
Range: n.override.Range,
ComputedAsUnknown: false,
}, schema)
}, schema.Body)
resp = providers.ReadDataSourceResponse{
State: override,
Diagnostics: overrideDiags,
@ -1621,12 +1628,12 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
if newVal == cty.NilVal {
// This can happen with incompletely-configured mocks. We'll allow it
// and treat it as an alias for a properly-typed null value.
newVal = cty.NullVal(schema.ImpliedType())
newVal = cty.NullVal(schema.Body.ImpliedType())
}
// We don't want to run the checks if the data source read is deferred
if deferred == nil {
for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
for _, err := range newVal.Type().TestConformance(schema.Body.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid object",
@ -1671,7 +1678,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
}
}
newVal = newVal.MarkWithPaths(pvm)
if sensitivePaths := schema.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
newVal = marks.MarkPaths(newVal, marks.Sensitive, sensitivePaths)
}
@ -1750,14 +1757,14 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
}
config := *n.Config
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type))
return nil, nil, deferred, keyData, diags
}
objTy := schema.ImpliedType()
objTy := schema.Body.ImpliedType()
priorVal := cty.NullVal(objTy)
forEach, _, _ := evaluateForEachExpression(config.ForEach, ctx, false)
@ -1775,10 +1782,10 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
}
var configDiags tfdiags.Diagnostics
configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema, nil, keyData)
configVal, _, configDiags = ctx.EvaluateBlock(config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, configVal, schema).InConfigBody(n.Config.Config, n.Addr.String()),
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
if diags.HasErrors() {
return nil, nil, deferred, keyData, diags
@ -1842,13 +1849,13 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
reason = plans.ResourceInstanceReadBecauseDependencyPending
}
proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
proposedNewVal := objchange.PlannedDataResourceObject(schema.Body, unmarkedConfigVal)
// even though we are only returning the config value because we can't
// yet read the data source, we need to incorporate the schema marks so
// that downstream consumers can detect them when planning.
proposedNewVal = proposedNewVal.MarkWithPaths(unmarkedPaths)
if sensitivePaths := schema.SensitivePaths(proposedNewVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(proposedNewVal, nil); len(sensitivePaths) != 0 {
proposedNewVal = marks.MarkPaths(proposedNewVal, marks.Sensitive, sensitivePaths)
}
@ -1917,13 +1924,13 @@ func (n *NodeAbstractResourceInstance) planDataSource(ctx EvalContext, checkRule
if readDiags.HasErrors() {
// If we had errors, then we can cover that up by marking the new
// state as unknown.
newVal = objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
newVal = objchange.PlannedDataResourceObject(schema.Body, unmarkedConfigVal)
// not only do we want to ensure this synthetic value has the marks,
// but since this is the value being returned from the data source
// we need to ensure the schema marks are added as well.
newVal = newVal.MarkWithPaths(unmarkedPaths)
if sensitivePaths := schema.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
newVal = marks.MarkPaths(newVal, marks.Sensitive, sensitivePaths)
}
@ -2080,8 +2087,8 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned
}
config := *n.Config
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.Addr.ContainingResource().Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider %q does not support data source %q", n.ResolvedProvider, n.Addr.ContainingResource().Resource.Type))
return nil, keyData, diags
@ -2111,7 +2118,7 @@ func (n *NodeAbstractResourceInstance) applyDataSource(ctx EvalContext, planned
return nil, keyData, diags
}
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData)
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, keyData, diags
@ -2477,8 +2484,8 @@ func (n *NodeAbstractResourceInstance) apply(
if err != nil {
return nil, diags.Append(err)
}
schema, _ := providerSchema.SchemaForResourceType(n.Addr.Resource.Resource.Mode, n.Addr.Resource.Resource.Type)
if schema == nil {
schema := providerSchema.SchemaForResourceType(n.Addr.Resource.Resource.Mode, n.Addr.Resource.Resource.Type)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Resource.Type))
return nil, diags
@ -2489,7 +2496,7 @@ func (n *NodeAbstractResourceInstance) apply(
configVal := cty.NullVal(cty.DynamicPseudoType)
if applyConfig != nil {
var configDiags tfdiags.Diagnostics
configVal, _, configDiags = ctx.EvaluateBlock(applyConfig.Config, schema, nil, keyData)
configVal, _, configDiags = ctx.EvaluateBlock(applyConfig.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return nil, diags
@ -2564,7 +2571,7 @@ func (n *NodeAbstractResourceInstance) apply(
Value: n.override.Values,
Range: n.override.Range,
ComputedAsUnknown: false,
}, schema)
}, schema.Body)
resp = providers.ApplyResourceChangeResponse{
NewState: override,
Diagnostics: overrideDiags,
@ -2608,7 +2615,7 @@ func (n *NodeAbstractResourceInstance) apply(
// we were trying to execute a delete, because the provider in this case
// probably left the newVal unset intending it to be interpreted as "null".
if change.After.IsNull() {
newVal = cty.NullVal(schema.ImpliedType())
newVal = cty.NullVal(schema.Body.ImpliedType())
}
if !diags.HasErrors() {
@ -2624,7 +2631,7 @@ func (n *NodeAbstractResourceInstance) apply(
}
var conformDiags tfdiags.Diagnostics
for _, err := range newVal.Type().TestConformance(schema.ImpliedType()) {
for _, err := range newVal.Type().TestConformance(schema.Body.ImpliedType()) {
conformDiags = conformDiags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid object",
@ -2652,7 +2659,7 @@ func (n *NodeAbstractResourceInstance) apply(
)
},
newVal,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -2705,7 +2712,7 @@ func (n *NodeAbstractResourceInstance) apply(
// won't be included in afterPaths, which are only what was read from the
// After plan value.
newVal = newVal.MarkWithPaths(afterPaths)
if sensitivePaths := schema.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(newVal, nil); len(sensitivePaths) != 0 {
newVal = marks.MarkPaths(newVal, marks.Sensitive, sensitivePaths)
}
@ -2719,7 +2726,7 @@ func (n *NodeAbstractResourceInstance) apply(
// a pass since the other errors are usually the explanation for
// this one and so it's more helpful to let the user focus on the
// root cause rather than distract with this extra problem.
if errs := objchange.AssertObjectCompatible(schema, change.After, newVal); len(errs) > 0 {
if errs := objchange.AssertObjectCompatible(schema.Body, change.After, newVal); len(errs) > 0 {
if resp.LegacyTypeSystem {
// The shimming of the old type system in the legacy SDK is not precise
// enough to pass this consistency check, so we'll give it a pass here,
@ -2831,6 +2838,47 @@ func (n *NodeAbstractResourceInstance) prevRunAddr(ctx EvalContext) addrs.AbsRes
return resourceInstancePrevRunAddr(ctx, n.Addr)
}
func (n *NodeAbstractResourceInstance) validateIdentity(state *states.ResourceInstanceObject, newIdentity cty.Value, isAllowedToChange bool) (diags tfdiags.Diagnostics) {
// Identities can not contain unknown values
if !newIdentity.IsWhollyKnown() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid identity",
fmt.Sprintf(
"Provider %q planned an identity with unknown values for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ResolvedProvider.Provider, n.Addr,
),
))
}
// Identities can not contain marks
if _, marks := newIdentity.UnmarkDeep(); len(marks) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid identity",
fmt.Sprintf(
"Provider %q planned an identity with marks for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ResolvedProvider.Provider, n.Addr,
),
))
}
// Identities can not change (except if they are re-created or initially recorded)
if !isAllowedToChange && !state.Identity.IsNull() && state.Identity.Equals(newIdentity).False() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced different identity",
fmt.Sprintf(
"Provider %q planned an different identity for %s during refresh. \n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ResolvedProvider.Provider, n.Addr,
),
))
}
return diags
}
func resourceInstancePrevRunAddr(ctx EvalContext, currentAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance {
table := ctx.MoveResults()
return table.OldAddr(currentAddr)

View file

@ -407,8 +407,8 @@ func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plan
var diags tfdiags.Diagnostics
addr := n.ResourceInstanceAddr().Resource
schema, _ := providerSchema.SchemaForResourceAddr(addr.ContainingResource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(addr.ContainingResource())
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support %q", addr.Resource.Type))
return diags
@ -450,7 +450,7 @@ func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plan
}
}
errs := objchange.AssertObjectCompatible(schema, plannedChange.After, actualChange.After)
errs := objchange.AssertObjectCompatible(schema.Body, plannedChange.After, actualChange.After)
for _, err := range errs {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,

View file

@ -447,14 +447,15 @@ func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ct
return err
}
schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
if schema.Body == nil {
// It shouldn't be possible to get this far in any real scenario
// without a schema, but we might end up here in contrived tests that
// fail to set up their world properly.
return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
}
src, err := obj.Encode(schema.ImpliedType(), currentVersion)
src, err := obj.Encode(schema)
if err != nil {
return fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
}

View file

@ -40,8 +40,8 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) (*provid
}
config := inp.config
schema, _ := providerSchema.SchemaForResourceAddr(inp.addr.ContainingResource().Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(inp.addr.ContainingResource().Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(
fmt.Errorf("provider %q does not support ephemeral resource %q",
@ -71,7 +71,7 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) (*provid
return nil, diags // failed preconditions prevent further evaluation
}
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema, nil, keyData)
configVal, _, configDiags := ctx.EvaluateBlock(config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if diags.HasErrors() {
return nil, diags
@ -84,7 +84,7 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) (*provid
// We don't know what the result will be, but we need to keep the
// configured attributes for consistent evaluation. We can use the same
// technique we used for data sources to create the plan-time value.
unknownResult := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
unknownResult := objchange.PlannedDataResourceObject(schema.Body, unmarkedConfigVal)
// add back any configured marks
unknownResult = unknownResult.MarkWithPaths(configMarks)
// and mark the entire value as ephemeral, since it's coming from an ephemeral context.
@ -135,7 +135,7 @@ func ephemeralResourceOpen(ctx EvalContext, inp ephemeralResourceInput) (*provid
}
resultVal := resp.Result.MarkWithPaths(configMarks)
errs := objchange.AssertPlanValid(schema, cty.NullVal(schema.ImpliedType()), configVal, resultVal)
errs := objchange.AssertPlanValid(schema.Body, cty.NullVal(schema.Body.ImpliedType()), configVal, resultVal)
for _, err := range errs {
diags = diags.Append(tfdiags.AttributeValue(
tfdiags.Error,

View file

@ -81,8 +81,8 @@ func (n *graphNodeImportState) Execute(ctx EvalContext, op walkOperation) (diags
return diags
}
schema, _ := providerSchema.SchemaForResourceType(n.Addr.Resource.Resource.Mode, n.Addr.Resource.Resource.Type)
if schema == nil {
schema := providerSchema.SchemaForResourceType(n.Addr.Resource.Resource.Mode, n.Addr.Resource.Resource.Type)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.Addr.Resource.Resource.Type))
return diags
@ -125,7 +125,7 @@ func (n *graphNodeImportState) Execute(ctx EvalContext, op walkOperation) (diags
)
},
imported.State,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
}
@ -250,7 +250,7 @@ func (n *graphNodeImportStateSub) Execute(ctx EvalContext, op walkOperation) (di
return diags
}
state := n.State.AsInstanceObject()
state := states.NewResourceInstanceObjectFromIR(n.State)
// Refresh
riNode := &NodeAbstractResourceInstance{

View file

@ -587,8 +587,8 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
return nil, deferred, diags
}
schema, _ := providerSchema.SchemaForResourceAddr(n.Addr.Resource.Resource)
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.Addr.Resource.Resource)
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type for %q", n.Addr))
return nil, deferred, diags
@ -615,7 +615,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
forEach, _, _ := evaluateForEachExpression(n.Config.ForEach, ctx, false)
keyData := EvalDataForInstanceKey(n.ResourceInstanceAddr().Resource.Key, forEach)
configVal, _, configDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
configVal, _, configDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
if configDiags.HasErrors() {
// We have an overridden resource so we're definitely in a test and
// the users config is not valid. So give up and just report the
@ -638,7 +638,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
Value: n.override.Values,
Range: n.override.Range,
ComputedAsUnknown: false,
}, schema)
}, schema.Body)
resp = providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
@ -699,12 +699,12 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
// state we're going to import.
state := providers.ImportedResource{
TypeName: addr.Resource.Resource.Type,
State: cty.NullVal(schema.ImpliedType()),
State: cty.NullVal(schema.Body.ImpliedType()),
}
// We skip the read and further validation since we make up the state
// of the imported resource anyways.
return state.AsInstanceObject(), deferred, diags
return states.NewResourceInstanceObjectFromIR(state), deferred, diags
}
for _, obj := range imported {
@ -735,7 +735,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
)
},
imported[0].State,
schema,
schema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -743,7 +743,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
return nil, deferred, diags
}
importedState := imported[0].AsInstanceObject()
importedState := states.NewResourceInstanceObjectFromIR(imported[0])
if deferred == nil && importedState.Value.IsNull() {
// It's actually okay for a deferred import to have returned a null.
diags = diags.Append(tfdiags.Sourceless(
@ -807,7 +807,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs.
// First we generate the contents of the resource block for use within
// the planning node. Then we wrap it in an enclosing resource block to
// pass into the plan for rendering.
generatedHCLAttributes, generatedDiags := n.generateHCLStringAttributes(n.Addr, instanceRefreshState, schema)
generatedHCLAttributes, generatedDiags := n.generateHCLStringAttributes(n.Addr, instanceRefreshState, schema.Body)
diags = diags.Append(generatedDiags)
n.generatedConfigHCL = genconfig.WrapResourceContents(n.Addr, generatedHCLAttributes)

View file

@ -165,8 +165,8 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
return &change, diags
}
schema, _ := providerSchema.SchemaForResourceAddr(n.addr.Resource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.addr.Resource())
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.addr.Resource().Type))
return &change, diags
@ -194,7 +194,7 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
keyData := n.keyData()
configVal, _, configDiags := ctx.EvaluateBlock(n.config.Config, schema, nil, keyData)
configVal, _, configDiags := ctx.EvaluateBlock(n.config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return &change, diags
@ -214,8 +214,8 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
}
unmarkedConfigVal, unmarkedPaths := configVal.UnmarkDeepWithPaths()
priorVal := cty.NullVal(schema.ImpliedType()) // we don't have any specific prior value to use
proposedNewVal := objchange.ProposedNew(schema, priorVal, unmarkedConfigVal)
priorVal := cty.NullVal(schema.Body.ImpliedType()) // we don't have any specific prior value to use
proposedNewVal := objchange.ProposedNew(schema.Body, priorVal, unmarkedConfigVal)
// The provider now gets to plan an imaginary substitute that represents
// all of the possible resource instances together. Correctly-implemented
@ -247,7 +247,7 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
panic(fmt.Sprintf("PlanResourceChange of %s produced nil value", n.addr.String()))
}
for _, err := range plannedNewVal.Type().TestConformance(schema.ImpliedType()) {
for _, err := range plannedNewVal.Type().TestConformance(schema.Body.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Provider produced invalid plan",
@ -261,7 +261,7 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
return &change, diags
}
if errs := objchange.AssertPlanValid(schema, priorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if errs := objchange.AssertPlanValid(schema.Body, priorVal, unmarkedConfigVal, plannedNewVal); len(errs) > 0 {
if resp.LegacyTypeSystem {
// The shimming of the old type system in the legacy SDK is not precise
// enough to pass this consistency check, so we'll give it a pass here,
@ -295,7 +295,7 @@ func (n *nodePlannablePartialExpandedResource) managedResourceExecute(ctx EvalCo
// We need to combine the dynamic marks with the static marks implied by
// the provider's schema.
plannedNewVal = plannedNewVal.MarkWithPaths(unmarkedPaths)
if sensitivePaths := schema.SensitivePaths(plannedNewVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(plannedNewVal, nil); len(sensitivePaths) != 0 {
plannedNewVal = marks.MarkPaths(plannedNewVal, marks.Sensitive, sensitivePaths)
}
@ -339,8 +339,8 @@ func (n *nodePlannablePartialExpandedResource) dataResourceExecute(ctx EvalConte
// This is the point where we switch to mirroring logic from
// NodeAbstractResourceInstance's planDataSource. If you were curious.
schema, _ := providerSchema.SchemaForResourceAddr(n.addr.Resource())
if schema == nil {
schema := providerSchema.SchemaForResourceAddr(n.addr.Resource())
if schema.Body == nil {
// Should be caught during validation, so we don't bother with a pretty error here
diags = diags.Append(fmt.Errorf("provider does not support resource type %q", n.addr.Resource().Type))
return &change, diags
@ -348,7 +348,7 @@ func (n *nodePlannablePartialExpandedResource) dataResourceExecute(ctx EvalConte
keyData := n.keyData()
configVal, _, configDiags := ctx.EvaluateBlock(n.config.Config, schema, nil, keyData)
configVal, _, configDiags := ctx.EvaluateBlock(n.config.Config, schema.Body, nil, keyData)
diags = diags.Append(configDiags)
if configDiags.HasErrors() {
return &change, diags
@ -369,9 +369,9 @@ func (n *nodePlannablePartialExpandedResource) dataResourceExecute(ctx EvalConte
// logic for a data source with unknown config, which is sort of what we
// are, after all.
unmarkedConfigVal, unmarkedPaths := configVal.UnmarkDeepWithPaths()
proposedNewVal := objchange.PlannedDataResourceObject(schema, unmarkedConfigVal)
proposedNewVal := objchange.PlannedDataResourceObject(schema.Body, unmarkedConfigVal)
proposedNewVal = proposedNewVal.MarkWithPaths(unmarkedPaths)
if sensitivePaths := schema.SensitivePaths(proposedNewVal, nil); len(sensitivePaths) != 0 {
if sensitivePaths := schema.Body.SensitivePaths(proposedNewVal, nil); len(sensitivePaths) != 0 {
proposedNewVal = marks.MarkPaths(proposedNewVal, marks.Sensitive, sensitivePaths)
}
// yay we made it

View file

@ -323,10 +323,10 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
// in the provider abstraction.
switch n.Config.Mode {
case addrs.ManagedResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
schema := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema.Body == nil {
var suggestion string
if dSchema, _ := providerSchema.SchemaForResourceType(addrs.DataResourceMode, n.Config.Type); dSchema != nil {
if dSchema := providerSchema.SchemaForResourceType(addrs.DataResourceMode, n.Config.Type); dSchema.Body != nil {
suggestion = fmt.Sprintf("\n\nDid you intend to use the data source %q? If so, declare this using a \"data\" block instead of a \"resource\" block.", n.Config.Type)
} else if len(providerSchema.ResourceTypes) > 0 {
suggestions := make([]string, 0, len(providerSchema.ResourceTypes))
@ -347,19 +347,19 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
return diags
}
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
return diags
}
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, configVal, schema).InConfigBody(n.Config.Config, n.Addr.String()),
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
if n.Config.Managed != nil { // can be nil only in tests with poorly-configured mocks
for _, traversal := range n.Config.Managed.IgnoreChanges {
// validate the ignore_changes traversals apply.
moreDiags := schema.StaticValidateTraversal(traversal)
moreDiags := schema.Body.StaticValidateTraversal(traversal)
diags = diags.Append(moreDiags)
// ignore_changes cannot be used for Computed attributes,
@ -370,7 +370,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
if !diags.HasErrors() {
path, _ := traversalToPath(traversal)
attrSchema := schema.AttributeByPath(path)
attrSchema := schema.Body.AttributeByPath(path)
if attrSchema != nil && !attrSchema.Optional && attrSchema.Computed {
// ignore_changes uses absolute traversal syntax in config despite
@ -402,10 +402,10 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.DataResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
schema := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema.Body == nil {
var suggestion string
if dSchema, _ := providerSchema.SchemaForResourceType(addrs.ManagedResourceMode, n.Config.Type); dSchema != nil {
if dSchema := providerSchema.SchemaForResourceType(addrs.ManagedResourceMode, n.Config.Type); dSchema.Body != nil {
suggestion = fmt.Sprintf("\n\nDid you intend to use the managed resource type %q? If so, declare this using a \"resource\" block instead of a \"data\" block.", n.Config.Type)
} else if len(providerSchema.DataSources) > 0 {
suggestions := make([]string, 0, len(providerSchema.DataSources))
@ -426,13 +426,13 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
return diags
}
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
return diags
}
diags = diags.Append(
validateResourceForbiddenEphemeralValues(ctx, configVal, schema).InConfigBody(n.Config.Config, n.Addr.String()),
validateResourceForbiddenEphemeralValues(ctx, configVal, schema.Body).InConfigBody(n.Config.Config, n.Addr.String()),
)
// Use unmarked value for validate request
@ -445,8 +445,8 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
resp := provider.ValidateDataResourceConfig(req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.EphemeralResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema == nil {
schema := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
if schema.Body == nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid ephemeral resource",
@ -456,7 +456,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
return diags
}
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema, nil, keyData)
configVal, _, valDiags := ctx.EvaluateBlock(n.Config.Config, schema.Body, nil, keyData)
diags = diags.Append(valDiags)
if valDiags.HasErrors() {
return diags

View file

@ -124,6 +124,8 @@ type providerSchema struct {
ResourceTypes map[string]*configschema.Block
ResourceTypeSchemaVersions map[string]uint64
DataSources map[string]*configschema.Block
IdentityTypes map[string]*configschema.Object
IdentityTypeSchemaVersions map[string]uint64
}
// getProviderSchemaResponseFromProviderSchema is a test helper to convert a
@ -137,10 +139,18 @@ func getProviderSchemaResponseFromProviderSchema(providerSchema *providerSchema)
}
for name, schema := range providerSchema.ResourceTypes {
resp.ResourceTypes[name] = providers.Schema{
ps := providers.Schema{
Body: schema,
Version: int64(providerSchema.ResourceTypeSchemaVersions[name]),
}
id, ok := providerSchema.IdentityTypes[name]
if ok {
ps.Identity = id
ps.IdentityVersion = int64(providerSchema.IdentityTypeSchemaVersions[name])
}
resp.ResourceTypes[name] = ps
}
for name, schema := range providerSchema.DataSources {

View file

@ -65,16 +65,16 @@ func (t *AttachSchemaTransformer) Transform(g *Graph) error {
typeName := addr.Resource.Type
providerFqn := tv.Provider()
schema, version, err := t.Plugins.ResourceTypeSchema(providerFqn, mode, typeName)
schema, err := t.Plugins.ResourceTypeSchema(providerFqn, mode, typeName)
if err != nil {
return fmt.Errorf("failed to read schema for %s in %s: %s", addr, providerFqn, err)
}
if schema == nil {
if schema.Body == nil {
log.Printf("[ERROR] AttachSchemaTransformer: No resource schema available for %s", addr)
continue
}
log.Printf("[TRACE] AttachSchemaTransformer: attaching resource schema to %s", dag.VertexName(v))
tv.AttachResourceSchema(schema, version)
tv.AttachResourceSchema(schema.Body, uint64(schema.Version))
}
if tv, ok := v.(GraphNodeAttachProviderConfigSchema); ok {

View file

@ -10,7 +10,6 @@ import (
"log"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/ephemeral"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
@ -25,7 +24,7 @@ import (
//
// If any errors occur during upgrade, error diagnostics are returned. In that
// case it is not safe to proceed with using the original state object.
func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Interface, src *states.ResourceInstanceObjectSrc, currentSchema *configschema.Block, currentVersion uint64) (*states.ResourceInstanceObjectSrc, tfdiags.Diagnostics) {
func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Interface, src *states.ResourceInstanceObjectSrc, currentSchema providers.Schema) (*states.ResourceInstanceObjectSrc, tfdiags.Diagnostics) {
if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
// We only do state upgrading for managed resources.
// This was a part of the normal workflow in older versions and
@ -41,16 +40,16 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
// Legacy flatmap state is already taken care of during conversion.
// If the schema version is be changed, then allow the provider to handle
// removed attributes.
if len(src.AttrsJSON) > 0 && src.SchemaVersion == currentVersion {
src.AttrsJSON = stripRemovedStateAttributes(src.AttrsJSON, currentSchema.ImpliedType())
if len(src.AttrsJSON) > 0 && src.SchemaVersion == uint64(currentSchema.Version) {
src.AttrsJSON = stripRemovedStateAttributes(src.AttrsJSON, currentSchema.Body.ImpliedType())
}
stateIsFlatmap := len(src.AttrsJSON) == 0
// TODO: This should eventually use a proper FQN.
providerType := addr.Resource.Resource.ImpliedProvider()
if src.SchemaVersion > currentVersion {
log.Printf("[TRACE] upgradeResourceState: can't downgrade state for %s from version %d to %d", addr, src.SchemaVersion, currentVersion)
if src.SchemaVersion > uint64(currentSchema.Version) {
log.Printf("[TRACE] upgradeResourceState: can't downgrade state for %s from version %d to %d", addr, src.SchemaVersion, currentSchema.Version)
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -69,10 +68,10 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
// v0.12, this also includes translating from legacy flatmap to new-style
// representation, since only the provider has enough information to
// understand a flatmap built against an older schema.
if src.SchemaVersion != currentVersion {
log.Printf("[TRACE] upgradeResourceState: upgrading state for %s from version %d to %d using provider %q", addr, src.SchemaVersion, currentVersion, providerType)
if src.SchemaVersion != uint64(currentSchema.Version) {
log.Printf("[TRACE] upgradeResourceState: upgrading state for %s from version %d to %d using provider %q", addr, src.SchemaVersion, currentSchema.Version, providerType)
} else {
log.Printf("[TRACE] upgradeResourceState: schema version of %s is still %d; calling provider %q for any other minor fixups", addr, currentVersion, providerType)
log.Printf("[TRACE] upgradeResourceState: schema version of %s is still %d; calling provider %q for any other minor fixups", addr, currentSchema.Version, providerType)
}
req := providers.UpgradeResourceStateRequest{
@ -111,7 +110,7 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
// marshaling/unmarshaling of the new value, but we'll check it here
// anyway for robustness, e.g. for in-process providers.
newValue := resp.UpgradedState
if errs := newValue.Type().TestConformance(currentSchema.ImpliedType()); len(errs) > 0 {
if errs := newValue.Type().TestConformance(currentSchema.Body.ImpliedType()); len(errs) > 0 {
for _, err := range errs {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -132,7 +131,7 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
)
},
newValue,
currentSchema,
currentSchema.Body,
)
diags = diags.Append(writeOnlyDiags)
@ -140,7 +139,7 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
return nil, diags
}
new, err := src.CompleteUpgrade(newValue, currentSchema.ImpliedType(), uint64(currentVersion))
new, err := src.CompleteUpgrade(newValue, currentSchema.Body.ImpliedType(), uint64(currentSchema.Version))
if err != nil {
// We already checked for type conformance above, so getting into this
// codepath should be rare and is probably a bug somewhere under CompleteUpgrade.
@ -153,6 +152,83 @@ func upgradeResourceState(addr addrs.AbsResourceInstance, provider providers.Int
return new, diags
}
func upgradeResourceIdentity(addr addrs.AbsResourceInstance, provider providers.Interface, src *states.ResourceInstanceObjectSrc, currentSchema providers.Schema) (*states.ResourceInstanceObjectSrc, tfdiags.Diagnostics) {
// TODO: This should eventually use a proper FQN.
providerType := addr.Resource.Resource.ImpliedProvider()
if src.IdentitySchemaVersion > uint64(currentSchema.IdentityVersion) {
log.Printf("[TRACE] upgradeResourceIdentity: can't downgrade identity for %s from version %d to %d", addr, src.IdentitySchemaVersion, currentSchema.IdentityVersion)
var diags tfdiags.Diagnostics
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Resource instance managed by newer provider version",
// This is not a very good error message, but we don't retain enough
// information in state to give good feedback on what provider
// version might be required here. :(
fmt.Sprintf("The current state of %s was created by a newer provider version than is currently selected. Upgrade the %s provider to work with this state.", addr, providerType),
))
return nil, diags
}
// We don't need to do anything if the identity schema version is already up-to-date.
if src.IdentitySchemaVersion == uint64(currentSchema.IdentityVersion) {
return src, nil
}
req := providers.UpgradeResourceIdentityRequest{
TypeName: addr.Resource.Resource.Type,
// TODO: The internal schema version representations are all using
// uint64 instead of int64, but unsigned integers aren't friendly
// to all protobuf target languages so in practice we use int64
// on the wire. In future we will change all of our internal
// representations to int64 too.
Version: int64(src.SchemaVersion),
RawIdentityJSON: src.IdentityJSON,
}
resp := provider.UpgradeResourceIdentity(req)
diags := resp.Diagnostics
if diags.HasErrors() {
return nil, diags
}
if !resp.UpgradedIdentity.IsWhollyKnown() {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid resource identity upgrade",
fmt.Sprintf("The %s provider upgraded the identity for %s from a previous version, but produced an invalid result: The returned state contains unknown values.", providerType, addr),
))
return nil, diags
}
newIdentity := resp.UpgradedIdentity
newType := newIdentity.Type()
currentType := currentSchema.Identity.ImpliedType()
if errs := newType.TestConformance(currentType); len(errs) > 0 {
for _, err := range errs {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Invalid resource identity upgrade",
fmt.Sprintf("The %s provider upgraded the identity for %s from a previous version, but produced an invalid result: %s.", providerType, addr, tfdiags.FormatError(err)),
))
}
return nil, diags
}
new, err := src.CompleteIdentityUpgrade(newIdentity, currentSchema)
if err != nil {
// We already checked for type conformance above, so getting into this
// codepath should be rare and is probably a bug somewhere under CompleteUpgrade.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to encode result of resource identity upgrade",
fmt.Sprintf("Failed to encode state for %s after resource identity schema upgrade: %s.", addr, tfdiags.FormatError(err)),
))
}
return new, diags
}
// stripRemovedStateAttributes deletes any attributes no longer present in the
// schema, so that the json can be correctly decoded.
func stripRemovedStateAttributes(state []byte, ty cty.Type) []byte {

View file

@ -9,7 +9,6 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/lang/langrefs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -28,20 +27,20 @@ func validateSelfRef(addr addrs.Referenceable, config hcl.Body, providerSchema p
addrStrs = append(addrStrs, tAddr.ContainingResource().String())
}
var schema *configschema.Block
var schema providers.Schema
switch tAddr := addr.(type) {
case addrs.Resource:
schema, _ = providerSchema.SchemaForResourceAddr(tAddr)
schema = providerSchema.SchemaForResourceAddr(tAddr)
case addrs.ResourceInstance:
schema, _ = providerSchema.SchemaForResourceAddr(tAddr.ContainingResource())
schema = providerSchema.SchemaForResourceAddr(tAddr.ContainingResource())
}
if schema == nil {
if schema.Body == nil {
diags = diags.Append(fmt.Errorf("no schema available for %s to validate for self-references; this is a bug in Terraform and should be reported", addr))
return diags
}
refs, _ := langrefs.ReferencesInBlock(addrs.ParseRef, config, schema)
refs, _ := langrefs.ReferencesInBlock(addrs.ParseRef, config, schema.Body)
for _, ref := range refs {
for _, addrStr := range addrStrs {
if ref.Subject.String() == addrStr {