kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/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  	"kubeform.dev/terraform-backend-sdk/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  
    26  	// Now that the config is built, we can connect the provider names to all
    27  	// the known types for validation.
    28  	cfg.resolveProviderTypes()
    29  
    30  	diags = append(diags, validateProviderConfigs(nil, cfg, false)...)
    31  
    32  	return cfg, diags
    33  }
    34  
    35  func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, hcl.Diagnostics) {
    36  	var diags hcl.Diagnostics
    37  	ret := map[string]*Config{}
    38  
    39  	calls := parent.Module.ModuleCalls
    40  
    41  	// We'll sort the calls by their local names so that they'll appear in a
    42  	// predictable order in any logging that's produced during the walk.
    43  	callNames := make([]string, 0, len(calls))
    44  	for k := range calls {
    45  		callNames = append(callNames, k)
    46  	}
    47  	sort.Strings(callNames)
    48  
    49  	for _, callName := range callNames {
    50  		call := calls[callName]
    51  		path := make([]string, len(parent.Path)+1)
    52  		copy(path, parent.Path)
    53  		path[len(path)-1] = call.Name
    54  
    55  		req := ModuleRequest{
    56  			Name:              call.Name,
    57  			Path:              path,
    58  			SourceAddr:        call.SourceAddr,
    59  			SourceAddrRange:   call.SourceAddrRange,
    60  			VersionConstraint: call.Version,
    61  			Parent:            parent,
    62  			CallRange:         call.DeclRange,
    63  		}
    64  
    65  		mod, ver, modDiags := walker.LoadModule(&req)
    66  		diags = append(diags, modDiags...)
    67  		if mod == nil {
    68  			// nil can be returned if the source address was invalid and so
    69  			// nothing could be loaded whatsoever. LoadModule should've
    70  			// returned at least one error diagnostic in that case.
    71  			continue
    72  		}
    73  
    74  		child := &Config{
    75  			Parent:          parent,
    76  			Root:            parent.Root,
    77  			Path:            path,
    78  			Module:          mod,
    79  			CallRange:       call.DeclRange,
    80  			SourceAddr:      call.SourceAddr,
    81  			SourceAddrRange: call.SourceAddrRange,
    82  			Version:         ver,
    83  		}
    84  
    85  		child.Children, modDiags = buildChildModules(child, walker)
    86  		diags = append(diags, modDiags...)
    87  
    88  		if mod.Backend != nil {
    89  			diags = diags.Append(&hcl.Diagnostic{
    90  				Severity: hcl.DiagWarning,
    91  				Summary:  "Backend configuration ignored",
    92  				Detail:   "Any selected backend applies to the entire configuration, so Terraform expects provider configurations only in the root module.\n\nThis is a warning rather than an error because it's sometimes convenient to temporarily call a root module as a child module for testing purposes, but this backend configuration block will have no effect.",
    93  				Subject:  mod.Backend.DeclRange.Ptr(),
    94  			})
    95  		}
    96  
    97  		ret[call.Name] = child
    98  	}
    99  
   100  	return ret, diags
   101  }
   102  
   103  // A ModuleWalker knows how to find and load a child module given details about
   104  // the module to be loaded and a reference to its partially-loaded parent
   105  // Config.
   106  type ModuleWalker interface {
   107  	// LoadModule finds and loads a requested child module.
   108  	//
   109  	// If errors are detected during loading, implementations should return them
   110  	// in the diagnostics object. If the diagnostics object contains any errors
   111  	// then the caller will tolerate the returned module being nil or incomplete.
   112  	// If no errors are returned, it should be non-nil and complete.
   113  	//
   114  	// Full validation need not have been performed but an implementation should
   115  	// ensure that the basic file- and module-validations performed by the
   116  	// LoadConfigDir function (valid syntax, no namespace collisions, etc) have
   117  	// been performed before returning a module.
   118  	LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
   119  }
   120  
   121  // ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
   122  // a callback function, for more convenient use of that interface.
   123  type ModuleWalkerFunc func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
   124  
   125  // LoadModule implements ModuleWalker.
   126  func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
   127  	return f(req)
   128  }
   129  
   130  // ModuleRequest is used with the ModuleWalker interface to describe a child
   131  // module that must be loaded.
   132  type ModuleRequest struct {
   133  	// Name is the "logical name" of the module call within configuration.
   134  	// This is provided in case the name is used as part of a storage key
   135  	// for the module, but implementations must otherwise treat it as an
   136  	// opaque string. It is guaranteed to have already been validated as an
   137  	// HCL identifier and UTF-8 encoded.
   138  	Name string
   139  
   140  	// Path is a list of logical names that traverse from the root module to
   141  	// this module. This can be used, for example, to form a lookup key for
   142  	// each distinct module call in a configuration, allowing for multiple
   143  	// calls with the same name at different points in the tree.
   144  	Path addrs.Module
   145  
   146  	// SourceAddr is the source address string provided by the user in
   147  	// configuration.
   148  	SourceAddr addrs.ModuleSource
   149  
   150  	// SourceAddrRange is the source range for the SourceAddr value as it
   151  	// was provided in configuration. This can and should be used to generate
   152  	// diagnostics about the source address having invalid syntax, referring
   153  	// to a non-existent object, etc.
   154  	SourceAddrRange hcl.Range
   155  
   156  	// VersionConstraint is the version constraint applied to the module in
   157  	// configuration. This data structure includes the source range for
   158  	// the constraint, which can and should be used to generate diagnostics
   159  	// about constraint-related issues, such as constraints that eliminate all
   160  	// available versions of a module whose source is otherwise valid.
   161  	VersionConstraint VersionConstraint
   162  
   163  	// Parent is the partially-constructed module tree node that the loaded
   164  	// module will be added to. Callers may refer to any field of this
   165  	// structure except Children, which is still under construction when
   166  	// ModuleRequest objects are created and thus has undefined content.
   167  	// The main reason this is provided is so that full module paths can
   168  	// be constructed for uniqueness.
   169  	Parent *Config
   170  
   171  	// CallRange is the source range for the header of the "module" block
   172  	// in configuration that prompted this request. This can be used as the
   173  	// subject of an error diagnostic that relates to the module call itself,
   174  	// rather than to either its source address or its version number.
   175  	CallRange hcl.Range
   176  }
   177  
   178  // DisabledModuleWalker is a ModuleWalker that doesn't support
   179  // child modules at all, and so will return an error if asked to load one.
   180  //
   181  // This is provided primarily for testing. There is no good reason to use this
   182  // in the main application.
   183  var DisabledModuleWalker ModuleWalker
   184  
   185  func init() {
   186  	DisabledModuleWalker = ModuleWalkerFunc(func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
   187  		return nil, nil, hcl.Diagnostics{
   188  			{
   189  				Severity: hcl.DiagError,
   190  				Summary:  "Child modules are not supported",
   191  				Detail:   "Child module calls are not allowed in this context.",
   192  				Subject:  &req.CallRange,
   193  			},
   194  		}
   195  	})
   196  }