github.com/pulumi/terraform@v1.4.0/pkg/earlyconfig/config_build.go (about)

     1  package earlyconfig
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  
     8  	version "github.com/hashicorp/go-version"
     9  	"github.com/hashicorp/terraform-config-inspect/tfconfig"
    10  	"github.com/pulumi/terraform/pkg/addrs"
    11  	"github.com/pulumi/terraform/pkg/tfdiags"
    12  )
    13  
    14  // BuildConfig constructs a Config from a root module by loading all of its
    15  // descendent modules via the given ModuleWalker.
    16  func BuildConfig(root *tfconfig.Module, walker ModuleWalker) (*Config, tfdiags.Diagnostics) {
    17  	var diags tfdiags.Diagnostics
    18  	cfg := &Config{
    19  		Module: root,
    20  	}
    21  	cfg.Root = cfg // Root module is self-referential.
    22  	cfg.Children, diags = buildChildModules(cfg, walker)
    23  	return cfg, diags
    24  }
    25  
    26  func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, tfdiags.Diagnostics) {
    27  	var diags tfdiags.Diagnostics
    28  	ret := map[string]*Config{}
    29  	calls := parent.Module.ModuleCalls
    30  
    31  	// We'll sort the calls by their local names so that they'll appear in a
    32  	// predictable order in any logging that's produced during the walk.
    33  	callNames := make([]string, 0, len(calls))
    34  	for k := range calls {
    35  		callNames = append(callNames, k)
    36  	}
    37  	sort.Strings(callNames)
    38  
    39  	for _, callName := range callNames {
    40  		call := calls[callName]
    41  		path := make([]string, len(parent.Path)+1)
    42  		copy(path, parent.Path)
    43  		path[len(path)-1] = call.Name
    44  
    45  		var vc version.Constraints
    46  		haveVersionArg := false
    47  		if strings.TrimSpace(call.Version) != "" {
    48  			haveVersionArg = true
    49  
    50  			var err error
    51  			vc, err = version.NewConstraint(call.Version)
    52  			if err != nil {
    53  				diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
    54  					Severity: tfconfig.DiagError,
    55  					Summary:  "Invalid version constraint",
    56  					Detail:   fmt.Sprintf("Module %q (declared at %s line %d) has invalid version constraint %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Version, err),
    57  				}))
    58  				continue
    59  			}
    60  		}
    61  
    62  		var sourceAddr addrs.ModuleSource
    63  		var err error
    64  		if haveVersionArg {
    65  			sourceAddr, err = addrs.ParseModuleSourceRegistry(call.Source)
    66  		} else {
    67  			sourceAddr, err = addrs.ParseModuleSource(call.Source)
    68  		}
    69  		if err != nil {
    70  			if haveVersionArg {
    71  				diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
    72  					Severity: tfconfig.DiagError,
    73  					Summary:  "Invalid registry module source address",
    74  					Detail:   fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err),
    75  				}))
    76  			} else {
    77  				diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
    78  					Severity: tfconfig.DiagError,
    79  					Summary:  "Invalid module source address",
    80  					Detail:   fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err),
    81  				}))
    82  			}
    83  			// If we didn't have a valid source address then we can't continue
    84  			// down the module tree with this one.
    85  			continue
    86  		}
    87  
    88  		req := ModuleRequest{
    89  			Name:               call.Name,
    90  			Path:               path,
    91  			SourceAddr:         sourceAddr,
    92  			VersionConstraints: vc,
    93  			Parent:             parent,
    94  			CallPos:            call.Pos,
    95  		}
    96  
    97  		mod, ver, modDiags := walker.LoadModule(&req)
    98  		diags = append(diags, modDiags...)
    99  		if mod == nil {
   100  			// nil can be returned if the source address was invalid and so
   101  			// nothing could be loaded whatsoever. LoadModule should've
   102  			// returned at least one error diagnostic in that case.
   103  			continue
   104  		}
   105  
   106  		child := &Config{
   107  			Parent:     parent,
   108  			Root:       parent.Root,
   109  			Path:       path,
   110  			Module:     mod,
   111  			CallPos:    call.Pos,
   112  			SourceAddr: sourceAddr,
   113  			Version:    ver,
   114  		}
   115  
   116  		child.Children, modDiags = buildChildModules(child, walker)
   117  		diags = diags.Append(modDiags)
   118  
   119  		ret[call.Name] = child
   120  	}
   121  
   122  	return ret, diags
   123  }
   124  
   125  // ModuleRequest is used as part of the ModuleWalker interface used with
   126  // function BuildConfig.
   127  type ModuleRequest struct {
   128  	// Name is the "logical name" of the module call within configuration.
   129  	// This is provided in case the name is used as part of a storage key
   130  	// for the module, but implementations must otherwise treat it as an
   131  	// opaque string. It is guaranteed to have already been validated as an
   132  	// HCL identifier and UTF-8 encoded.
   133  	Name string
   134  
   135  	// Path is a list of logical names that traverse from the root module to
   136  	// this module. This can be used, for example, to form a lookup key for
   137  	// each distinct module call in a configuration, allowing for multiple
   138  	// calls with the same name at different points in the tree.
   139  	Path addrs.Module
   140  
   141  	// SourceAddr is the source address string provided by the user in
   142  	// configuration.
   143  	SourceAddr addrs.ModuleSource
   144  
   145  	// VersionConstraint is the version constraint applied to the module in
   146  	// configuration.
   147  	VersionConstraints version.Constraints
   148  
   149  	// Parent is the partially-constructed module tree node that the loaded
   150  	// module will be added to. Callers may refer to any field of this
   151  	// structure except Children, which is still under construction when
   152  	// ModuleRequest objects are created and thus has undefined content.
   153  	// The main reason this is provided is so that full module paths can
   154  	// be constructed for uniqueness.
   155  	Parent *Config
   156  
   157  	// CallRange is the source position for the header of the "module" block
   158  	// in configuration that prompted this request.
   159  	CallPos tfconfig.SourcePos
   160  }
   161  
   162  // ModuleWalker is an interface used with BuildConfig.
   163  type ModuleWalker interface {
   164  	LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
   165  }
   166  
   167  // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
   168  // a callback function, for more convenient use of that interface.
   169  type ModuleWalkerFunc func(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
   170  
   171  func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
   172  	return f(req)
   173  }