]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | package statefile |
2 | ||
3 | import ( | |
4 | "encoding/json" | |
5 | "fmt" | |
6 | "io" | |
7 | "sort" | |
8 | ||
9 | version "github.com/hashicorp/go-version" | |
10 | ctyjson "github.com/zclconf/go-cty/cty/json" | |
11 | ||
12 | "github.com/hashicorp/terraform/addrs" | |
13 | "github.com/hashicorp/terraform/states" | |
14 | "github.com/hashicorp/terraform/tfdiags" | |
15 | ) | |
16 | ||
17 | func readStateV4(src []byte) (*File, tfdiags.Diagnostics) { | |
18 | var diags tfdiags.Diagnostics | |
19 | sV4 := &stateV4{} | |
20 | err := json.Unmarshal(src, sV4) | |
21 | if err != nil { | |
22 | diags = diags.Append(jsonUnmarshalDiags(err)) | |
23 | return nil, diags | |
24 | } | |
25 | ||
26 | file, prepDiags := prepareStateV4(sV4) | |
27 | diags = diags.Append(prepDiags) | |
28 | return file, diags | |
29 | } | |
30 | ||
31 | func prepareStateV4(sV4 *stateV4) (*File, tfdiags.Diagnostics) { | |
32 | var diags tfdiags.Diagnostics | |
33 | ||
34 | var tfVersion *version.Version | |
35 | if sV4.TerraformVersion != "" { | |
36 | var err error | |
37 | tfVersion, err = version.NewVersion(sV4.TerraformVersion) | |
38 | if err != nil { | |
39 | diags = diags.Append(tfdiags.Sourceless( | |
40 | tfdiags.Error, | |
41 | "Invalid Terraform version string", | |
42 | fmt.Sprintf("State file claims to have been written by Terraform version %q, which is not a valid version string.", sV4.TerraformVersion), | |
43 | )) | |
44 | } | |
45 | } | |
46 | ||
47 | file := &File{ | |
48 | TerraformVersion: tfVersion, | |
49 | Serial: sV4.Serial, | |
50 | Lineage: sV4.Lineage, | |
51 | } | |
52 | ||
53 | state := states.NewState() | |
54 | ||
55 | for _, rsV4 := range sV4.Resources { | |
56 | rAddr := addrs.Resource{ | |
57 | Type: rsV4.Type, | |
58 | Name: rsV4.Name, | |
59 | } | |
60 | switch rsV4.Mode { | |
61 | case "managed": | |
62 | rAddr.Mode = addrs.ManagedResourceMode | |
63 | case "data": | |
64 | rAddr.Mode = addrs.DataResourceMode | |
65 | default: | |
66 | diags = diags.Append(tfdiags.Sourceless( | |
67 | tfdiags.Error, | |
68 | "Invalid resource mode in state", | |
69 | fmt.Sprintf("State contains a resource with mode %q (%q %q) which is not supported.", rsV4.Mode, rAddr.Type, rAddr.Name), | |
70 | )) | |
71 | continue | |
72 | } | |
73 | ||
74 | moduleAddr := addrs.RootModuleInstance | |
75 | if rsV4.Module != "" { | |
76 | var addrDiags tfdiags.Diagnostics | |
77 | moduleAddr, addrDiags = addrs.ParseModuleInstanceStr(rsV4.Module) | |
78 | diags = diags.Append(addrDiags) | |
79 | if addrDiags.HasErrors() { | |
80 | continue | |
81 | } | |
82 | } | |
83 | ||
84 | providerAddr, addrDiags := addrs.ParseAbsProviderConfigStr(rsV4.ProviderConfig) | |
85 | diags.Append(addrDiags) | |
86 | if addrDiags.HasErrors() { | |
87 | continue | |
88 | } | |
89 | ||
90 | var eachMode states.EachMode | |
91 | switch rsV4.EachMode { | |
92 | case "": | |
93 | eachMode = states.NoEach | |
94 | case "list": | |
95 | eachMode = states.EachList | |
96 | case "map": | |
97 | eachMode = states.EachMap | |
98 | default: | |
99 | diags = diags.Append(tfdiags.Sourceless( | |
100 | tfdiags.Error, | |
101 | "Invalid resource metadata in state", | |
102 | fmt.Sprintf("Resource %s has invalid \"each\" value %q in state.", rAddr.Absolute(moduleAddr), eachMode), | |
103 | )) | |
104 | continue | |
105 | } | |
106 | ||
107 | ms := state.EnsureModule(moduleAddr) | |
108 | ||
109 | // Ensure the resource container object is present in the state. | |
110 | ms.SetResourceMeta(rAddr, eachMode, providerAddr) | |
111 | ||
112 | for _, isV4 := range rsV4.Instances { | |
113 | keyRaw := isV4.IndexKey | |
114 | var key addrs.InstanceKey | |
115 | switch tk := keyRaw.(type) { | |
116 | case int: | |
117 | key = addrs.IntKey(tk) | |
118 | case float64: | |
119 | // Since JSON only has one number type, reading from encoding/json | |
120 | // gives us a float64 here even if the number is whole. | |
121 | // float64 has a smaller integer range than int, but in practice | |
122 | // we rarely have more than a few tens of instances and so | |
123 | // it's unlikely that we'll exhaust the 52 bits in a float64. | |
124 | key = addrs.IntKey(int(tk)) | |
125 | case string: | |
126 | key = addrs.StringKey(tk) | |
127 | default: | |
128 | if keyRaw != nil { | |
129 | diags = diags.Append(tfdiags.Sourceless( | |
130 | tfdiags.Error, | |
131 | "Invalid resource instance metadata in state", | |
132 | fmt.Sprintf("Resource %s has an instance with the invalid instance key %#v.", rAddr.Absolute(moduleAddr), keyRaw), | |
133 | )) | |
134 | continue | |
135 | } | |
136 | key = addrs.NoKey | |
137 | } | |
138 | ||
139 | instAddr := rAddr.Instance(key) | |
140 | ||
141 | obj := &states.ResourceInstanceObjectSrc{ | |
142 | SchemaVersion: isV4.SchemaVersion, | |
143 | } | |
144 | ||
145 | { | |
146 | // Instance attributes | |
147 | switch { | |
148 | case isV4.AttributesRaw != nil: | |
149 | obj.AttrsJSON = isV4.AttributesRaw | |
150 | case isV4.AttributesFlat != nil: | |
151 | obj.AttrsFlat = isV4.AttributesFlat | |
152 | default: | |
153 | // This is odd, but we'll accept it and just treat the | |
154 | // object has being empty. In practice this should arise | |
155 | // only from the contrived sort of state objects we tend | |
156 | // to hand-write inline in tests. | |
157 | obj.AttrsJSON = []byte{'{', '}'} | |
158 | } | |
159 | } | |
160 | ||
161 | { | |
162 | // Status | |
163 | raw := isV4.Status | |
164 | switch raw { | |
165 | case "": | |
166 | obj.Status = states.ObjectReady | |
167 | case "tainted": | |
168 | obj.Status = states.ObjectTainted | |
169 | default: | |
170 | diags = diags.Append(tfdiags.Sourceless( | |
171 | tfdiags.Error, | |
172 | "Invalid resource instance metadata in state", | |
173 | fmt.Sprintf("Instance %s has invalid status %q.", instAddr.Absolute(moduleAddr), raw), | |
174 | )) | |
175 | continue | |
176 | } | |
177 | } | |
178 | ||
179 | if raw := isV4.PrivateRaw; len(raw) > 0 { | |
180 | obj.Private = raw | |
181 | } | |
182 | ||
183 | { | |
184 | depsRaw := isV4.Dependencies | |
185 | deps := make([]addrs.Referenceable, 0, len(depsRaw)) | |
186 | for _, depRaw := range depsRaw { | |
187 | ref, refDiags := addrs.ParseRefStr(depRaw) | |
188 | diags = diags.Append(refDiags) | |
189 | if refDiags.HasErrors() { | |
190 | continue | |
191 | } | |
192 | if len(ref.Remaining) != 0 { | |
193 | diags = diags.Append(tfdiags.Sourceless( | |
194 | tfdiags.Error, | |
195 | "Invalid resource instance metadata in state", | |
196 | fmt.Sprintf("Instance %s declares dependency on %q, which is not a reference to a dependable object.", instAddr.Absolute(moduleAddr), depRaw), | |
197 | )) | |
198 | } | |
199 | if ref.Subject == nil { | |
200 | // Should never happen | |
201 | panic(fmt.Sprintf("parsing dependency %q for instance %s returned a nil address", depRaw, instAddr.Absolute(moduleAddr))) | |
202 | } | |
203 | deps = append(deps, ref.Subject) | |
204 | } | |
205 | obj.Dependencies = deps | |
206 | } | |
207 | ||
208 | switch { | |
209 | case isV4.Deposed != "": | |
210 | dk := states.DeposedKey(isV4.Deposed) | |
211 | if len(dk) != 8 { | |
212 | diags = diags.Append(tfdiags.Sourceless( | |
213 | tfdiags.Error, | |
214 | "Invalid resource instance metadata in state", | |
215 | fmt.Sprintf("Instance %s has an object with deposed key %q, which is not correctly formatted.", instAddr.Absolute(moduleAddr), isV4.Deposed), | |
216 | )) | |
217 | continue | |
218 | } | |
219 | is := ms.ResourceInstance(instAddr) | |
220 | if is.HasDeposed(dk) { | |
221 | diags = diags.Append(tfdiags.Sourceless( | |
222 | tfdiags.Error, | |
223 | "Duplicate resource instance in state", | |
224 | fmt.Sprintf("Instance %s deposed object %q appears multiple times in the state file.", instAddr.Absolute(moduleAddr), dk), | |
225 | )) | |
226 | continue | |
227 | } | |
228 | ||
229 | ms.SetResourceInstanceDeposed(instAddr, dk, obj, providerAddr) | |
230 | default: | |
231 | is := ms.ResourceInstance(instAddr) | |
232 | if is.HasCurrent() { | |
233 | diags = diags.Append(tfdiags.Sourceless( | |
234 | tfdiags.Error, | |
235 | "Duplicate resource instance in state", | |
236 | fmt.Sprintf("Instance %s appears multiple times in the state file.", instAddr.Absolute(moduleAddr)), | |
237 | )) | |
238 | continue | |
239 | } | |
240 | ||
241 | ms.SetResourceInstanceCurrent(instAddr, obj, providerAddr) | |
242 | } | |
243 | } | |
244 | ||
245 | // We repeat this after creating the instances because | |
246 | // SetResourceInstanceCurrent automatically resets this metadata based | |
247 | // on the incoming objects. That behavior is useful when we're making | |
248 | // piecemeal updates to the state during an apply, but when we're | |
249 | // reading the state file we want to reflect its contents exactly. | |
250 | ms.SetResourceMeta(rAddr, eachMode, providerAddr) | |
251 | } | |
252 | ||
253 | // The root module is special in that we persist its attributes and thus | |
254 | // need to reload them now. (For descendent modules we just re-calculate | |
255 | // them based on the latest configuration on each run.) | |
256 | { | |
257 | rootModule := state.RootModule() | |
258 | for name, fos := range sV4.RootOutputs { | |
259 | os := &states.OutputValue{} | |
260 | os.Sensitive = fos.Sensitive | |
261 | ||
262 | ty, err := ctyjson.UnmarshalType([]byte(fos.ValueTypeRaw)) | |
263 | if err != nil { | |
264 | diags = diags.Append(tfdiags.Sourceless( | |
265 | tfdiags.Error, | |
266 | "Invalid output value type in state", | |
267 | fmt.Sprintf("The state file has an invalid type specification for output %q: %s.", name, err), | |
268 | )) | |
269 | continue | |
270 | } | |
271 | ||
272 | val, err := ctyjson.Unmarshal([]byte(fos.ValueRaw), ty) | |
273 | if err != nil { | |
274 | diags = diags.Append(tfdiags.Sourceless( | |
275 | tfdiags.Error, | |
276 | "Invalid output value saved in state", | |
277 | fmt.Sprintf("The state file has an invalid value for output %q: %s.", name, err), | |
278 | )) | |
279 | continue | |
280 | } | |
281 | ||
282 | os.Value = val | |
283 | rootModule.OutputValues[name] = os | |
284 | } | |
285 | } | |
286 | ||
287 | file.State = state | |
288 | return file, diags | |
289 | } | |
290 | ||
291 | func writeStateV4(file *File, w io.Writer) tfdiags.Diagnostics { | |
292 | // Here we'll convert back from the "File" representation to our | |
293 | // stateV4 struct representation and write that. | |
294 | // | |
295 | // While we support legacy state formats for reading, we only support the | |
296 | // latest for writing and so if a V5 is added in future then this function | |
297 | // should be deleted and replaced with a writeStateV5, even though the | |
298 | // read/prepare V4 functions above would stick around. | |
299 | ||
300 | var diags tfdiags.Diagnostics | |
301 | if file == nil || file.State == nil { | |
302 | panic("attempt to write nil state to file") | |
303 | } | |
304 | ||
305 | var terraformVersion string | |
306 | if file.TerraformVersion != nil { | |
307 | terraformVersion = file.TerraformVersion.String() | |
308 | } | |
309 | ||
310 | sV4 := &stateV4{ | |
311 | TerraformVersion: terraformVersion, | |
312 | Serial: file.Serial, | |
313 | Lineage: file.Lineage, | |
314 | RootOutputs: map[string]outputStateV4{}, | |
315 | Resources: []resourceStateV4{}, | |
316 | } | |
317 | ||
318 | for name, os := range file.State.RootModule().OutputValues { | |
319 | src, err := ctyjson.Marshal(os.Value, os.Value.Type()) | |
320 | if err != nil { | |
321 | diags = diags.Append(tfdiags.Sourceless( | |
322 | tfdiags.Error, | |
323 | "Failed to serialize output value in state", | |
324 | fmt.Sprintf("An error occured while serializing output value %q: %s.", name, err), | |
325 | )) | |
326 | continue | |
327 | } | |
328 | ||
329 | typeSrc, err := ctyjson.MarshalType(os.Value.Type()) | |
330 | if err != nil { | |
331 | diags = diags.Append(tfdiags.Sourceless( | |
332 | tfdiags.Error, | |
333 | "Failed to serialize output value in state", | |
334 | fmt.Sprintf("An error occured while serializing the type of output value %q: %s.", name, err), | |
335 | )) | |
336 | continue | |
337 | } | |
338 | ||
339 | sV4.RootOutputs[name] = outputStateV4{ | |
340 | Sensitive: os.Sensitive, | |
341 | ValueRaw: json.RawMessage(src), | |
342 | ValueTypeRaw: json.RawMessage(typeSrc), | |
343 | } | |
344 | } | |
345 | ||
346 | for _, ms := range file.State.Modules { | |
347 | moduleAddr := ms.Addr | |
348 | for _, rs := range ms.Resources { | |
349 | resourceAddr := rs.Addr | |
350 | ||
351 | var mode string | |
352 | switch resourceAddr.Mode { | |
353 | case addrs.ManagedResourceMode: | |
354 | mode = "managed" | |
355 | case addrs.DataResourceMode: | |
356 | mode = "data" | |
357 | default: | |
358 | diags = diags.Append(tfdiags.Sourceless( | |
359 | tfdiags.Error, | |
360 | "Failed to serialize resource in state", | |
361 | fmt.Sprintf("Resource %s has mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), resourceAddr.Mode), | |
362 | )) | |
363 | continue | |
364 | } | |
365 | ||
366 | var eachMode string | |
367 | switch rs.EachMode { | |
368 | case states.NoEach: | |
369 | eachMode = "" | |
370 | case states.EachList: | |
371 | eachMode = "list" | |
372 | case states.EachMap: | |
373 | eachMode = "map" | |
374 | default: | |
375 | diags = diags.Append(tfdiags.Sourceless( | |
376 | tfdiags.Error, | |
377 | "Failed to serialize resource in state", | |
378 | fmt.Sprintf("Resource %s has \"each\" mode %s, which cannot be serialized in state", resourceAddr.Absolute(moduleAddr), rs.EachMode), | |
379 | )) | |
380 | continue | |
381 | } | |
382 | ||
383 | sV4.Resources = append(sV4.Resources, resourceStateV4{ | |
384 | Module: moduleAddr.String(), | |
385 | Mode: mode, | |
386 | Type: resourceAddr.Type, | |
387 | Name: resourceAddr.Name, | |
388 | EachMode: eachMode, | |
389 | ProviderConfig: rs.ProviderConfig.String(), | |
390 | Instances: []instanceObjectStateV4{}, | |
391 | }) | |
392 | rsV4 := &(sV4.Resources[len(sV4.Resources)-1]) | |
393 | ||
394 | for key, is := range rs.Instances { | |
395 | if is.HasCurrent() { | |
396 | var objDiags tfdiags.Diagnostics | |
397 | rsV4.Instances, objDiags = appendInstanceObjectStateV4( | |
398 | rs, is, key, is.Current, states.NotDeposed, | |
399 | rsV4.Instances, | |
400 | ) | |
401 | diags = diags.Append(objDiags) | |
402 | } | |
403 | for dk, obj := range is.Deposed { | |
404 | var objDiags tfdiags.Diagnostics | |
405 | rsV4.Instances, objDiags = appendInstanceObjectStateV4( | |
406 | rs, is, key, obj, dk, | |
407 | rsV4.Instances, | |
408 | ) | |
409 | diags = diags.Append(objDiags) | |
410 | } | |
411 | } | |
412 | } | |
413 | } | |
414 | ||
415 | sV4.normalize() | |
416 | ||
417 | src, err := json.MarshalIndent(sV4, "", " ") | |
418 | if err != nil { | |
419 | // Shouldn't happen if we do our conversion to *stateV4 correctly above. | |
420 | diags = diags.Append(tfdiags.Sourceless( | |
421 | tfdiags.Error, | |
422 | "Failed to serialize state", | |
423 | fmt.Sprintf("An error occured while serializing the state to save it. This is a bug in Terraform and should be reported: %s.", err), | |
424 | )) | |
425 | return diags | |
426 | } | |
427 | src = append(src, '\n') | |
428 | ||
429 | _, err = w.Write(src) | |
430 | if err != nil { | |
431 | diags = diags.Append(tfdiags.Sourceless( | |
432 | tfdiags.Error, | |
433 | "Failed to write state", | |
434 | fmt.Sprintf("An error occured while writing the serialized state: %s.", err), | |
435 | )) | |
436 | return diags | |
437 | } | |
438 | ||
439 | return diags | |
440 | } | |
441 | ||
442 | func appendInstanceObjectStateV4(rs *states.Resource, is *states.ResourceInstance, key addrs.InstanceKey, obj *states.ResourceInstanceObjectSrc, deposed states.DeposedKey, isV4s []instanceObjectStateV4) ([]instanceObjectStateV4, tfdiags.Diagnostics) { | |
443 | var diags tfdiags.Diagnostics | |
444 | ||
445 | var status string | |
446 | switch obj.Status { | |
447 | case states.ObjectReady: | |
448 | status = "" | |
449 | case states.ObjectTainted: | |
450 | status = "tainted" | |
451 | default: | |
452 | diags = diags.Append(tfdiags.Sourceless( | |
453 | tfdiags.Error, | |
454 | "Failed to serialize resource instance in state", | |
455 | fmt.Sprintf("Instance %s has status %s, which cannot be saved in state.", rs.Addr.Instance(key), obj.Status), | |
456 | )) | |
457 | } | |
458 | ||
459 | var privateRaw []byte | |
460 | if len(obj.Private) > 0 { | |
461 | privateRaw = obj.Private | |
462 | } | |
463 | ||
464 | deps := make([]string, len(obj.Dependencies)) | |
465 | for i, depAddr := range obj.Dependencies { | |
466 | deps[i] = depAddr.String() | |
467 | } | |
468 | ||
469 | var rawKey interface{} | |
470 | switch tk := key.(type) { | |
471 | case addrs.IntKey: | |
472 | rawKey = int(tk) | |
473 | case addrs.StringKey: | |
474 | rawKey = string(tk) | |
475 | default: | |
476 | if key != addrs.NoKey { | |
477 | diags = diags.Append(tfdiags.Sourceless( | |
478 | tfdiags.Error, | |
479 | "Failed to serialize resource instance in state", | |
480 | fmt.Sprintf("Instance %s has an unsupported instance key: %#v.", rs.Addr.Instance(key), key), | |
481 | )) | |
482 | } | |
483 | } | |
484 | ||
485 | return append(isV4s, instanceObjectStateV4{ | |
486 | IndexKey: rawKey, | |
487 | Deposed: string(deposed), | |
488 | Status: status, | |
489 | SchemaVersion: obj.SchemaVersion, | |
490 | AttributesFlat: obj.AttrsFlat, | |
491 | AttributesRaw: obj.AttrsJSON, | |
492 | PrivateRaw: privateRaw, | |
493 | Dependencies: deps, | |
494 | }), diags | |
495 | } | |
496 | ||
497 | type stateV4 struct { | |
498 | Version stateVersionV4 `json:"version"` | |
499 | TerraformVersion string `json:"terraform_version"` | |
500 | Serial uint64 `json:"serial"` | |
501 | Lineage string `json:"lineage"` | |
502 | RootOutputs map[string]outputStateV4 `json:"outputs"` | |
503 | Resources []resourceStateV4 `json:"resources"` | |
504 | } | |
505 | ||
506 | // normalize makes some in-place changes to normalize the way items are | |
507 | // stored to ensure that two functionally-equivalent states will be stored | |
508 | // identically. | |
509 | func (s *stateV4) normalize() { | |
510 | sort.Stable(sortResourcesV4(s.Resources)) | |
511 | for _, rs := range s.Resources { | |
512 | sort.Stable(sortInstancesV4(rs.Instances)) | |
513 | } | |
514 | } | |
515 | ||
516 | type outputStateV4 struct { | |
517 | ValueRaw json.RawMessage `json:"value"` | |
518 | ValueTypeRaw json.RawMessage `json:"type"` | |
519 | Sensitive bool `json:"sensitive,omitempty"` | |
520 | } | |
521 | ||
522 | type resourceStateV4 struct { | |
523 | Module string `json:"module,omitempty"` | |
524 | Mode string `json:"mode"` | |
525 | Type string `json:"type"` | |
526 | Name string `json:"name"` | |
527 | EachMode string `json:"each,omitempty"` | |
528 | ProviderConfig string `json:"provider"` | |
529 | Instances []instanceObjectStateV4 `json:"instances"` | |
530 | } | |
531 | ||
532 | type instanceObjectStateV4 struct { | |
533 | IndexKey interface{} `json:"index_key,omitempty"` | |
534 | Status string `json:"status,omitempty"` | |
535 | Deposed string `json:"deposed,omitempty"` | |
536 | ||
537 | SchemaVersion uint64 `json:"schema_version"` | |
538 | AttributesRaw json.RawMessage `json:"attributes,omitempty"` | |
539 | AttributesFlat map[string]string `json:"attributes_flat,omitempty"` | |
540 | ||
541 | PrivateRaw []byte `json:"private,omitempty"` | |
542 | ||
543 | Dependencies []string `json:"depends_on,omitempty"` | |
544 | } | |
545 | ||
546 | // stateVersionV4 is a weird special type we use to produce our hard-coded | |
547 | // "version": 4 in the JSON serialization. | |
548 | type stateVersionV4 struct{} | |
549 | ||
550 | func (sv stateVersionV4) MarshalJSON() ([]byte, error) { | |
551 | return []byte{'4'}, nil | |
552 | } | |
553 | ||
554 | func (sv stateVersionV4) UnmarshalJSON([]byte) error { | |
555 | // Nothing to do: we already know we're version 4 | |
556 | return nil | |
557 | } | |
558 | ||
559 | type sortResourcesV4 []resourceStateV4 | |
560 | ||
561 | func (sr sortResourcesV4) Len() int { return len(sr) } | |
562 | func (sr sortResourcesV4) Swap(i, j int) { sr[i], sr[j] = sr[j], sr[i] } | |
563 | func (sr sortResourcesV4) Less(i, j int) bool { | |
564 | switch { | |
565 | case sr[i].Mode != sr[j].Mode: | |
566 | return sr[i].Mode < sr[j].Mode | |
567 | case sr[i].Type != sr[j].Type: | |
568 | return sr[i].Type < sr[j].Type | |
569 | case sr[i].Name != sr[j].Name: | |
570 | return sr[i].Name < sr[j].Name | |
571 | default: | |
572 | return false | |
573 | } | |
574 | } | |
575 | ||
576 | type sortInstancesV4 []instanceObjectStateV4 | |
577 | ||
578 | func (si sortInstancesV4) Len() int { return len(si) } | |
579 | func (si sortInstancesV4) Swap(i, j int) { si[i], si[j] = si[j], si[i] } | |
580 | func (si sortInstancesV4) Less(i, j int) bool { | |
581 | ki := si[i].IndexKey | |
582 | kj := si[j].IndexKey | |
583 | if ki != kj { | |
584 | if (ki == nil) != (kj == nil) { | |
585 | return ki == nil | |
586 | } | |
587 | if kii, isInt := ki.(int); isInt { | |
588 | if kji, isInt := kj.(int); isInt { | |
589 | return kii < kji | |
590 | } | |
591 | return true | |
592 | } | |
593 | if kis, isStr := ki.(string); isStr { | |
594 | if kjs, isStr := kj.(string); isStr { | |
595 | return kis < kjs | |
596 | } | |
597 | return true | |
598 | } | |
599 | } | |
600 | if si[i].Deposed != si[j].Deposed { | |
601 | return si[i].Deposed < si[j].Deposed | |
602 | } | |
603 | return false | |
604 | } |