]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/states/state_string.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / states / state_string.go
1 package states
2
3 import (
4 "bufio"
5 "bytes"
6 "encoding/json"
7 "fmt"
8 "sort"
9 "strings"
10
11 ctyjson "github.com/zclconf/go-cty/cty/json"
12
13 "github.com/hashicorp/terraform/addrs"
14 "github.com/hashicorp/terraform/config/hcl2shim"
15 )
16
17 // String returns a rather-odd string representation of the entire state.
18 //
19 // This is intended to match the behavior of the older terraform.State.String
20 // method that is used in lots of existing tests. It should not be used in
21 // new tests: instead, use "cmp" to directly compare the state data structures
22 // and print out a diff if they do not match.
23 //
24 // This method should never be used in non-test code, whether directly by call
25 // or indirectly via a %s or %q verb in package fmt.
26 func (s *State) String() string {
27 if s == nil {
28 return "<nil>"
29 }
30
31 // sort the modules by name for consistent output
32 modules := make([]string, 0, len(s.Modules))
33 for m := range s.Modules {
34 modules = append(modules, m)
35 }
36 sort.Strings(modules)
37
38 var buf bytes.Buffer
39 for _, name := range modules {
40 m := s.Modules[name]
41 mStr := m.testString()
42
43 // If we're the root module, we just write the output directly.
44 if m.Addr.IsRoot() {
45 buf.WriteString(mStr + "\n")
46 continue
47 }
48
49 // We need to build out a string that resembles the not-quite-standard
50 // format that terraform.State.String used to use, where there's a
51 // "module." prefix but then just a chain of all of the module names
52 // without any further "module." portions.
53 buf.WriteString("module")
54 for _, step := range m.Addr {
55 buf.WriteByte('.')
56 buf.WriteString(step.Name)
57 if step.InstanceKey != addrs.NoKey {
58 buf.WriteByte('[')
59 buf.WriteString(step.InstanceKey.String())
60 buf.WriteByte(']')
61 }
62 }
63 buf.WriteString(":\n")
64
65 s := bufio.NewScanner(strings.NewReader(mStr))
66 for s.Scan() {
67 text := s.Text()
68 if text != "" {
69 text = " " + text
70 }
71
72 buf.WriteString(fmt.Sprintf("%s\n", text))
73 }
74 }
75
76 return strings.TrimSpace(buf.String())
77 }
78
79 // testString is used to produce part of the output of State.String. It should
80 // never be used directly.
81 func (m *Module) testString() string {
82 var buf bytes.Buffer
83
84 if len(m.Resources) == 0 {
85 buf.WriteString("<no state>")
86 }
87
88 // We use AbsResourceInstance here, even though everything belongs to
89 // the same module, just because we have a sorting behavior defined
90 // for those but not for just ResourceInstance.
91 addrsOrder := make([]addrs.AbsResourceInstance, 0, len(m.Resources))
92 for _, rs := range m.Resources {
93 for ik := range rs.Instances {
94 addrsOrder = append(addrsOrder, rs.Addr.Instance(ik).Absolute(addrs.RootModuleInstance))
95 }
96 }
97
98 sort.Slice(addrsOrder, func(i, j int) bool {
99 return addrsOrder[i].Less(addrsOrder[j])
100 })
101
102 for _, fakeAbsAddr := range addrsOrder {
103 addr := fakeAbsAddr.Resource
104 rs := m.Resource(addr.ContainingResource())
105 is := m.ResourceInstance(addr)
106
107 // Here we need to fake up a legacy-style address as the old state
108 // types would've used, since that's what our tests against those
109 // old types expect. The significant difference is that instancekey
110 // is dot-separated rather than using index brackets.
111 k := addr.ContainingResource().String()
112 if addr.Key != addrs.NoKey {
113 switch tk := addr.Key.(type) {
114 case addrs.IntKey:
115 k = fmt.Sprintf("%s.%d", k, tk)
116 default:
117 // No other key types existed for the legacy types, so we
118 // can do whatever we want here. We'll just use our standard
119 // syntax for these.
120 k = k + tk.String()
121 }
122 }
123
124 id := LegacyInstanceObjectID(is.Current)
125
126 taintStr := ""
127 if is.Current != nil && is.Current.Status == ObjectTainted {
128 taintStr = " (tainted)"
129 }
130
131 deposedStr := ""
132 if len(is.Deposed) > 0 {
133 deposedStr = fmt.Sprintf(" (%d deposed)", len(is.Deposed))
134 }
135
136 buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr))
137 buf.WriteString(fmt.Sprintf(" ID = %s\n", id))
138 buf.WriteString(fmt.Sprintf(" provider = %s\n", rs.ProviderConfig.String()))
139
140 // Attributes were a flatmap before, but are not anymore. To preserve
141 // our old output as closely as possible we need to do a conversion
142 // to flatmap. Normally we'd want to do this with schema for
143 // accuracy, but for our purposes here it only needs to be approximate.
144 // This should produce an identical result for most cases, though
145 // in particular will differ in a few cases:
146 // - The keys used for elements in a set will be different
147 // - Values for attributes of type cty.DynamicPseudoType will be
148 // misinterpreted (but these weren't possible in old world anyway)
149 var attributes map[string]string
150 if obj := is.Current; obj != nil {
151 switch {
152 case obj.AttrsFlat != nil:
153 // Easy (but increasingly unlikely) case: the state hasn't
154 // actually been upgraded to the new form yet.
155 attributes = obj.AttrsFlat
156 case obj.AttrsJSON != nil:
157 ty, err := ctyjson.ImpliedType(obj.AttrsJSON)
158 if err == nil {
159 val, err := ctyjson.Unmarshal(obj.AttrsJSON, ty)
160 if err == nil {
161 attributes = hcl2shim.FlatmapValueFromHCL2(val)
162 }
163 }
164 }
165 }
166 attrKeys := make([]string, 0, len(attributes))
167 for ak, val := range attributes {
168 if ak == "id" {
169 continue
170 }
171
172 // don't show empty containers in the output
173 if val == "0" && (strings.HasSuffix(ak, ".#") || strings.HasSuffix(ak, ".%")) {
174 continue
175 }
176
177 attrKeys = append(attrKeys, ak)
178 }
179
180 sort.Strings(attrKeys)
181
182 for _, ak := range attrKeys {
183 av := attributes[ak]
184 buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av))
185 }
186
187 // CAUTION: Since deposed keys are now random strings instead of
188 // incrementing integers, this result will not be deterministic
189 // if there is more than one deposed object.
190 i := 1
191 for _, t := range is.Deposed {
192 id := LegacyInstanceObjectID(t)
193 taintStr := ""
194 if t.Status == ObjectTainted {
195 taintStr = " (tainted)"
196 }
197 buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s%s\n", i, id, taintStr))
198 i++
199 }
200
201 if obj := is.Current; obj != nil && len(obj.Dependencies) > 0 {
202 buf.WriteString(fmt.Sprintf("\n Dependencies:\n"))
203 for _, dep := range obj.Dependencies {
204 buf.WriteString(fmt.Sprintf(" %s\n", dep.String()))
205 }
206 }
207 }
208
209 if len(m.OutputValues) > 0 {
210 buf.WriteString("\nOutputs:\n\n")
211
212 ks := make([]string, 0, len(m.OutputValues))
213 for k := range m.OutputValues {
214 ks = append(ks, k)
215 }
216 sort.Strings(ks)
217
218 for _, k := range ks {
219 v := m.OutputValues[k]
220 lv := hcl2shim.ConfigValueFromHCL2(v.Value)
221 switch vTyped := lv.(type) {
222 case string:
223 buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped))
224 case []interface{}:
225 buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped))
226 case map[string]interface{}:
227 var mapKeys []string
228 for key := range vTyped {
229 mapKeys = append(mapKeys, key)
230 }
231 sort.Strings(mapKeys)
232
233 var mapBuf bytes.Buffer
234 mapBuf.WriteString("{")
235 for _, key := range mapKeys {
236 mapBuf.WriteString(fmt.Sprintf("%s:%s ", key, vTyped[key]))
237 }
238 mapBuf.WriteString("}")
239
240 buf.WriteString(fmt.Sprintf("%s = %s\n", k, mapBuf.String()))
241 default:
242 buf.WriteString(fmt.Sprintf("%s = %#v\n", k, lv))
243 }
244 }
245 }
246
247 return buf.String()
248 }
249
250 // LegacyInstanceObjectID is a helper for extracting an object id value from
251 // an instance object in a way that approximates how we used to do this
252 // for the old state types. ID is no longer first-class, so this is preserved
253 // only for compatibility with old tests that include the id as part of their
254 // expected value.
255 func LegacyInstanceObjectID(obj *ResourceInstanceObjectSrc) string {
256 if obj == nil {
257 return "<not created>"
258 }
259
260 if obj.AttrsJSON != nil {
261 type WithID struct {
262 ID string `json:"id"`
263 }
264 var withID WithID
265 err := json.Unmarshal(obj.AttrsJSON, &withID)
266 if err == nil {
267 return withID.ID
268 }
269 } else if obj.AttrsFlat != nil {
270 if flatID, exists := obj.AttrsFlat["id"]; exists {
271 return flatID
272 }
273 }
274
275 // For resource types created after we removed id as special there may
276 // not actually be one at all. This is okay because older tests won't
277 // encounter this, and new tests shouldn't be using ids.
278 return "<none>"
279 }