github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonplan/values.go (about) 1 package jsonplan 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/jsonstate" 13 "github.com/hashicorp/terraform/internal/configs/configschema" 14 "github.com/hashicorp/terraform/internal/plans" 15 "github.com/hashicorp/terraform/internal/states" 16 "github.com/hashicorp/terraform/internal/terraform" 17 ) 18 19 // stateValues is the common representation of resolved values for both the 20 // prior state (which is always complete) and the planned new state. 21 type stateValues struct { 22 Outputs map[string]output `json:"outputs,omitempty"` 23 RootModule module `json:"root_module,omitempty"` 24 } 25 26 // attributeValues is the JSON representation of the attribute values of the 27 // resource, whose structure depends on the resource type schema. 28 type attributeValues map[string]interface{} 29 30 func marshalAttributeValues(value cty.Value, schema *configschema.Block) attributeValues { 31 if value == cty.NilVal || value.IsNull() { 32 return nil 33 } 34 ret := make(attributeValues) 35 36 it := value.ElementIterator() 37 for it.Next() { 38 k, v := it.Element() 39 vJSON, _ := ctyjson.Marshal(v, v.Type()) 40 ret[k.AsString()] = json.RawMessage(vJSON) 41 } 42 return ret 43 } 44 45 // marshalPlannedOutputs takes a list of changes and returns a map of output 46 // values 47 func marshalPlannedOutputs(changes *plans.Changes) (map[string]output, error) { 48 if changes.Outputs == nil { 49 // No changes - we're done here! 50 return nil, nil 51 } 52 53 ret := make(map[string]output) 54 55 for _, oc := range changes.Outputs { 56 if oc.ChangeSrc.Action == plans.Delete { 57 continue 58 } 59 60 var after, afterType []byte 61 changeV, err := oc.Decode() 62 if err != nil { 63 return ret, err 64 } 65 // The values may be marked, but we must rely on the Sensitive flag 66 // as the decoded value is only an intermediate step in transcoding 67 // this to a json format. 68 changeV.After, _ = changeV.After.UnmarkDeep() 69 70 if changeV.After != cty.NilVal && changeV.After.IsWhollyKnown() { 71 ty := changeV.After.Type() 72 after, err = ctyjson.Marshal(changeV.After, ty) 73 if err != nil { 74 return ret, err 75 } 76 afterType, err = ctyjson.MarshalType(ty) 77 if err != nil { 78 return ret, err 79 } 80 } 81 82 ret[oc.Addr.OutputValue.Name] = output{ 83 Value: json.RawMessage(after), 84 Type: json.RawMessage(afterType), 85 Sensitive: oc.Sensitive, 86 } 87 } 88 89 return ret, nil 90 91 } 92 93 func marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) (module, error) { 94 var ret module 95 96 // build two maps: 97 // module name -> [resource addresses] 98 // module -> [children modules] 99 moduleResourceMap := make(map[string][]addrs.AbsResourceInstance) 100 moduleMap := make(map[string][]addrs.ModuleInstance) 101 seenModules := make(map[string]bool) 102 103 for _, resource := range changes.Resources { 104 // If the resource is being deleted, skip over it. 105 // Deposed instances are always conceptually a destroy, but if they 106 // were gone during refresh then the change becomes a noop. 107 if resource.Action != plans.Delete && resource.DeposedKey == states.NotDeposed { 108 containingModule := resource.Addr.Module.String() 109 moduleResourceMap[containingModule] = append(moduleResourceMap[containingModule], resource.Addr) 110 111 // the root module has no parents 112 if !resource.Addr.Module.IsRoot() { 113 parent := resource.Addr.Module.Parent().String() 114 // we expect to see multiple resources in one module, so we 115 // only need to report the "parent" module for each child module 116 // once. 117 if !seenModules[containingModule] { 118 moduleMap[parent] = append(moduleMap[parent], resource.Addr.Module) 119 seenModules[containingModule] = true 120 } 121 122 // If any given parent module has no resources, it needs to be 123 // added to the moduleMap. This walks through the current 124 // resources' modules' ancestors, taking advantage of the fact 125 // that Ancestors() returns an ordered slice, and verifies that 126 // each one is in the map. 127 ancestors := resource.Addr.Module.Ancestors() 128 for i, ancestor := range ancestors[:len(ancestors)-1] { 129 aStr := ancestor.String() 130 131 // childStr here is the immediate child of the current step 132 childStr := ancestors[i+1].String() 133 // we likely will see multiple resources in one module, so we 134 // only need to report the "parent" module for each child module 135 // once. 136 if !seenModules[childStr] { 137 moduleMap[aStr] = append(moduleMap[aStr], ancestors[i+1]) 138 seenModules[childStr] = true 139 } 140 } 141 } 142 } 143 } 144 145 // start with the root module 146 resources, err := marshalPlanResources(changes, moduleResourceMap[""], schemas) 147 if err != nil { 148 return ret, err 149 } 150 ret.Resources = resources 151 152 childModules, err := marshalPlanModules(changes, schemas, moduleMap[""], moduleMap, moduleResourceMap) 153 if err != nil { 154 return ret, err 155 } 156 sort.Slice(childModules, func(i, j int) bool { 157 return childModules[i].Address < childModules[j].Address 158 }) 159 160 ret.ChildModules = childModules 161 162 return ret, nil 163 } 164 165 // marshalPlanResources 166 func marshalPlanResources(changes *plans.Changes, ris []addrs.AbsResourceInstance, schemas *terraform.Schemas) ([]resource, error) { 167 var ret []resource 168 169 for _, ri := range ris { 170 r := changes.ResourceInstance(ri) 171 if r.Action == plans.Delete { 172 continue 173 } 174 175 resource := resource{ 176 Address: r.Addr.String(), 177 Type: r.Addr.Resource.Resource.Type, 178 Name: r.Addr.Resource.Resource.Name, 179 ProviderName: r.ProviderAddr.Provider.String(), 180 Index: r.Addr.Resource.Key, 181 } 182 183 switch r.Addr.Resource.Resource.Mode { 184 case addrs.ManagedResourceMode: 185 resource.Mode = "managed" 186 case addrs.DataResourceMode: 187 resource.Mode = "data" 188 default: 189 return nil, fmt.Errorf("resource %s has an unsupported mode %s", 190 r.Addr.String(), 191 r.Addr.Resource.Resource.Mode.String(), 192 ) 193 } 194 195 schema, schemaVer := schemas.ResourceTypeConfig( 196 r.ProviderAddr.Provider, 197 r.Addr.Resource.Resource.Mode, 198 resource.Type, 199 ) 200 if schema == nil { 201 return nil, fmt.Errorf("no schema found for %s", r.Addr.String()) 202 } 203 resource.SchemaVersion = schemaVer 204 changeV, err := r.Decode(schema.ImpliedType()) 205 if err != nil { 206 return nil, err 207 } 208 209 // copy the marked After values so we can use these in marshalSensitiveValues 210 markedAfter := changeV.After 211 212 // The values may be marked, but we must rely on the Sensitive flag 213 // as the decoded value is only an intermediate step in transcoding 214 // this to a json format. 215 changeV.Before, _ = changeV.Before.UnmarkDeep() 216 changeV.After, _ = changeV.After.UnmarkDeep() 217 218 if changeV.After != cty.NilVal { 219 if changeV.After.IsWhollyKnown() { 220 resource.AttributeValues = marshalAttributeValues(changeV.After, schema) 221 } else { 222 knowns := omitUnknowns(changeV.After) 223 resource.AttributeValues = marshalAttributeValues(knowns, schema) 224 } 225 } 226 227 s := jsonstate.SensitiveAsBool(markedAfter) 228 v, err := ctyjson.Marshal(s, s.Type()) 229 if err != nil { 230 return nil, err 231 } 232 resource.SensitiveValues = v 233 234 ret = append(ret, resource) 235 } 236 237 sort.Slice(ret, func(i, j int) bool { 238 return ret[i].Address < ret[j].Address 239 }) 240 241 return ret, nil 242 } 243 244 // marshalPlanModules iterates over a list of modules to recursively describe 245 // the full module tree. 246 func marshalPlanModules( 247 changes *plans.Changes, 248 schemas *terraform.Schemas, 249 childModules []addrs.ModuleInstance, 250 moduleMap map[string][]addrs.ModuleInstance, 251 moduleResourceMap map[string][]addrs.AbsResourceInstance, 252 ) ([]module, error) { 253 254 var ret []module 255 256 for _, child := range childModules { 257 moduleResources := moduleResourceMap[child.String()] 258 // cm for child module, naming things is hard. 259 var cm module 260 // don't populate the address for the root module 261 if child.String() != "" { 262 cm.Address = child.String() 263 } 264 rs, err := marshalPlanResources(changes, moduleResources, schemas) 265 if err != nil { 266 return nil, err 267 } 268 cm.Resources = rs 269 270 if len(moduleMap[child.String()]) > 0 { 271 moreChildModules, err := marshalPlanModules(changes, schemas, moduleMap[child.String()], moduleMap, moduleResourceMap) 272 if err != nil { 273 return nil, err 274 } 275 cm.ChildModules = moreChildModules 276 } 277 278 ret = append(ret, cm) 279 } 280 281 return ret, nil 282 }