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  }