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