1package resource
2
3import (
4	"encoding/json"
5	"fmt"
6
7	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
8	"github.com/zclconf/go-cty/cty"
9
10	"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
11	"github.com/hashicorp/terraform-plugin-sdk/internal/configs/hcl2shim"
12
13	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
14	"github.com/hashicorp/terraform-plugin-sdk/terraform"
15)
16
17// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
18func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) {
19	state := terraform.NewState()
20
21	// in the odd case of a nil state, let the helper packages handle it
22	if newState == nil {
23		return nil, nil
24	}
25
26	for _, newMod := range newState.Modules {
27		mod := state.AddModule(newMod.Addr)
28
29		for name, out := range newMod.OutputValues {
30			outputType := ""
31			val := hcl2shim.ConfigValueFromHCL2(out.Value)
32			ty := out.Value.Type()
33			switch {
34			case ty == cty.String:
35				outputType = "string"
36			case ty.IsTupleType() || ty.IsListType():
37				outputType = "list"
38			case ty.IsMapType():
39				outputType = "map"
40			}
41
42			mod.Outputs[name] = &terraform.OutputState{
43				Type:      outputType,
44				Value:     val,
45				Sensitive: out.Sensitive,
46			}
47		}
48
49		for _, res := range newMod.Resources {
50			resType := res.Addr.Type
51			providerType := res.ProviderConfig.ProviderConfig.Type
52
53			resource := getResource(providers, providerType, res.Addr)
54
55			for key, i := range res.Instances {
56				resState := &terraform.ResourceState{
57					Type:     resType,
58					Provider: res.ProviderConfig.String(),
59				}
60
61				// We should always have a Current instance here, but be safe about checking.
62				if i.Current != nil {
63					flatmap, err := shimmedAttributes(i.Current, resource)
64					if err != nil {
65						return nil, fmt.Errorf("error decoding state for %q: %s", resType, err)
66					}
67
68					var meta map[string]interface{}
69					if i.Current.Private != nil {
70						err := json.Unmarshal(i.Current.Private, &meta)
71						if err != nil {
72							return nil, err
73						}
74					}
75
76					resState.Primary = &terraform.InstanceState{
77						ID:         flatmap["id"],
78						Attributes: flatmap,
79						Tainted:    i.Current.Status == states.ObjectTainted,
80						Meta:       meta,
81					}
82
83					if i.Current.SchemaVersion != 0 {
84						resState.Primary.Meta = map[string]interface{}{
85							"schema_version": i.Current.SchemaVersion,
86						}
87					}
88
89					for _, dep := range i.Current.Dependencies {
90						resState.Dependencies = append(resState.Dependencies, dep.String())
91					}
92
93					// convert the indexes to the old style flapmap indexes
94					idx := ""
95					switch key.(type) {
96					case addrs.IntKey:
97						// don't add numeric index values to resources with a count of 0
98						if len(res.Instances) > 1 {
99							idx = fmt.Sprintf(".%d", key)
100						}
101					case addrs.StringKey:
102						idx = "." + key.String()
103					}
104
105					mod.Resources[res.Addr.String()+idx] = resState
106				}
107
108				// add any deposed instances
109				for _, dep := range i.Deposed {
110					flatmap, err := shimmedAttributes(dep, resource)
111					if err != nil {
112						return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err)
113					}
114
115					var meta map[string]interface{}
116					if dep.Private != nil {
117						err := json.Unmarshal(dep.Private, &meta)
118						if err != nil {
119							return nil, err
120						}
121					}
122
123					deposed := &terraform.InstanceState{
124						ID:         flatmap["id"],
125						Attributes: flatmap,
126						Tainted:    dep.Status == states.ObjectTainted,
127						Meta:       meta,
128					}
129					if dep.SchemaVersion != 0 {
130						deposed.Meta = map[string]interface{}{
131							"schema_version": dep.SchemaVersion,
132						}
133					}
134
135					resState.Deposed = append(resState.Deposed, deposed)
136				}
137			}
138		}
139	}
140
141	return state, nil
142}
143
144func getResource(providers map[string]terraform.ResourceProvider, providerName string, addr addrs.Resource) *schema.Resource {
145	p := providers[providerName]
146	if p == nil {
147		panic(fmt.Sprintf("provider %q not found in test step", providerName))
148	}
149
150	// this is only for tests, so should only see schema.Providers
151	provider := p.(*schema.Provider)
152
153	switch addr.Mode {
154	case addrs.ManagedResourceMode:
155		resource := provider.ResourcesMap[addr.Type]
156		if resource != nil {
157			return resource
158		}
159	case addrs.DataResourceMode:
160		resource := provider.DataSourcesMap[addr.Type]
161		if resource != nil {
162			return resource
163		}
164	}
165
166	panic(fmt.Sprintf("resource %s not found in test step", addr.Type))
167}
168
169func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) {
170	flatmap := instance.AttrsFlat
171	if flatmap != nil {
172		return flatmap, nil
173	}
174
175	// if we have json attrs, they need to be decoded
176	rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType())
177	if err != nil {
178		return nil, err
179	}
180
181	instanceState, err := res.ShimInstanceStateFromValue(rio.Value)
182	if err != nil {
183		return nil, err
184	}
185
186	return instanceState.Attributes, nil
187}
188