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