github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonstate/state.go (about) 1 package jsonstate 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 8 "github.com/zclconf/go-cty/cty" 9 ctyjson "github.com/zclconf/go-cty/cty/json" 10 11 "github.com/hashicorp/terraform/internal/addrs" 12 "github.com/hashicorp/terraform/internal/command/jsonchecks" 13 "github.com/hashicorp/terraform/internal/lang/marks" 14 "github.com/hashicorp/terraform/internal/states" 15 "github.com/hashicorp/terraform/internal/states/statefile" 16 "github.com/hashicorp/terraform/internal/terraform" 17 ) 18 19 const ( 20 // FormatVersion represents the version of the json format and will be 21 // incremented for any change to this format that requires changes to a 22 // consuming parser. 23 FormatVersion = "1.0" 24 25 ManagedResourceMode = "managed" 26 DataResourceMode = "data" 27 ) 28 29 // state is the top-level representation of the json format of a terraform 30 // state. 31 type state struct { 32 FormatVersion string `json:"format_version,omitempty"` 33 TerraformVersion string `json:"terraform_version,omitempty"` 34 Values *stateValues `json:"values,omitempty"` 35 Checks json.RawMessage `json:"checks,omitempty"` 36 } 37 38 // stateValues is the common representation of resolved values for both the prior 39 // state (which is always complete) and the planned new state. 40 type stateValues struct { 41 Outputs map[string]Output `json:"outputs,omitempty"` 42 RootModule Module `json:"root_module,omitempty"` 43 } 44 45 type Output struct { 46 Sensitive bool `json:"sensitive"` 47 Value json.RawMessage `json:"value,omitempty"` 48 Type json.RawMessage `json:"type,omitempty"` 49 } 50 51 // Module is the representation of a module in state. This can be the root module 52 // or a child module 53 type Module struct { 54 // Resources are sorted in a user-friendly order that is undefined at this 55 // time, but consistent. 56 Resources []Resource `json:"resources,omitempty"` 57 58 // Address is the absolute module address, omitted for the root module 59 Address string `json:"address,omitempty"` 60 61 // Each module object can optionally have its own nested "child_modules", 62 // recursively describing the full module tree. 63 ChildModules []Module `json:"child_modules,omitempty"` 64 } 65 66 // Resource is the representation of a resource in the state. 67 type Resource struct { 68 // Address is the absolute resource address 69 Address string `json:"address,omitempty"` 70 71 // Mode can be "managed" or "data" 72 Mode string `json:"mode,omitempty"` 73 74 Type string `json:"type,omitempty"` 75 Name string `json:"name,omitempty"` 76 77 // Index is omitted for a resource not using `count` or `for_each`. 78 Index json.RawMessage `json:"index,omitempty"` 79 80 // ProviderName allows the property "type" to be interpreted unambiguously 81 // in the unusual situation where a provider offers a resource type whose 82 // name does not start with its own name, such as the "googlebeta" provider 83 // offering "google_compute_instance". 84 ProviderName string `json:"provider_name"` 85 86 // SchemaVersion indicates which version of the resource type schema the 87 // "values" property conforms to. 88 SchemaVersion uint64 `json:"schema_version"` 89 90 // AttributeValues is the JSON representation of the attribute values of the 91 // resource, whose structure depends on the resource type schema. Any 92 // unknown values are omitted or set to null, making them indistinguishable 93 // from absent values. 94 AttributeValues AttributeValues `json:"values,omitempty"` 95 96 // SensitiveValues is similar to AttributeValues, but with all sensitive 97 // values replaced with true, and all non-sensitive leaf values omitted. 98 SensitiveValues json.RawMessage `json:"sensitive_values,omitempty"` 99 100 // DependsOn contains a list of the resource's dependencies. The entries are 101 // addresses relative to the containing module. 102 DependsOn []string `json:"depends_on,omitempty"` 103 104 // Tainted is true if the resource is tainted in terraform state. 105 Tainted bool `json:"tainted,omitempty"` 106 107 // Deposed is set if the resource is deposed in terraform state. 108 DeposedKey string `json:"deposed_key,omitempty"` 109 } 110 111 // AttributeValues is the JSON representation of the attribute values of the 112 // resource, whose structure depends on the resource type schema. 113 type AttributeValues map[string]json.RawMessage 114 115 func marshalAttributeValues(value cty.Value) AttributeValues { 116 // unmark our value to show all values 117 value, _ = value.UnmarkDeep() 118 119 if value == cty.NilVal || value.IsNull() { 120 return nil 121 } 122 123 ret := make(AttributeValues) 124 125 it := value.ElementIterator() 126 for it.Next() { 127 k, v := it.Element() 128 vJSON, _ := ctyjson.Marshal(v, v.Type()) 129 ret[k.AsString()] = json.RawMessage(vJSON) 130 } 131 return ret 132 } 133 134 // newState() returns a minimally-initialized state 135 func newState() *state { 136 return &state{ 137 FormatVersion: FormatVersion, 138 } 139 } 140 141 // MarshalForRenderer returns the pre-json encoding changes of the state, in a 142 // format available to the structured renderer. 143 func MarshalForRenderer(sf *statefile.File, schemas *terraform.Schemas) (Module, map[string]Output, error) { 144 if sf.State.Modules == nil { 145 // Empty state case. 146 return Module{}, nil, nil 147 } 148 149 outputs, err := MarshalOutputs(sf.State.RootModule().OutputValues) 150 if err != nil { 151 return Module{}, nil, err 152 } 153 154 root, err := marshalRootModule(sf.State, schemas) 155 if err != nil { 156 return Module{}, nil, err 157 } 158 159 return root, outputs, err 160 } 161 162 // Marshal returns the json encoding of a terraform state. 163 func Marshal(sf *statefile.File, schemas *terraform.Schemas) ([]byte, error) { 164 output := newState() 165 166 if sf == nil || sf.State.Empty() { 167 ret, err := json.Marshal(output) 168 return ret, err 169 } 170 171 if sf.TerraformVersion != nil { 172 output.TerraformVersion = sf.TerraformVersion.String() 173 } 174 175 // output.StateValues 176 err := output.marshalStateValues(sf.State, schemas) 177 if err != nil { 178 return nil, err 179 } 180 181 // output.Checks 182 if sf.State.CheckResults != nil && sf.State.CheckResults.ConfigResults.Len() > 0 { 183 output.Checks = jsonchecks.MarshalCheckStates(sf.State.CheckResults) 184 } 185 186 ret, err := json.Marshal(output) 187 return ret, err 188 } 189 190 func (jsonstate *state) marshalStateValues(s *states.State, schemas *terraform.Schemas) error { 191 var sv stateValues 192 var err error 193 194 // only marshal the root module outputs 195 sv.Outputs, err = MarshalOutputs(s.RootModule().OutputValues) 196 if err != nil { 197 return err 198 } 199 200 // use the state and module map to build up the module structure 201 sv.RootModule, err = marshalRootModule(s, schemas) 202 if err != nil { 203 return err 204 } 205 206 jsonstate.Values = &sv 207 return nil 208 } 209 210 // MarshalOutputs translates a map of states.OutputValue to a map of jsonstate.Output, 211 // which are defined for json encoding. 212 func MarshalOutputs(outputs map[string]*states.OutputValue) (map[string]Output, error) { 213 if outputs == nil { 214 return nil, nil 215 } 216 217 ret := make(map[string]Output) 218 for k, v := range outputs { 219 ty := v.Value.Type() 220 ov, err := ctyjson.Marshal(v.Value, ty) 221 if err != nil { 222 return ret, err 223 } 224 ot, err := ctyjson.MarshalType(ty) 225 if err != nil { 226 return ret, err 227 } 228 ret[k] = Output{ 229 Value: ov, 230 Type: ot, 231 Sensitive: v.Sensitive, 232 } 233 } 234 235 return ret, nil 236 } 237 238 func marshalRootModule(s *states.State, schemas *terraform.Schemas) (Module, error) { 239 var ret Module 240 var err error 241 242 ret.Address = "" 243 rs, err := marshalResources(s.RootModule().Resources, addrs.RootModuleInstance, schemas) 244 if err != nil { 245 return ret, err 246 } 247 ret.Resources = rs 248 249 // build a map of module -> set[child module addresses] 250 moduleChildSet := make(map[string]map[string]struct{}) 251 for _, mod := range s.Modules { 252 if mod.Addr.IsRoot() { 253 continue 254 } else { 255 for childAddr := mod.Addr; !childAddr.IsRoot(); childAddr = childAddr.Parent() { 256 if _, ok := moduleChildSet[childAddr.Parent().String()]; !ok { 257 moduleChildSet[childAddr.Parent().String()] = map[string]struct{}{} 258 } 259 moduleChildSet[childAddr.Parent().String()][childAddr.String()] = struct{}{} 260 } 261 } 262 } 263 264 // transform the previous map into map of module -> [child module addresses] 265 moduleMap := make(map[string][]addrs.ModuleInstance) 266 for parent, children := range moduleChildSet { 267 for child := range children { 268 childModuleInstance, diags := addrs.ParseModuleInstanceStr(child) 269 if diags.HasErrors() { 270 return ret, diags.Err() 271 } 272 moduleMap[parent] = append(moduleMap[parent], childModuleInstance) 273 } 274 } 275 276 // use the state and module map to build up the module structure 277 ret.ChildModules, err = marshalModules(s, schemas, moduleMap[""], moduleMap) 278 return ret, err 279 } 280 281 // marshalModules is an ungainly recursive function to build a module structure 282 // out of terraform state. 283 func marshalModules( 284 s *states.State, 285 schemas *terraform.Schemas, 286 modules []addrs.ModuleInstance, 287 moduleMap map[string][]addrs.ModuleInstance, 288 ) ([]Module, error) { 289 var ret []Module 290 for _, child := range modules { 291 // cm for child module, naming things is hard. 292 cm := Module{Address: child.String()} 293 294 // the module may be resourceless and contain only submodules, it will then be nil here 295 stateMod := s.Module(child) 296 if stateMod != nil { 297 rs, err := marshalResources(stateMod.Resources, stateMod.Addr, schemas) 298 if err != nil { 299 return nil, err 300 } 301 cm.Resources = rs 302 } 303 304 if moduleMap[child.String()] != nil { 305 moreChildModules, err := marshalModules(s, schemas, moduleMap[child.String()], moduleMap) 306 if err != nil { 307 return nil, err 308 } 309 cm.ChildModules = moreChildModules 310 } 311 312 ret = append(ret, cm) 313 } 314 315 // sort the child modules by address for consistency. 316 sort.Slice(ret, func(i, j int) bool { 317 return ret[i].Address < ret[j].Address 318 }) 319 320 return ret, nil 321 } 322 323 func marshalResources(resources map[string]*states.Resource, module addrs.ModuleInstance, schemas *terraform.Schemas) ([]Resource, error) { 324 var ret []Resource 325 326 var sortedResources []*states.Resource 327 for _, r := range resources { 328 sortedResources = append(sortedResources, r) 329 } 330 sort.Slice(sortedResources, func(i, j int) bool { 331 return sortedResources[i].Addr.Less(sortedResources[j].Addr) 332 }) 333 334 for _, r := range sortedResources { 335 336 var sortedKeys []addrs.InstanceKey 337 for k := range r.Instances { 338 sortedKeys = append(sortedKeys, k) 339 } 340 sort.Slice(sortedKeys, func(i, j int) bool { 341 return addrs.InstanceKeyLess(sortedKeys[i], sortedKeys[j]) 342 }) 343 344 for _, k := range sortedKeys { 345 ri := r.Instances[k] 346 347 var err error 348 349 resAddr := r.Addr.Resource 350 351 current := Resource{ 352 Address: r.Addr.Instance(k).String(), 353 Type: resAddr.Type, 354 Name: resAddr.Name, 355 ProviderName: r.ProviderConfig.Provider.String(), 356 } 357 358 if k != nil { 359 index := k.Value() 360 if current.Index, err = ctyjson.Marshal(index, index.Type()); err != nil { 361 return nil, err 362 } 363 } 364 365 switch resAddr.Mode { 366 case addrs.ManagedResourceMode: 367 current.Mode = ManagedResourceMode 368 case addrs.DataResourceMode: 369 current.Mode = DataResourceMode 370 default: 371 return ret, fmt.Errorf("resource %s has an unsupported mode %s", 372 resAddr.String(), 373 resAddr.Mode.String(), 374 ) 375 } 376 377 schema, version := schemas.ResourceTypeConfig( 378 r.ProviderConfig.Provider, 379 resAddr.Mode, 380 resAddr.Type, 381 ) 382 383 // It is possible that the only instance is deposed 384 if ri.Current != nil { 385 if version != ri.Current.SchemaVersion { 386 return nil, fmt.Errorf("schema version %d for %s in state does not match version %d from the provider", ri.Current.SchemaVersion, resAddr, version) 387 } 388 389 current.SchemaVersion = ri.Current.SchemaVersion 390 391 if schema == nil { 392 return nil, fmt.Errorf("no schema found for %s (in provider %s)", resAddr.String(), r.ProviderConfig.Provider) 393 } 394 riObj, err := ri.Current.Decode(schema.ImpliedType()) 395 if err != nil { 396 return nil, err 397 } 398 399 current.AttributeValues = marshalAttributeValues(riObj.Value) 400 401 s := SensitiveAsBool(riObj.Value) 402 v, err := ctyjson.Marshal(s, s.Type()) 403 if err != nil { 404 return nil, err 405 } 406 current.SensitiveValues = v 407 408 if len(riObj.Dependencies) > 0 { 409 dependencies := make([]string, len(riObj.Dependencies)) 410 for i, v := range riObj.Dependencies { 411 dependencies[i] = v.String() 412 } 413 current.DependsOn = dependencies 414 } 415 416 if riObj.Status == states.ObjectTainted { 417 current.Tainted = true 418 } 419 ret = append(ret, current) 420 } 421 422 var sortedDeposedKeys []string 423 for k := range ri.Deposed { 424 sortedDeposedKeys = append(sortedDeposedKeys, string(k)) 425 } 426 sort.Strings(sortedDeposedKeys) 427 428 for _, deposedKey := range sortedDeposedKeys { 429 rios := ri.Deposed[states.DeposedKey(deposedKey)] 430 431 // copy the base fields from the current instance 432 deposed := Resource{ 433 Address: current.Address, 434 Type: current.Type, 435 Name: current.Name, 436 ProviderName: current.ProviderName, 437 Mode: current.Mode, 438 Index: current.Index, 439 } 440 441 riObj, err := rios.Decode(schema.ImpliedType()) 442 if err != nil { 443 return nil, err 444 } 445 446 deposed.AttributeValues = marshalAttributeValues(riObj.Value) 447 448 s := SensitiveAsBool(riObj.Value) 449 v, err := ctyjson.Marshal(s, s.Type()) 450 if err != nil { 451 return nil, err 452 } 453 deposed.SensitiveValues = v 454 455 if len(riObj.Dependencies) > 0 { 456 dependencies := make([]string, len(riObj.Dependencies)) 457 for i, v := range riObj.Dependencies { 458 dependencies[i] = v.String() 459 } 460 deposed.DependsOn = dependencies 461 } 462 463 if riObj.Status == states.ObjectTainted { 464 deposed.Tainted = true 465 } 466 deposed.DeposedKey = deposedKey 467 ret = append(ret, deposed) 468 } 469 } 470 } 471 472 return ret, nil 473 } 474 475 func SensitiveAsBool(val cty.Value) cty.Value { 476 if val.HasMark(marks.Sensitive) { 477 return cty.True 478 } 479 480 ty := val.Type() 481 switch { 482 case val.IsNull(), ty.IsPrimitiveType(), ty.Equals(cty.DynamicPseudoType): 483 return cty.False 484 case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): 485 if !val.IsKnown() { 486 // If the collection is unknown we can't say anything about the 487 // sensitivity of its contents 488 return cty.EmptyTupleVal 489 } 490 length := val.LengthInt() 491 if length == 0 { 492 // If there are no elements then we can't have sensitive values 493 return cty.EmptyTupleVal 494 } 495 vals := make([]cty.Value, 0, length) 496 it := val.ElementIterator() 497 for it.Next() { 498 _, v := it.Element() 499 vals = append(vals, SensitiveAsBool(v)) 500 } 501 // The above transform may have changed the types of some of the 502 // elements, so we'll always use a tuple here in case we've now made 503 // different elements have different types. Our ultimate goal is to 504 // marshal to JSON anyway, and all of these sequence types are 505 // indistinguishable in JSON. 506 return cty.TupleVal(vals) 507 case ty.IsMapType() || ty.IsObjectType(): 508 if !val.IsKnown() { 509 // If the map/object is unknown we can't say anything about the 510 // sensitivity of its attributes 511 return cty.EmptyObjectVal 512 } 513 var length int 514 switch { 515 case ty.IsMapType(): 516 length = val.LengthInt() 517 default: 518 length = len(val.Type().AttributeTypes()) 519 } 520 if length == 0 { 521 // If there are no elements then we can't have sensitive values 522 return cty.EmptyObjectVal 523 } 524 vals := make(map[string]cty.Value) 525 it := val.ElementIterator() 526 for it.Next() { 527 k, v := it.Element() 528 s := SensitiveAsBool(v) 529 // Omit all of the "false"s for non-sensitive values for more 530 // compact serialization 531 if !s.RawEquals(cty.False) { 532 vals[k.AsString()] = s 533 } 534 } 535 // The above transform may have changed the types of some of the 536 // elements, so we'll always use an object here in case we've now made 537 // different elements have different types. Our ultimate goal is to 538 // marshal to JSON anyway, and all of these mapping types are 539 // indistinguishable in JSON. 540 return cty.ObjectVal(vals) 541 default: 542 // Should never happen, since the above should cover all types 543 panic(fmt.Sprintf("sensitiveAsBool cannot handle %#v", val)) 544 } 545 }