522 lines
16 KiB
Go
522 lines
16 KiB
Go
package terraform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
ctyjson "github.com/zclconf/go-cty/cty/json"
|
|
|
|
"github.com/hashicorp/terraform/configs/hcl2shim"
|
|
"github.com/hashicorp/terraform/providers"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
)
|
|
|
|
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{}
|
|
|
|
GetSchemaCalled bool
|
|
GetSchemaReturn *ProviderSchema // This is using ProviderSchema directly rather than providers.GetSchemaResponse for compatibility with old tests
|
|
|
|
PrepareProviderConfigCalled bool
|
|
PrepareProviderConfigResponse providers.PrepareProviderConfigResponse
|
|
PrepareProviderConfigRequest providers.PrepareProviderConfigRequest
|
|
PrepareProviderConfigFn func(providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse
|
|
|
|
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
|
|
ConfigureNewFn func(providers.ConfigureRequest) providers.ConfigureResponse // Named ConfigureNewFn so we can still have the legacy ConfigureFn declared below
|
|
|
|
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
|
|
// Legacy return type for existing tests, which will be shimmed into an
|
|
// ImportResourceStateResponse if set
|
|
ImportStateReturn []*InstanceState
|
|
|
|
ReadDataSourceCalled bool
|
|
ReadDataSourceResponse providers.ReadDataSourceResponse
|
|
ReadDataSourceRequest providers.ReadDataSourceRequest
|
|
ReadDataSourceFn func(providers.ReadDataSourceRequest) providers.ReadDataSourceResponse
|
|
|
|
CloseCalled bool
|
|
CloseError error
|
|
|
|
// 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)
|
|
}
|
|
|
|
func (p *MockProvider) GetSchema() providers.GetSchemaResponse {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
p.GetSchemaCalled = true
|
|
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.
|
|
|
|
ret := providers.GetSchemaResponse{
|
|
Provider: providers.Schema{},
|
|
DataSources: map[string]providers.Schema{},
|
|
ResourceTypes: map[string]providers.Schema{},
|
|
}
|
|
if p.GetSchemaReturn != nil {
|
|
ret.Provider.Block = p.GetSchemaReturn.Provider
|
|
for n, s := range p.GetSchemaReturn.DataSources {
|
|
ret.DataSources[n] = providers.Schema{
|
|
Block: s,
|
|
}
|
|
}
|
|
for n, s := range p.GetSchemaReturn.ResourceTypes {
|
|
ret.ResourceTypes[n] = providers.Schema{
|
|
Version: int64(p.GetSchemaReturn.ResourceTypeSchemaVersions[n]),
|
|
Block: s,
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
func (p *MockProvider) PrepareProviderConfig(r providers.PrepareProviderConfigRequest) providers.PrepareProviderConfigResponse {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
p.PrepareProviderConfigCalled = true
|
|
p.PrepareProviderConfigRequest = r
|
|
if p.PrepareProviderConfigFn != nil {
|
|
return p.PrepareProviderConfigFn(r)
|
|
}
|
|
return p.PrepareProviderConfigResponse
|
|
}
|
|
|
|
func (p *MockProvider) ValidateResourceTypeConfig(r providers.ValidateResourceTypeConfigRequest) providers.ValidateResourceTypeConfigResponse {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
p.ValidateResourceTypeConfigCalled = true
|
|
p.ValidateResourceTypeConfigRequest = r
|
|
|
|
if p.ValidateFn != nil {
|
|
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)
|
|
}
|
|
}
|
|
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()
|
|
|
|
schemas := p.getSchema()
|
|
schema := schemas.ResourceTypes[r.TypeName]
|
|
schemaType := schema.Block.ImpliedType()
|
|
|
|
p.UpgradeResourceStateCalled = true
|
|
p.UpgradeResourceStateRequest = r
|
|
|
|
if p.UpgradeResourceStateFn != nil {
|
|
return p.UpgradeResourceStateFn(r)
|
|
}
|
|
|
|
resp := p.UpgradeResourceStateResponse
|
|
|
|
if resp.UpgradedState == cty.NilVal {
|
|
switch {
|
|
case r.RawStateFlatmap != nil:
|
|
v, err := hcl2shim.HCL2ValueFromFlatmap(r.RawStateFlatmap, schemaType)
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
resp.UpgradedState = v
|
|
case len(r.RawStateJSON) > 0:
|
|
v, err := ctyjson.Unmarshal(r.RawStateJSON, schemaType)
|
|
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
return resp
|
|
}
|
|
resp.UpgradedState = v
|
|
}
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func (p *MockProvider) Configure(r providers.ConfigureRequest) providers.ConfigureResponse {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
p.ConfigureCalled = true
|
|
p.ConfigureRequest = r
|
|
|
|
if p.ConfigureFn != nil {
|
|
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)
|
|
}
|
|
return ret
|
|
}
|
|
if p.ConfigureNewFn != nil {
|
|
return p.ConfigureNewFn(r)
|
|
}
|
|
|
|
return p.ConfigureResponse
|
|
}
|
|
|
|
func (p *MockProvider) Stop() error {
|
|
// 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.
|
|
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
func (p *MockProvider) PlanResourceChange(r providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
|
|
p.Lock()
|
|
defer p.Unlock()
|
|
|
|
p.PlanResourceChangeCalled = true
|
|
p.PlanResourceChangeRequest = r
|
|
|
|
if p.DiffFn != nil {
|
|
ps := p.getSchema()
|
|
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)),
|
|
}
|
|
}
|
|
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 = r.ProposedNewState
|
|
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
|
|
}
|
|
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()
|
|
|
|
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.
|
|
providerSchema := p.getSchema()
|
|
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 == hcl2shim.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,
|
|
}
|
|
}
|
|
}
|
|
newState, err := p.ApplyFn(info, s, d)
|
|
resp := providers.ApplyResourceChangeResponse{}
|
|
if err != nil {
|
|
resp.Diagnostics = resp.Diagnostics.Append(err)
|
|
}
|
|
if newState != nil {
|
|
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())
|
|
}
|
|
resp.NewState = newVal
|
|
}
|
|
|
|
return resp
|
|
}
|
|
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()
|
|
|
|
if p.ImportStateReturn != nil {
|
|
for _, is := range p.ImportStateReturn {
|
|
if is.Attributes == nil {
|
|
is.Attributes = make(map[string]string)
|
|
}
|
|
is.Attributes["id"] = is.ID
|
|
|
|
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)
|
|
}
|
|
|
|
private, err := json.Marshal(is.Meta)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
state, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schema.ImpliedType())
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
state, err = schema.CoerceValue(state)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
p.ImportResourceStateResponse.ImportedResources = append(
|
|
p.ImportResourceStateResponse.ImportedResources,
|
|
providers.ImportedResource{
|
|
TypeName: is.Ephemeral.Type,
|
|
State: state,
|
|
Private: private,
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|