github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/configupgrade/analysis.go (about) 1 package configupgrade 2 3 import ( 4 "fmt" 5 "log" 6 "strings" 7 8 hcl1 "github.com/hashicorp/hcl" 9 hcl1ast "github.com/hashicorp/hcl/hcl/ast" 10 hcl1parser "github.com/hashicorp/hcl/hcl/parser" 11 hcl1token "github.com/hashicorp/hcl/hcl/token" 12 13 "github.com/hashicorp/terraform/addrs" 14 "github.com/hashicorp/terraform/configs/configschema" 15 "github.com/hashicorp/terraform/moduledeps" 16 "github.com/hashicorp/terraform/plugin/discovery" 17 "github.com/hashicorp/terraform/terraform" 18 ) 19 20 // analysis is a container for the various different information gathered 21 // by Upgrader.analyze. 22 type analysis struct { 23 ProviderSchemas map[string]*terraform.ProviderSchema 24 ProvisionerSchemas map[string]*configschema.Block 25 ResourceProviderType map[addrs.Resource]string 26 ResourceHasCount map[addrs.Resource]bool 27 VariableTypes map[string]string 28 ModuleDir string 29 } 30 31 // analyze processes the configuration files included inside the receiver 32 // and returns an assortment of information required to make decisions during 33 // a configuration upgrade. 34 func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) { 35 ret := &analysis{ 36 ProviderSchemas: make(map[string]*terraform.ProviderSchema), 37 ProvisionerSchemas: make(map[string]*configschema.Block), 38 ResourceProviderType: make(map[addrs.Resource]string), 39 ResourceHasCount: make(map[addrs.Resource]bool), 40 VariableTypes: make(map[string]string), 41 } 42 43 m := &moduledeps.Module{ 44 Providers: make(moduledeps.Providers), 45 } 46 47 // This is heavily based on terraform.ModuleTreeDependencies but 48 // differs in that it works directly with the HCL1 AST rather than 49 // the legacy config structs (and can thus outlive those) and that 50 // it only works on one module at a time, and so doesn't need to 51 // recurse into child calls. 52 for name, src := range ms { 53 if ext := fileExt(name); ext != ".tf" { 54 continue 55 } 56 57 log.Printf("[TRACE] configupgrade: Analyzing %q", name) 58 59 f, err := hcl1parser.Parse(src) 60 if err != nil { 61 // If we encounter a syntax error then we'll just skip for now 62 // and assume that we'll catch this again when we do the upgrade. 63 // If not, we'll break the upgrade step of renaming .tf files to 64 // .tf.json if they seem to be JSON syntax. 65 log.Printf("[ERROR] Failed to parse %q: %s", name, err) 66 continue 67 } 68 69 list, ok := f.Node.(*hcl1ast.ObjectList) 70 if !ok { 71 return nil, fmt.Errorf("error parsing: file doesn't contain a root object") 72 } 73 74 if providersList := list.Filter("provider"); len(providersList.Items) > 0 { 75 providerObjs := providersList.Children() 76 for _, providerObj := range providerObjs.Items { 77 if len(providerObj.Keys) != 1 { 78 return nil, fmt.Errorf("provider block has wrong number of labels") 79 } 80 name := providerObj.Keys[0].Token.Value().(string) 81 82 var listVal *hcl1ast.ObjectList 83 if ot, ok := providerObj.Val.(*hcl1ast.ObjectType); ok { 84 listVal = ot.List 85 } else { 86 return nil, fmt.Errorf("provider %q: must be a block", name) 87 } 88 89 var versionStr string 90 if a := listVal.Filter("version"); len(a.Items) > 0 { 91 err := hcl1.DecodeObject(&versionStr, a.Items[0].Val) 92 if err != nil { 93 return nil, fmt.Errorf("Error reading version for provider %q: %s", name, err) 94 } 95 } 96 var constraints discovery.Constraints 97 if versionStr != "" { 98 constraints, err = discovery.ConstraintStr(versionStr).Parse() 99 if err != nil { 100 return nil, fmt.Errorf("Error parsing version for provider %q: %s", name, err) 101 } 102 } 103 104 var alias string 105 if a := listVal.Filter("alias"); len(a.Items) > 0 { 106 err := hcl1.DecodeObject(&alias, a.Items[0].Val) 107 if err != nil { 108 return nil, fmt.Errorf("Error reading alias for provider %q: %s", name, err) 109 } 110 } 111 112 inst := moduledeps.ProviderInstance(name) 113 if alias != "" { 114 inst = moduledeps.ProviderInstance(name + "." + alias) 115 } 116 log.Printf("[TRACE] Provider block requires provider %q", inst) 117 m.Providers[inst] = moduledeps.ProviderDependency{ 118 Constraints: constraints, 119 Reason: moduledeps.ProviderDependencyExplicit, 120 } 121 } 122 } 123 124 { 125 resourceConfigsList := list.Filter("resource") 126 dataResourceConfigsList := list.Filter("data") 127 // list.Filter annoyingly strips off the key used for matching, 128 // so we'll put it back here so we can distinguish our two types 129 // of blocks below. 130 for _, obj := range resourceConfigsList.Items { 131 obj.Keys = append([]*hcl1ast.ObjectKey{ 132 {Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "resource"}}, 133 }, obj.Keys...) 134 } 135 for _, obj := range dataResourceConfigsList.Items { 136 obj.Keys = append([]*hcl1ast.ObjectKey{ 137 {Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "data"}}, 138 }, obj.Keys...) 139 } 140 // Now we can merge the two lists together, since we can distinguish 141 // them just by their keys[0]. 142 resourceConfigsList.Items = append(resourceConfigsList.Items, dataResourceConfigsList.Items...) 143 144 resourceObjs := resourceConfigsList.Children() 145 for _, resourceObj := range resourceObjs.Items { 146 if len(resourceObj.Keys) != 3 { 147 return nil, fmt.Errorf("resource or data block has wrong number of labels") 148 } 149 typeName := resourceObj.Keys[1].Token.Value().(string) 150 name := resourceObj.Keys[2].Token.Value().(string) 151 rAddr := addrs.Resource{ 152 Mode: addrs.ManagedResourceMode, 153 Type: typeName, 154 Name: name, 155 } 156 if resourceObj.Keys[0].Token.Value() == "data" { 157 rAddr.Mode = addrs.DataResourceMode 158 } 159 160 var listVal *hcl1ast.ObjectList 161 if ot, ok := resourceObj.Val.(*hcl1ast.ObjectType); ok { 162 listVal = ot.List 163 } else { 164 return nil, fmt.Errorf("config for %q must be a block", rAddr) 165 } 166 167 if o := listVal.Filter("count"); len(o.Items) > 0 { 168 ret.ResourceHasCount[rAddr] = true 169 } else { 170 ret.ResourceHasCount[rAddr] = false 171 } 172 173 var providerKey string 174 if o := listVal.Filter("provider"); len(o.Items) > 0 { 175 err := hcl1.DecodeObject(&providerKey, o.Items[0].Val) 176 if err != nil { 177 return nil, fmt.Errorf("Error reading provider for resource %s: %s", rAddr, err) 178 } 179 } 180 181 if providerKey == "" { 182 providerKey = rAddr.DefaultProviderConfig().StringCompact() 183 } 184 185 inst := moduledeps.ProviderInstance(providerKey) 186 log.Printf("[TRACE] Resource block for %s requires provider %q", rAddr, inst) 187 if _, exists := m.Providers[inst]; !exists { 188 m.Providers[inst] = moduledeps.ProviderDependency{ 189 Reason: moduledeps.ProviderDependencyImplicit, 190 } 191 } 192 ret.ResourceProviderType[rAddr] = inst.Type() 193 } 194 } 195 196 if variablesList := list.Filter("variable"); len(variablesList.Items) > 0 { 197 variableObjs := variablesList.Children() 198 for _, variableObj := range variableObjs.Items { 199 if len(variableObj.Keys) != 1 { 200 return nil, fmt.Errorf("variable block has wrong number of labels") 201 } 202 name := variableObj.Keys[0].Token.Value().(string) 203 204 var listVal *hcl1ast.ObjectList 205 if ot, ok := variableObj.Val.(*hcl1ast.ObjectType); ok { 206 listVal = ot.List 207 } else { 208 return nil, fmt.Errorf("variable %q: must be a block", name) 209 } 210 211 var typeStr string 212 if a := listVal.Filter("type"); len(a.Items) > 0 { 213 err := hcl1.DecodeObject(&typeStr, a.Items[0].Val) 214 if err != nil { 215 return nil, fmt.Errorf("Error reading type for variable %q: %s", name, err) 216 } 217 } else if a := listVal.Filter("default"); len(a.Items) > 0 { 218 switch a.Items[0].Val.(type) { 219 case *hcl1ast.ObjectType: 220 typeStr = "map" 221 case *hcl1ast.ListType: 222 typeStr = "list" 223 default: 224 typeStr = "string" 225 } 226 } else { 227 typeStr = "string" 228 } 229 230 ret.VariableTypes[name] = strings.TrimSpace(typeStr) 231 } 232 } 233 } 234 235 providerFactories, errs := u.Providers.ResolveProviders(m.PluginRequirements()) 236 if len(errs) > 0 { 237 var errorsMsg string 238 for _, err := range errs { 239 errorsMsg += fmt.Sprintf("\n- %s", err) 240 } 241 return nil, fmt.Errorf("error resolving providers:\n%s", errorsMsg) 242 } 243 244 for fqn, fn := range providerFactories { 245 log.Printf("[TRACE] Fetching schema from provider %q", fqn.LegacyString()) 246 provider, err := fn() 247 if err != nil { 248 return nil, fmt.Errorf("failed to load provider %q: %s", fqn.LegacyString(), err) 249 } 250 251 resp := provider.GetSchema() 252 if resp.Diagnostics.HasErrors() { 253 return nil, resp.Diagnostics.Err() 254 } 255 256 schema := &terraform.ProviderSchema{ 257 Provider: resp.Provider.Block, 258 ResourceTypes: map[string]*configschema.Block{}, 259 DataSources: map[string]*configschema.Block{}, 260 } 261 for t, s := range resp.ResourceTypes { 262 schema.ResourceTypes[t] = s.Block 263 } 264 for t, s := range resp.DataSources { 265 schema.DataSources[t] = s.Block 266 } 267 ret.ProviderSchemas[fqn.LegacyString()] = schema 268 } 269 270 for name, fn := range u.Provisioners { 271 log.Printf("[TRACE] Fetching schema from provisioner %q", name) 272 provisioner, err := fn() 273 if err != nil { 274 return nil, fmt.Errorf("failed to load provisioner %q: %s", name, err) 275 } 276 277 resp := provisioner.GetSchema() 278 if resp.Diagnostics.HasErrors() { 279 return nil, resp.Diagnostics.Err() 280 } 281 282 ret.ProvisionerSchemas[name] = resp.Provisioner 283 } 284 285 return ret, nil 286 }