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