github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/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/hashicorp/terraform/internal/addrs"
    11  	"github.com/hashicorp/terraform/internal/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  		if strings.TrimSpace(call.Version) != "" {
    47  			var err error
    48  			vc, err = version.NewConstraint(call.Version)
    49  			if err != nil {
    50  				diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
    51  					Severity: tfconfig.DiagError,
    52  					Summary:  "Invalid version constraint",
    53  					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),
    54  				}))
    55  				continue
    56  			}
    57  		}
    58  
    59  		sourceAddr, err := addrs.ParseModuleSource(call.Source)
    60  		if err != nil {
    61  			diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{
    62  				Severity: tfconfig.DiagError,
    63  				Summary:  "Invalid module source address",
    64  				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),
    65  			}))
    66  			// If we didn't have a valid source address then we can't continue
    67  			// down the module tree with this one.
    68  			continue
    69  		}
    70  
    71  		req := ModuleRequest{
    72  			Name:               call.Name,
    73  			Path:               path,
    74  			SourceAddr:         sourceAddr,
    75  			VersionConstraints: vc,
    76  			Parent:             parent,
    77  			CallPos:            call.Pos,
    78  		}
    79  
    80  		mod, ver, modDiags := walker.LoadModule(&req)
    81  		diags = append(diags, modDiags...)
    82  		if mod == nil {
    83  			// nil can be returned if the source address was invalid and so
    84  			// nothing could be loaded whatsoever. LoadModule should've
    85  			// returned at least one error diagnostic in that case.
    86  			continue
    87  		}
    88  
    89  		child := &Config{
    90  			Parent:     parent,
    91  			Root:       parent.Root,
    92  			Path:       path,
    93  			Module:     mod,
    94  			CallPos:    call.Pos,
    95  			SourceAddr: sourceAddr,
    96  			Version:    ver,
    97  		}
    98  
    99  		child.Children, modDiags = buildChildModules(child, walker)
   100  		diags = diags.Append(modDiags)
   101  
   102  		ret[call.Name] = child
   103  	}
   104  
   105  	return ret, diags
   106  }
   107  
   108  // ModuleRequest is used as part of the ModuleWalker interface used with
   109  // function BuildConfig.
   110  type ModuleRequest struct {
   111  	// Name is the "logical name" of the module call within configuration.
   112  	// This is provided in case the name is used as part of a storage key
   113  	// for the module, but implementations must otherwise treat it as an
   114  	// opaque string. It is guaranteed to have already been validated as an
   115  	// HCL identifier and UTF-8 encoded.
   116  	Name string
   117  
   118  	// Path is a list of logical names that traverse from the root module to
   119  	// this module. This can be used, for example, to form a lookup key for
   120  	// each distinct module call in a configuration, allowing for multiple
   121  	// calls with the same name at different points in the tree.
   122  	Path addrs.Module
   123  
   124  	// SourceAddr is the source address string provided by the user in
   125  	// configuration.
   126  	SourceAddr addrs.ModuleSource
   127  
   128  	// VersionConstraint is the version constraint applied to the module in
   129  	// configuration.
   130  	VersionConstraints version.Constraints
   131  
   132  	// Parent is the partially-constructed module tree node that the loaded
   133  	// module will be added to. Callers may refer to any field of this
   134  	// structure except Children, which is still under construction when
   135  	// ModuleRequest objects are created and thus has undefined content.
   136  	// The main reason this is provided is so that full module paths can
   137  	// be constructed for uniqueness.
   138  	Parent *Config
   139  
   140  	// CallRange is the source position for the header of the "module" block
   141  	// in configuration that prompted this request.
   142  	CallPos tfconfig.SourcePos
   143  }
   144  
   145  // ModuleWalker is an interface used with BuildConfig.
   146  type ModuleWalker interface {
   147  	LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
   148  }
   149  
   150  // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
   151  // a callback function, for more convenient use of that interface.
   152  type ModuleWalkerFunc func(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics)
   153  
   154  func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) {
   155  	return f(req)
   156  }