2018-08-15 13:29:46 -05:00
package terraform
import (
2018-09-05 16:35:30 -05:00
"encoding/json"
2018-08-24 18:13:50 -05:00
"fmt"
2018-08-15 13:29:46 -05:00
"sync"
2018-09-06 18:49:21 -05:00
"github.com/zclconf/go-cty/cty"
2018-08-24 18:13:50 -05:00
2018-09-07 14:19:27 -05:00
"github.com/hashicorp/terraform/config"
2018-09-06 18:49:21 -05:00
"github.com/hashicorp/terraform/config/hcl2shim"
2018-08-15 13:29:46 -05:00
"github.com/hashicorp/terraform/providers"
2018-09-06 18:49:21 -05:00
"github.com/hashicorp/terraform/tfdiags"
2018-08-15 13:29:46 -05:00
)
var _ providers . Interface = ( * MockProvider ) ( nil )
// MockProvider implements providers.Interface but mocks out all the
// calls for testing purposes.
type MockProvider struct {
sync . Mutex
// Anything you want, in case you need to store extra data with the mock.
Meta interface { }
2018-08-24 18:13:50 -05:00
GetSchemaCalled bool
GetSchemaReturn * ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests
2018-08-15 13:29:46 -05:00
ValidateProviderConfigCalled bool
ValidateProviderConfigResponse providers . ValidateProviderConfigResponse
ValidateProviderConfigRequest providers . ValidateProviderConfigRequest
ValidateProviderConfigFn func ( providers . ValidateProviderConfigRequest ) providers . ValidateProviderConfigResponse
ValidateResourceTypeConfigCalled bool
ValidateResourceTypeConfigTypeName string
ValidateResourceTypeConfigResponse providers . ValidateResourceTypeConfigResponse
ValidateResourceTypeConfigRequest providers . ValidateResourceTypeConfigRequest
ValidateResourceTypeConfigFn func ( providers . ValidateResourceTypeConfigRequest ) providers . ValidateResourceTypeConfigResponse
ValidateDataSourceConfigCalled bool
ValidateDataSourceConfigTypeName string
ValidateDataSourceConfigResponse providers . ValidateDataSourceConfigResponse
ValidateDataSourceConfigRequest providers . ValidateDataSourceConfigRequest
ValidateDataSourceConfigFn func ( providers . ValidateDataSourceConfigRequest ) providers . ValidateDataSourceConfigResponse
UpgradeResourceStateCalled bool
UpgradeResourceStateTypeName string
UpgradeResourceStateResponse providers . UpgradeResourceStateResponse
UpgradeResourceStateRequest providers . UpgradeResourceStateRequest
UpgradeResourceStateFn func ( providers . UpgradeResourceStateRequest ) providers . UpgradeResourceStateResponse
ConfigureCalled bool
ConfigureResponse providers . ConfigureResponse
ConfigureRequest providers . ConfigureRequest
2018-08-24 18:13:50 -05:00
ConfigureNewFn func ( providers . ConfigureRequest ) providers . ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below
2018-08-15 13:29:46 -05:00
StopCalled bool
StopFn func ( ) error
StopResponse error
ReadResourceCalled bool
ReadResourceResponse providers . ReadResourceResponse
ReadResourceRequest providers . ReadResourceRequest
ReadResourceFn func ( providers . ReadResourceRequest ) providers . ReadResourceResponse
PlanResourceChangeCalled bool
PlanResourceChangeResponse providers . PlanResourceChangeResponse
PlanResourceChangeRequest providers . PlanResourceChangeRequest
PlanResourceChangeFn func ( providers . PlanResourceChangeRequest ) providers . PlanResourceChangeResponse
ApplyResourceChangeCalled bool
ApplyResourceChangeResponse providers . ApplyResourceChangeResponse
ApplyResourceChangeRequest providers . ApplyResourceChangeRequest
ApplyResourceChangeFn func ( providers . ApplyResourceChangeRequest ) providers . ApplyResourceChangeResponse
ImportResourceStateCalled bool
ImportResourceStateResponse providers . ImportResourceStateResponse
ImportResourceStateRequest providers . ImportResourceStateRequest
ImportResourceStateFn func ( providers . ImportResourceStateRequest ) providers . ImportResourceStateResponse
2018-09-05 16:35:30 -05:00
// Legacy return type for existing tests, which will be shimmed into an
// ImportResourceStateResponse if set
ImportStateReturn [ ] * InstanceState
2018-08-15 13:29:46 -05:00
ReadDataSourceCalled bool
ReadDataSourceResponse providers . ReadDataSourceResponse
ReadDataSourceRequest providers . ReadDataSourceRequest
ReadDataSourceFn func ( providers . ReadDataSourceRequest ) providers . ReadDataSourceResponse
CloseCalled bool
CloseError error
2018-08-24 18:13:50 -05:00
// Legacy callbacks: if these are set, we will shim incoming calls for
// new-style methods to these old-fashioned terraform.ResourceProvider
// mock callbacks, for the benefit of older tests that were written against
// the old mock API.
ValidateFn func ( c * ResourceConfig ) ( ws [ ] string , es [ ] error )
ConfigureFn func ( c * ResourceConfig ) error
DiffFn func ( info * InstanceInfo , s * InstanceState , c * ResourceConfig ) ( * InstanceDiff , error )
ApplyFn func ( info * InstanceInfo , s * InstanceState , d * InstanceDiff ) ( * InstanceState , error )
2018-08-15 13:29:46 -05:00
}
func ( p * MockProvider ) GetSchema ( ) providers . GetSchemaResponse {
p . Lock ( )
defer p . Unlock ( )
2018-09-30 10:12:20 -05:00
p . GetSchemaCalled = true
2018-09-07 18:18:06 -05:00
return p . getSchema ( )
}
func ( p * MockProvider ) getSchema ( ) providers . GetSchemaResponse {
// This version of getSchema doesn't do any locking, so it's suitable to
// call from other methods of this mock as long as they are already
// holding the lock.
2018-08-15 13:29:46 -05:00
2018-08-24 18:13:50 -05:00
ret := providers . GetSchemaResponse {
2018-09-30 10:12:20 -05:00
Provider : providers . Schema { } ,
2018-08-24 18:13:50 -05:00
DataSources : map [ string ] providers . Schema { } ,
ResourceTypes : map [ string ] providers . Schema { } ,
}
2018-09-30 10:12:20 -05:00
if p . GetSchemaReturn != nil {
ret . Provider . Block = p . GetSchemaReturn . Provider
for n , s := range p . GetSchemaReturn . DataSources {
ret . DataSources [ n ] = providers . Schema {
Block : s ,
}
2018-08-24 18:13:50 -05:00
}
2018-09-30 10:12:20 -05:00
for n , s := range p . GetSchemaReturn . ResourceTypes {
ret . ResourceTypes [ n ] = providers . Schema {
Block : s ,
}
2018-08-24 18:13:50 -05:00
}
}
2018-09-20 17:22:42 -05:00
2018-08-24 18:13:50 -05:00
return ret
2018-08-15 13:29:46 -05:00
}
func ( p * MockProvider ) ValidateProviderConfig ( r providers . ValidateProviderConfigRequest ) providers . ValidateProviderConfigResponse {
p . Lock ( )
defer p . Unlock ( )
p . ValidateProviderConfigCalled = true
p . ValidateProviderConfigRequest = r
if p . ValidateProviderConfigFn != nil {
return p . ValidateProviderConfigFn ( r )
}
return p . ValidateProviderConfigResponse
}
func ( p * MockProvider ) ValidateResourceTypeConfig ( r providers . ValidateResourceTypeConfigRequest ) providers . ValidateResourceTypeConfigResponse {
p . Lock ( )
defer p . Unlock ( )
p . ValidateResourceTypeConfigCalled = true
p . ValidateResourceTypeConfigRequest = r
2018-08-24 18:13:50 -05:00
if p . ValidateFn != nil {
2018-09-07 18:21:49 -05:00
resp := p . getSchema ( )
schema := resp . Provider . Block
rc := NewResourceConfigShimmed ( r . Config , schema )
warns , errs := p . ValidateFn ( rc )
ret := providers . ValidateResourceTypeConfigResponse { }
for _ , warn := range warns {
ret . Diagnostics = ret . Diagnostics . Append ( tfdiags . SimpleWarning ( warn ) )
}
for _ , err := range errs {
ret . Diagnostics = ret . Diagnostics . Append ( err )
2018-08-24 18:13:50 -05:00
}
}
2018-08-15 13:29:46 -05:00
if p . ValidateResourceTypeConfigFn != nil {
return p . ValidateResourceTypeConfigFn ( r )
}
return p . ValidateResourceTypeConfigResponse
}
func ( p * MockProvider ) ValidateDataSourceConfig ( r providers . ValidateDataSourceConfigRequest ) providers . ValidateDataSourceConfigResponse {
p . Lock ( )
defer p . Unlock ( )
p . ValidateDataSourceConfigCalled = true
p . ValidateDataSourceConfigRequest = r
if p . ValidateDataSourceConfigFn != nil {
return p . ValidateDataSourceConfigFn ( r )
}
return p . ValidateDataSourceConfigResponse
}
func ( p * MockProvider ) UpgradeResourceState ( r providers . UpgradeResourceStateRequest ) providers . UpgradeResourceStateResponse {
p . Lock ( )
defer p . Unlock ( )
p . UpgradeResourceStateCalled = true
p . UpgradeResourceStateRequest = r
if p . UpgradeResourceStateFn != nil {
return p . UpgradeResourceStateFn ( r )
}
return p . UpgradeResourceStateResponse
}
func ( p * MockProvider ) Configure ( r providers . ConfigureRequest ) providers . ConfigureResponse {
p . Lock ( )
defer p . Unlock ( )
p . ConfigureCalled = true
p . ConfigureRequest = r
if p . ConfigureFn != nil {
2018-09-07 18:18:06 -05:00
resp := p . getSchema ( )
schema := resp . Provider . Block
rc := NewResourceConfigShimmed ( r . Config , schema )
ret := providers . ConfigureResponse { }
err := p . ConfigureFn ( rc )
if err != nil {
ret . Diagnostics = ret . Diagnostics . Append ( err )
2018-08-24 18:13:50 -05:00
}
2018-09-07 18:18:06 -05:00
return ret
2018-08-24 18:13:50 -05:00
}
if p . ConfigureNewFn != nil {
return p . ConfigureNewFn ( r )
2018-08-15 13:29:46 -05:00
}
return p . ConfigureResponse
}
func ( p * MockProvider ) Stop ( ) error {
2018-09-25 17:24:42 -05:00
// We intentionally don't lock in this one because the whole point of this
// method is to be called concurrently with another operation that can
// be cancelled. The provider itself is responsible for handling
// any concurrency concerns in this case.
2018-08-15 13:29:46 -05:00
p . StopCalled = true
if p . StopFn != nil {
return p . StopFn ( )
}
return p . StopResponse
}
func ( p * MockProvider ) ReadResource ( r providers . ReadResourceRequest ) providers . ReadResourceResponse {
p . Lock ( )
defer p . Unlock ( )
p . ReadResourceCalled = true
p . ReadResourceRequest = r
if p . ReadResourceFn != nil {
return p . ReadResourceFn ( r )
}
2018-09-20 17:22:42 -05:00
// make sure the NewState fits the schema
newState , err := p . GetSchemaReturn . ResourceTypes [ r . TypeName ] . CoerceValue ( p . ReadResourceResponse . NewState )
if err != nil {
panic ( err )
}
resp := p . ReadResourceResponse
resp . NewState = newState
return resp
2018-08-15 13:29:46 -05:00
}
func ( p * MockProvider ) PlanResourceChange ( r providers . PlanResourceChangeRequest ) providers . PlanResourceChangeResponse {
2018-09-10 16:41:07 -05:00
p . Lock ( )
defer p . Unlock ( )
2018-08-15 13:29:46 -05:00
p . PlanResourceChangeCalled = true
p . PlanResourceChangeRequest = r
2018-08-24 18:13:50 -05:00
if p . DiffFn != nil {
2018-09-07 18:18:06 -05:00
ps := p . getSchema ( )
2018-09-06 18:49:21 -05:00
if ps . ResourceTypes == nil || ps . ResourceTypes [ r . TypeName ] . Block == nil {
return providers . PlanResourceChangeResponse {
Diagnostics : tfdiags . Diagnostics ( nil ) . Append ( fmt . Printf ( "mock provider has no schema for resource type %s" , r . TypeName ) ) ,
}
2018-08-24 18:13:50 -05:00
}
2018-09-06 18:49:21 -05:00
schema := ps . ResourceTypes [ r . TypeName ] . Block
info := & InstanceInfo {
Type : r . TypeName ,
}
priorState := NewInstanceStateShimmedFromValue ( r . PriorState , 0 )
cfg := NewResourceConfigShimmed ( r . Config , schema )
legacyDiff , err := p . DiffFn ( info , priorState , cfg )
var res providers . PlanResourceChangeResponse
res . PlannedState = cty . NullVal ( schema . ImpliedType ( ) ) // mimic how an absent value would arrive over the GRPC channel
if err != nil {
res . Diagnostics = res . Diagnostics . Append ( err )
}
if legacyDiff != nil {
newVal , err := legacyDiff . ApplyToValue ( r . PriorState , schema )
if err != nil {
res . Diagnostics = res . Diagnostics . Append ( err )
}
res . PlannedState = newVal
var requiresNew [ ] string
for attr , d := range legacyDiff . Attributes {
if d . RequiresNew {
requiresNew = append ( requiresNew , attr )
}
}
requiresReplace , err := hcl2shim . RequiresReplace ( requiresNew , schema . ImpliedType ( ) )
if err != nil {
res . Diagnostics = res . Diagnostics . Append ( err )
}
res . RequiresReplace = requiresReplace
}
return res
2018-08-24 18:13:50 -05:00
}
2018-08-15 13:29:46 -05:00
if p . PlanResourceChangeFn != nil {
return p . PlanResourceChangeFn ( r )
}
return p . PlanResourceChangeResponse
}
func ( p * MockProvider ) ApplyResourceChange ( r providers . ApplyResourceChangeRequest ) providers . ApplyResourceChangeResponse {
p . Lock ( )
p . ApplyResourceChangeCalled = true
p . ApplyResourceChangeRequest = r
p . Unlock ( )
2018-09-07 14:19:27 -05:00
if p . ApplyFn != nil {
// ApplyFn is a special callback fashioned after our old provider
// interface, which expected to be given an actual diff rather than
// separate old/new values to apply. Therefore we need to approximate
// a diff here well enough that _most_ of our legacy ApplyFns in old
// tests still see the behavior they are expecting. New tests should
// not use this, and should instead use ApplyResourceChangeFn directly.
2018-09-07 18:18:06 -05:00
providerSchema := p . getSchema ( )
2018-09-07 14:19:27 -05:00
schema , ok := providerSchema . ResourceTypes [ r . TypeName ]
if ! ok {
return providers . ApplyResourceChangeResponse {
Diagnostics : tfdiags . Diagnostics ( nil ) . Append ( fmt . Errorf ( "no mocked schema available for resource type %s" , r . TypeName ) ) ,
}
}
info := & InstanceInfo {
Type : r . TypeName ,
}
priorVal := r . PriorState
plannedVal := r . PlannedState
priorMap := hcl2shim . FlatmapValueFromHCL2 ( priorVal )
plannedMap := hcl2shim . FlatmapValueFromHCL2 ( plannedVal )
s := NewInstanceStateShimmedFromValue ( priorVal , 0 )
d := & InstanceDiff {
Attributes : make ( map [ string ] * ResourceAttrDiff ) ,
}
if plannedMap == nil { // destroying, then
d . Destroy = true
// Destroy diffs don't have any attribute diffs
} else {
if priorMap == nil { // creating, then
// We'll just make an empty prior map to make things easier below.
priorMap = make ( map [ string ] string )
}
for k , new := range plannedMap {
old := priorMap [ k ]
newComputed := false
if new == config . UnknownVariableValue {
new = ""
newComputed = true
}
d . Attributes [ k ] = & ResourceAttrDiff {
Old : old ,
New : new ,
NewComputed : newComputed ,
Type : DiffAttrInput , // not generally used in tests, so just hard-coded
}
}
// Also need any attributes that were removed in "planned"
for k , old := range priorMap {
if _ , ok := plannedMap [ k ] ; ok {
continue
}
d . Attributes [ k ] = & ResourceAttrDiff {
Old : old ,
NewRemoved : true ,
Type : DiffAttrInput ,
}
}
2018-08-24 18:13:50 -05:00
}
2018-09-07 14:19:27 -05:00
newState , err := p . ApplyFn ( info , s , d )
resp := providers . ApplyResourceChangeResponse { }
if err != nil {
resp . Diagnostics = resp . Diagnostics . Append ( err )
}
2018-09-11 18:38:14 -05:00
if newState != nil {
2018-09-20 20:28:27 -05:00
var newVal cty . Value
if newState != nil {
var err error
newVal , err = newState . AttrsAsObjectValue ( schema . Block . ImpliedType ( ) )
if err != nil {
resp . Diagnostics = resp . Diagnostics . Append ( err )
}
} else {
// If apply returned a nil new state then that's the old way to
// indicate that the object was destroyed. Our new interface calls
// for that to be signalled as a null value.
newVal = cty . NullVal ( schema . Block . ImpliedType ( ) )
2018-09-11 18:38:14 -05:00
}
2018-09-20 20:28:27 -05:00
resp . NewState = newVal
2018-09-07 14:19:27 -05:00
}
return resp
2018-08-24 18:13:50 -05:00
}
2018-08-15 13:29:46 -05:00
if p . ApplyResourceChangeFn != nil {
return p . ApplyResourceChangeFn ( r )
}
return p . ApplyResourceChangeResponse
}
func ( p * MockProvider ) ImportResourceState ( r providers . ImportResourceStateRequest ) providers . ImportResourceStateResponse {
p . Lock ( )
defer p . Unlock ( )
2018-09-05 16:35:30 -05:00
if p . ImportStateReturn != nil {
for _ , is := range p . ImportStateReturn {
2018-09-06 15:27:05 -05:00
if is . Attributes == nil {
is . Attributes = make ( map [ string ] string )
}
2018-09-05 16:35:30 -05:00
is . Attributes [ "id" ] = is . ID
2018-09-20 17:22:42 -05:00
typeName := is . Ephemeral . Type
// Use the requested type if the resource has no type of it's own.
// We still return the empty type, which will error, but this prevents a panic.
if typeName == "" {
typeName = r . TypeName
}
schema := p . GetSchemaReturn . ResourceTypes [ typeName ]
if schema == nil {
panic ( "no schema found for " + typeName )
}
2018-09-05 16:35:30 -05:00
private , err := json . Marshal ( is . Meta )
if err != nil {
panic ( err )
}
state , err := hcl2shim . HCL2ValueFromFlatmap ( is . Attributes , schema . ImpliedType ( ) )
if err != nil {
panic ( err )
}
2018-09-20 17:22:42 -05:00
state , err = schema . CoerceValue ( state )
if err != nil {
panic ( err )
}
2018-09-05 16:35:30 -05:00
p . ImportResourceStateResponse . ImportedResources = append (
p . ImportResourceStateResponse . ImportedResources ,
providers . ImportedResource {
2018-09-20 17:22:42 -05:00
TypeName : is . Ephemeral . Type ,
2018-09-05 16:35:30 -05:00
State : state ,
Private : private ,
} )
}
}
2018-08-15 13:29:46 -05:00
p . ImportResourceStateCalled = true
p . ImportResourceStateRequest = r
if p . ImportResourceStateFn != nil {
return p . ImportResourceStateFn ( r )
}
return p . ImportResourceStateResponse
}
func ( p * MockProvider ) ReadDataSource ( r providers . ReadDataSourceRequest ) providers . ReadDataSourceResponse {
p . Lock ( )
defer p . Unlock ( )
p . ReadDataSourceCalled = true
p . ReadDataSourceRequest = r
if p . ReadDataSourceFn != nil {
return p . ReadDataSourceFn ( r )
}
return p . ReadDataSourceResponse
}
func ( p * MockProvider ) Close ( ) error {
p . CloseCalled = true
return p . CloseError
}