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