github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/configs/config_build.go (about)

     1  package configs
     2  
     3  import (
     4  	"sort"
     5  
     6  	version "github.com/hashicorp/go-version"
     7  	"github.com/hashicorp/hcl/v2"
     8  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     9  )
    10  
    11  // BuildConfig constructs a Config from a root module by loading all of its
    12  // descendent modules via the given ModuleWalker.
    13  //
    14  // The result is a module tree that has so far only had basic module- and
    15  // file-level invariants validated. If the returned diagnostics contains errors,
    16  // the returned module tree may be incomplete but can still be used carefully
    17  // for static analysis.
    18  func BuildConfig(root *Module, walker ModuleWalker) (*Config, hcl.Diagnostics) {
    19  	var diags hcl.Diagnostics
    20  	cfg := &Config{
    21  		Module: root,
    22  	}
    23  	cfg.Root = cfg // Root module is self-referential.
    24  	cfg.Children, diags = buildChildModules(cfg, walker)
    25  	return cfg, diags
    26  }
    27  
    28  func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, hcl.Diagnostics) {
    29  	var diags hcl.Diagnostics
    30  	ret := map[string]*Config{}
    31  
    32  	calls := parent.Module.ModuleCalls
    33  
    34  	// We'll sort the calls by their local names so that they'll appear in a
    35  	// predictable order in any logging that's produced during the walk.
    36  	callNames := make([]string, 0, len(calls))
    37  	for k := range calls {
    38  		callNames = append(callNames, k)
    39  	}
    40  	sort.Strings(callNames)
    41  
    42  	for _, callName := range callNames {
    43  		call := calls[callName]
    44  		path := make([]string, len(parent.Path)+1)
    45  		copy(path, parent.Path)
    46  		path[len(path)-1] = call.Name
    47  
    48  		req := ModuleRequest{
    49  			Name:              call.Name,
    50  			Path:              path,
    51  			SourceAddr:        call.SourceAddr,
    52  			SourceAddrRange:   call.SourceAddrRange,
    53  			VersionConstraint: call.Version,
    54  			Parent:            parent,
    55  			CallRange:         call.DeclRange,
    56  		}
    57  
    58  		mod, ver, modDiags := walker.LoadModule(&req)
    59  		diags = append(diags, modDiags...)
    60  		if mod == nil {
    61  			// nil can be returned if the source address was invalid and so
    62  			// nothing could be loaded whatsoever. LoadModule should've
    63  			// returned at least one error diagnostic in that case.
    64  			continue
    65  		}
    66  
    67  		child := &Config{
    68  			Parent:          parent,
    69  			Root:            parent.Root,
    70  			Path:            path,
    71  			Module:          mod,
    72  			CallRange:       call.DeclRange,
    73  			SourceAddr:      call.SourceAddr,
    74  			SourceAddrRange: call.SourceAddrRange,
    75  			Version:         ver,
    76  		}
    77  
    78  		child.Children, modDiags = buildChildModules(child, walker)
    79  		diags = append(diags, modDiags...)
    80  
    81  		ret[call.Name] = child
    82  	}
    83  
    84  	return ret, diags
    85  }
    86  
    87  // A ModuleWalker knows how to find and load a child module given details about
    88  // the module to be loaded and a reference to its partially-loaded parent
    89  // Config.
    90  type ModuleWalker interface {
    91  	// LoadModule finds and loads a requested child module.
    92  	//
    93  	// If errors are detected during loading, implementations should return them
    94  	// in the diagnostics object. If the diagnostics object contains any errors
    95  	// then the caller will tolerate the returned module being nil or incomplete.
    96  	// If no errors are returned, it should be non-nil and complete.
    97  	//
    98  	// Full validation need not have been performed but an implementation should
    99  	// ensure that the basic file- and module-validations performed by the
   100  	// LoadConfigDir function (valid syntax, no namespace collisions, etc) have
   101  	// been performed before returning a module.
   102  	LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
   103  }
   104  
   105  // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
   106  // a callback function, for more convenient use of that interface.
   107  type ModuleWalkerFunc func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
   108  
   109  // LoadModule implements ModuleWalker.
   110  func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
   111  	return f(req)
   112  }
   113  
   114  // ModuleRequest is used with the ModuleWalker interface to describe a child
   115  // module that must be loaded.
   116  type ModuleRequest struct {
   117  	// Name is the "logical name" of the module call within configuration.
   118  	// This is provided in case the name is used as part of a storage key
   119  	// for the module, but implementations must otherwise treat it as an
   120  	// opaque string. It is guaranteed to have already been validated as an
   121  	// HCL identifier and UTF-8 encoded.
   122  	Name string
   123  
   124  	// Path is a list of logical names that traverse from the root module to
   125  	// this module. This can be used, for example, to form a lookup key for
   126  	// each distinct module call in a configuration, allowing for multiple
   127  	// calls with the same name at different points in the tree.
   128  	Path addrs.Module
   129  
   130  	// SourceAddr is the source address string provided by the user in
   131  	// configuration.
   132  	SourceAddr string
   133  
   134  	// SourceAddrRange is the source range for the SourceAddr value as it
   135  	// was provided in configuration. This can and should be used to generate
   136  	// diagnostics about the source address having invalid syntax, referring
   137  	// to a non-existent object, etc.
   138  	SourceAddrRange hcl.Range
   139  
   140  	// VersionConstraint is the version constraint applied to the module in
   141  	// configuration. This data structure includes the source range for
   142  	// the constraint, which can and should be used to generate diagnostics
   143  	// about constraint-related issues, such as constraints that eliminate all
   144  	// available versions of a module whose source is otherwise valid.
   145  	VersionConstraint VersionConstraint
   146  
   147  	// Parent is the partially-constructed module tree node that the loaded
   148  	// module will be added to. Callers may refer to any field of this
   149  	// structure except Children, which is still under construction when
   150  	// ModuleRequest objects are created and thus has undefined content.
   151  	// The main reason this is provided is so that full module paths can
   152  	// be constructed for uniqueness.
   153  	Parent *Config
   154  
   155  	// CallRange is the source range for the header of the "module" block
   156  	// in configuration that prompted this request. This can be used as the
   157  	// subject of an error diagnostic that relates to the module call itself,
   158  	// rather than to either its source address or its version number.
   159  	CallRange hcl.Range
   160  }