2024-09-05 05:29:15 -04:00
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package testing
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/tfdiags"
)
// resource is an interface that represents a resource that can be managed by
// the mock provider defined in this package.
type resource interface {
// Read reads the current state of the resource from the store.
Read ( request providers . ReadResourceRequest , store * ResourceStore ) providers . ReadResourceResponse
// Plan plans the resource for creation.
Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) providers . PlanResourceChangeResponse
// Apply applies the planned changes to the resource.
Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) providers . ApplyResourceChangeResponse
}
func getResource ( name string ) resource {
switch name {
case "testing_resource" :
return & testingResource { }
case "testing_deferred_resource" :
return & deferredResource { }
case "testing_failed_resource" :
return & failedResource { }
case "testing_blocked_resource" :
return & blockedResource { }
2025-04-02 09:58:42 -04:00
case "testing_write_only_resource" :
return & writeOnlyResource { }
2025-03-11 09:33:32 -04:00
case "testing_resource_with_identity" :
return & testingResourceWithIdentity { }
2024-09-05 05:29:15 -04:00
default :
panic ( "unknown resource: " + name )
}
}
var (
_ resource = ( * testingResource ) ( nil )
_ resource = ( * deferredResource ) ( nil )
_ resource = ( * failedResource ) ( nil )
_ resource = ( * blockedResource ) ( nil )
2025-04-02 09:58:42 -04:00
_ resource = ( * writeOnlyResource ) ( nil )
2025-03-11 09:33:32 -04:00
_ resource = ( * testingResourceWithIdentity ) ( nil )
2024-09-05 05:29:15 -04:00
)
// testingResource is a simple resource that can be managed by the mock provider
// defined in this package.
type testingResource struct { }
func ( t * testingResource ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
2025-03-11 15:58:44 -04:00
response . NewState = cty . NullVal ( TestingResourceSchema . Body . ImpliedType ( ) )
2024-09-05 05:29:15 -04:00
}
return
}
func ( t * testingResource ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
response . PlannedState = request . ProposedNewState
return
}
response . PlannedState = planEnsureId ( request . ProposedNewState )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( t * testingResource ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
response . NewState = request . PlannedState
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
response . NewState = value
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
// deferredResource is a resource that can defer itself based on the provided
// configuration.
type deferredResource struct { }
func ( d * deferredResource ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
2025-03-11 15:58:44 -04:00
response . NewState = cty . NullVal ( DeferredResourceSchema . Body . ImpliedType ( ) )
2024-09-05 05:29:15 -04:00
}
return
}
func ( d * deferredResource ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
if deferred := request . PriorState . GetAttr ( "deferred" ) ; ! deferred . IsNull ( ) && deferred . IsKnown ( ) && deferred . True ( ) {
response . Deferred = & providers . Deferred {
Reason : providers . DeferredReasonResourceConfigUnknown ,
}
}
response . PlannedState = request . ProposedNewState
return
}
response . PlannedState = planEnsureId ( request . ProposedNewState )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "deferredResource error" , err . Error ( ) ) )
return
}
if deferred := response . PlannedState . GetAttr ( "deferred" ) ; ! deferred . IsNull ( ) && deferred . IsKnown ( ) && deferred . True ( ) {
response . Deferred = & providers . Deferred {
Reason : providers . DeferredReasonResourceConfigUnknown ,
}
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( d * deferredResource ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
response . NewState = request . PlannedState
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "deferredResource error" , err . Error ( ) ) )
return
}
response . NewState = value
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
// failedResource is a resource that can be set to fail during Plan or Apply.
type failedResource struct { }
func ( f * failedResource ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
2025-03-11 15:58:44 -04:00
response . NewState = cty . NullVal ( FailedResourceSchema . Body . ImpliedType ( ) )
2024-09-05 05:29:15 -04:00
}
return
}
func ( f * failedResource ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
response . PlannedState = request . ProposedNewState
if attr := request . PriorState . GetAttr ( "fail_plan" ) ; ! attr . IsNull ( ) && attr . IsKnown ( ) && attr . True ( ) {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "failedResource error" , "failed during plan" ) )
return
}
return
}
response . PlannedState = planEnsureId ( request . ProposedNewState )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "failedResource error" , err . Error ( ) ) )
return
}
setUnknown ( response . PlannedState , "fail_apply" )
setUnknown ( response . PlannedState , "fail_plan" )
if attr := response . PlannedState . GetAttr ( "fail_plan" ) ; ! attr . IsNull ( ) && attr . IsKnown ( ) && attr . True ( ) {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "failedResource error" , "failed during plan" ) )
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( f * failedResource ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
if attr := request . PriorState . GetAttr ( "fail_apply" ) ; ! attr . IsNull ( ) && attr . IsKnown ( ) && attr . True ( ) {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "failedResource error" , "failed during apply" ) )
return
}
response . NewState = request . PlannedState
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
setKnown ( value , "fail_apply" , cty . False )
setKnown ( value , "fail_plan" , cty . False )
if attr := value . GetAttr ( "fail_apply" ) ; ! attr . IsNull ( ) && attr . IsKnown ( ) && attr . True ( ) {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "failedResource error" , "failed during apply" ) )
return
}
response . NewState = value
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
2024-09-05 06:10:24 -04:00
// blockedResource is a resource that accepts a list of required resource ids
// and will fail to apply if those resources don't exist. They will also fail to
// destroy if the resources do not exist - this ensures they have to be created
// and destroyed in the correct order.
2024-09-05 05:29:15 -04:00
type blockedResource struct { }
func ( b * blockedResource ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
2025-03-11 15:58:44 -04:00
response . NewState = cty . NullVal ( DeferredResourceSchema . Body . ImpliedType ( ) )
2024-09-05 05:29:15 -04:00
}
return
}
func ( b * blockedResource ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
response . PlannedState = request . ProposedNewState
return
}
response . PlannedState = planEnsureId ( request . ProposedNewState )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( b * blockedResource ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
if required := request . PriorState . GetAttr ( "required_resources" ) ; ! required . IsNull ( ) && required . IsKnown ( ) {
for _ , id := range required . AsValueSlice ( ) {
2024-09-05 06:10:24 -04:00
if _ , exists := store . Get ( id . AsString ( ) ) ; ! exists {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "blockedResource error" , fmt . Sprintf ( "required resource %q does not exists, so can't destroy self" , id . AsString ( ) ) ) )
2024-09-05 05:29:15 -04:00
return
}
}
}
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
response . NewState = request . PlannedState
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
if required := value . GetAttr ( "required_resources" ) ; ! required . IsNull ( ) && required . IsKnown ( ) {
for _ , id := range required . AsValueSlice ( ) {
if _ , exists := store . Get ( id . AsString ( ) ) ; ! exists {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "blockedResource error" , fmt . Sprintf ( "required resource %q does not exist, so can't apply self" , id . AsString ( ) ) ) )
return
}
}
}
response . NewState = value
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
2025-04-02 09:58:42 -04:00
// writeOnlyResource is the same as testingResource but it includes an extra
// write-only attribute.
type writeOnlyResource struct { }
func ( w * writeOnlyResource ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
response . NewState = cty . NullVal ( WriteOnlyResourceSchema . Body . ImpliedType ( ) )
}
return
}
func ( w * writeOnlyResource ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
response . PlannedState = request . ProposedNewState
return
}
response . PlannedState = setNull ( planEnsureId ( request . ProposedNewState ) , "write_only" )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( w * writeOnlyResource ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
response . NewState = request . PlannedState
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResource error" , err . Error ( ) ) )
return
}
response . NewState = value
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
2025-03-11 09:33:32 -04:00
// testingResourceWithIdentity is the same as testingResource but it returns an identity.
type testingResourceWithIdentity struct { }
func ( t * testingResourceWithIdentity ) Read ( request providers . ReadResourceRequest , store * ResourceStore ) ( response providers . ReadResourceResponse ) {
id := request . PriorState . GetAttr ( "id" ) . AsString ( )
var exists bool
response . NewState , exists = store . Get ( id )
if ! exists {
response . NewState = cty . NullVal ( TestingResourceSchema . Body . ImpliedType ( ) )
response . Identity = cty . UnknownVal ( TestingResourceWithIdentitySchema . Identity . ImpliedType ( ) )
} else {
response . Identity = cty . StringVal ( "id:" + id )
}
return
}
func ( t * testingResourceWithIdentity ) Plan ( request providers . PlanResourceChangeRequest , store * ResourceStore ) ( response providers . PlanResourceChangeResponse ) {
if request . ProposedNewState . IsNull ( ) {
response . PlannedState = request . ProposedNewState
return
}
response . PlannedState = planEnsureId ( request . ProposedNewState )
response . PlannedIdentity = cty . ObjectVal ( map [ string ] cty . Value {
"id" : cty . StringVal ( "id:" + response . PlannedState . GetAttr ( "id" ) . AsString ( ) ) ,
} )
replace , err := validateId ( response . PlannedState , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResourceWithIdentity error" , err . Error ( ) ) )
return
}
if replace {
response . RequiresReplace = [ ] cty . Path { cty . GetAttrPath ( "id" ) }
}
return
}
func ( t * testingResourceWithIdentity ) Apply ( request providers . ApplyResourceChangeRequest , store * ResourceStore ) ( response providers . ApplyResourceChangeResponse ) {
if request . PlannedState . IsNull ( ) {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
response . NewState = request . PlannedState
return
}
value := applyEnsureId ( request . PlannedState )
replace , err := validateId ( value , request . PriorState , store )
if err != nil {
response . Diagnostics = append ( response . Diagnostics , tfdiags . Sourceless ( tfdiags . Error , "testingResourceWithIdentity error" , err . Error ( ) ) )
return
}
response . NewState = value
response . NewIdentity = request . PlannedIdentity
if replace {
store . Delete ( request . PriorState . GetAttr ( "id" ) . AsString ( ) )
}
store . Set ( response . NewState . GetAttr ( "id" ) . AsString ( ) , response . NewState )
return
}
2024-09-05 05:29:15 -04:00
func validateId ( target cty . Value , prior cty . Value , store * ResourceStore ) ( bool , error ) {
if prior . IsNull ( ) {
// Then we're creating a resource, we want to make sure we're not
// creating a resource with an existing ID.
if id := target . GetAttr ( "id" ) ; id . IsKnown ( ) {
if _ , exists := store . Get ( id . AsString ( ) ) ; exists {
return false , fmt . Errorf ( "resource with id %q already exists" , id . AsString ( ) )
}
}
return false , nil
}
if attr := target . GetAttr ( "id" ) ; ! attr . IsKnown ( ) {
// Then the attribute has been set to unknown, which means we're
// potentially changing the id.
return true , nil
}
// Now, we know that the ID is known in both the prior and target states.
if result := prior . GetAttr ( "id" ) . Equals ( target . GetAttr ( "id" ) ) ; result . False ( ) {
// Then the ID value is changing, so we need to delete the old ID
// and create the new one.
return true , nil
}
return false , nil
}
func planEnsureId ( value cty . Value ) cty . Value {
return setUnknown ( value , "id" )
}
func applyEnsureId ( value cty . Value ) cty . Value {
return setKnown ( value , "id" , cty . StringVal ( mustGenerateUUID ( ) ) )
}
func setUnknown ( value cty . Value , attr string ) cty . Value {
if v := value . GetAttr ( attr ) ; v . IsNull ( ) {
vals := value . AsValueMap ( )
vals [ attr ] = cty . UnknownVal ( cty . String )
return cty . ObjectVal ( vals )
}
return value
}
func setKnown ( value cty . Value , attr string , attrValue cty . Value ) cty . Value {
if v := value . GetAttr ( attr ) ; ! v . IsKnown ( ) {
vals := value . AsValueMap ( )
vals [ attr ] = attrValue
return cty . ObjectVal ( vals )
}
return value
}
2025-04-02 09:58:42 -04:00
func setNull ( value cty . Value , attr string ) cty . Value {
if v := value . GetAttr ( attr ) ; ! v . IsKnown ( ) {
vals := value . AsValueMap ( )
vals [ attr ] = cty . NullVal ( v . Type ( ) )
return cty . ObjectVal ( vals )
}
return value
}