terraform/helper/resource/state_shim.go

223 lines
6.1 KiB
Go

package resource
import (
"encoding/json"
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/configs/hcl2shim"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
)
// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) {
state := terraform.NewState()
// in the odd case of a nil state, let the helper packages handle it
if newState == nil {
return nil, nil
}
for _, newMod := range newState.Modules {
mod := state.AddModule(newMod.Addr)
for name, out := range newMod.OutputValues {
outputType := ""
val := hcl2shim.ConfigValueFromHCL2(out.Value)
ty := out.Value.Type()
switch {
case ty == cty.String:
outputType = "string"
case ty.IsTupleType() || ty.IsListType():
outputType = "list"
case ty.IsMapType():
outputType = "map"
}
mod.Outputs[name] = &terraform.OutputState{
Type: outputType,
Value: val,
Sensitive: out.Sensitive,
}
}
for _, res := range newMod.Resources {
resType := res.Addr.Resource.Type
providerType := res.ProviderConfig.Provider.Type
resource := getResource(providers, providerType, res.Addr.Resource)
for key, i := range res.Instances {
resState := &terraform.ResourceState{
Type: resType,
Provider: legacyProviderConfigString(res.ProviderConfig),
}
// We should always have a Current instance here, but be safe about checking.
if i.Current != nil {
flatmap, err := shimmedAttributes(i.Current, resource)
if err != nil {
return nil, fmt.Errorf("error decoding state for %q: %s", resType, err)
}
var meta map[string]interface{}
if i.Current.Private != nil {
err := json.Unmarshal(i.Current.Private, &meta)
if err != nil {
return nil, err
}
}
resState.Primary = &terraform.InstanceState{
ID: flatmap["id"],
Attributes: flatmap,
Tainted: i.Current.Status == states.ObjectTainted,
Meta: meta,
}
if i.Current.SchemaVersion != 0 {
if resState.Primary.Meta == nil {
resState.Primary.Meta = map[string]interface{}{}
}
resState.Primary.Meta["schema_version"] = i.Current.SchemaVersion
}
for _, dep := range i.Current.DependsOn {
resState.Dependencies = append(resState.Dependencies, dep.String())
}
// convert the indexes to the old style flapmap indexes
idx := ""
switch key.(type) {
case addrs.IntKey:
// don't add numeric index values to resources with a count of 0
if len(res.Instances) > 1 {
idx = fmt.Sprintf(".%d", key)
}
case addrs.StringKey:
idx = "." + key.String()
}
mod.Resources[res.Addr.Resource.String()+idx] = resState
}
// add any deposed instances
for _, dep := range i.Deposed {
flatmap, err := shimmedAttributes(dep, resource)
if err != nil {
return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err)
}
var meta map[string]interface{}
if dep.Private != nil {
err := json.Unmarshal(dep.Private, &meta)
if err != nil {
return nil, err
}
}
deposed := &terraform.InstanceState{
ID: flatmap["id"],
Attributes: flatmap,
Tainted: dep.Status == states.ObjectTainted,
Meta: meta,
}
if dep.SchemaVersion != 0 {
deposed.Meta = map[string]interface{}{
"schema_version": dep.SchemaVersion,
}
}
resState.Deposed = append(resState.Deposed, deposed)
}
}
}
}
return state, nil
}
func getResource(providers map[string]terraform.ResourceProvider, providerName string, addr addrs.Resource) *schema.Resource {
p := providers[providerName]
if p == nil {
panic(fmt.Sprintf("provider %q not found in test step", providerName))
}
// this is only for tests, so should only see schema.Providers
provider := p.(*schema.Provider)
switch addr.Mode {
case addrs.ManagedResourceMode:
resource := provider.ResourcesMap[addr.Type]
if resource != nil {
return resource
}
case addrs.DataResourceMode:
resource := provider.DataSourcesMap[addr.Type]
if resource != nil {
return resource
}
}
panic(fmt.Sprintf("resource %s not found in test step", addr.Type))
}
func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) {
flatmap := instance.AttrsFlat
if flatmap != nil {
return flatmap, nil
}
// if we have json attrs, they need to be decoded
rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType())
if err != nil {
return nil, err
}
instanceState, err := res.ShimInstanceStateFromValue(rio.Value)
if err != nil {
return nil, err
}
return instanceState.Attributes, nil
}
func shimLegacyState(legacy *terraform.State) (*states.State, error) {
state, err := terraform.ShimLegacyState(legacy)
if err != nil {
return nil, err
}
if state.HasResources() {
for _, module := range state.Modules {
for name, resource := range module.Resources {
module.Resources[name].ProviderConfig.Provider = addrs.ImpliedProviderForUnqualifiedType(resource.Addr.Resource.ImpliedProvider())
}
}
}
return state, err
}
// legacyProviderConfigString was copied from addrs.Provider.LegacyString() to
// create a legacy-style string from a non-legacy provider. This is only
// necessary as this package shims back and forth between legacy and modern
// state, neither of which encode the addrs.Provider for a resource.
func legacyProviderConfigString(pc addrs.AbsProviderConfig) string {
if pc.Alias != "" {
if len(pc.Module) == 0 {
return fmt.Sprintf("%s.%s.%s", "provider", pc.Provider.Type, pc.Alias)
} else {
return fmt.Sprintf("%s.%s.%s.%s", pc.Module.String(), "provider", pc.Provider.LegacyString(), pc.Alias)
}
}
if len(pc.Module) == 0 {
return fmt.Sprintf("%s.%s", "provider", pc.Provider.Type)
}
return fmt.Sprintf("%s.%s.%s", pc.Module.String(), "provider", pc.Provider.Type)
}