128 lines
4.7 KiB
Go
128 lines
4.7 KiB
Go
|
package configs
|
||
|
|
||
|
import (
|
||
|
version "github.com/hashicorp/go-version"
|
||
|
"github.com/hashicorp/hcl2/hcl"
|
||
|
)
|
||
|
|
||
|
// BuildConfig constructs a Config from a root module by loading all of its
|
||
|
// descendent modules via the given ModuleWalker.
|
||
|
//
|
||
|
// The result is a module tree that has so far only had basic module- and
|
||
|
// file-level invariants validated. If the returned diagnostics contains errors,
|
||
|
// the returned module tree may be incomplete but can still be used carefully
|
||
|
// for static analysis.
|
||
|
func BuildConfig(root *Module, walker ModuleWalker) (*Config, hcl.Diagnostics) {
|
||
|
var diags hcl.Diagnostics
|
||
|
cfg := &Config{
|
||
|
Module: root,
|
||
|
}
|
||
|
cfg.Root = cfg // Root module is self-referential.
|
||
|
cfg.Children, diags = buildChildModules(cfg, walker)
|
||
|
return cfg, diags
|
||
|
}
|
||
|
|
||
|
func buildChildModules(parent *Config, walker ModuleWalker) (map[string]*Config, hcl.Diagnostics) {
|
||
|
var diags hcl.Diagnostics
|
||
|
ret := map[string]*Config{}
|
||
|
|
||
|
calls := parent.Module.ModuleCalls
|
||
|
|
||
|
for _, call := range calls {
|
||
|
req := ModuleRequest{
|
||
|
Name: call.Name,
|
||
|
SourceAddr: call.SourceAddr,
|
||
|
SourceAddrRange: call.SourceAddrRange,
|
||
|
VersionConstraints: []VersionConstraint{call.Version},
|
||
|
Parent: parent,
|
||
|
}
|
||
|
|
||
|
mod, ver, modDiags := walker.LoadModule(&req)
|
||
|
diags = append(diags, modDiags...)
|
||
|
if mod == nil {
|
||
|
// nil can be returned if the source address was invalid and so
|
||
|
// nothing could be loaded whatsoever. LoadModule should've
|
||
|
// returned at least one error diagnostic in that case.
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
child := &Config{
|
||
|
Parent: parent,
|
||
|
Root: parent.Root,
|
||
|
Module: mod,
|
||
|
SourceAddr: call.SourceAddr,
|
||
|
SourceAddrRange: call.SourceAddrRange,
|
||
|
Version: ver,
|
||
|
}
|
||
|
|
||
|
child.Children, modDiags = buildChildModules(child, walker)
|
||
|
|
||
|
ret[call.Name] = child
|
||
|
}
|
||
|
|
||
|
return ret, diags
|
||
|
}
|
||
|
|
||
|
// A ModuleWalker knows how to find and load a child module given details about
|
||
|
// the module to be loaded and a reference to its partially-loaded parent
|
||
|
// Config.
|
||
|
type ModuleWalker interface {
|
||
|
// LoadModule finds and loads a requested child module.
|
||
|
//
|
||
|
// If errors are detected during loading, implementations should return them
|
||
|
// in the diagnostics object. If the diagnostics object contains any errors
|
||
|
// then the caller will tolerate the returned module being nil or incomplete.
|
||
|
// If no errors are returned, it should be non-nil and complete.
|
||
|
//
|
||
|
// Full validation need not have been performed but an implementation should
|
||
|
// ensure that the basic file- and module-validations performed by the
|
||
|
// LoadConfigDir function (valid syntax, no namespace collisions, etc) have
|
||
|
// been performed before returning a module.
|
||
|
LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
|
||
|
}
|
||
|
|
||
|
// ModuleWalkerFunc is an implementation of ModuleWalker that directly wraps
|
||
|
// a callback function, for more convenient use of that interface.
|
||
|
type ModuleWalkerFunc func(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics)
|
||
|
|
||
|
// LoadModule implements ModuleWalker.
|
||
|
func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*Module, *version.Version, hcl.Diagnostics) {
|
||
|
return f(req)
|
||
|
}
|
||
|
|
||
|
// ModuleRequest is used with the ModuleWalker interface to describe a child
|
||
|
// module that must be loaded.
|
||
|
type ModuleRequest struct {
|
||
|
// Name is the "logical name" of the module call within configuration.
|
||
|
// This is provided in case the name is used as part of a storage key
|
||
|
// for the module, but implementations must otherwise treat it as an
|
||
|
// opaque string. It is guaranteed to have already been validated as an
|
||
|
// HCL identifier and UTF-8 encoded.
|
||
|
Name string
|
||
|
|
||
|
// SourceAddr is the source address string provided by the user in
|
||
|
// configuration.
|
||
|
SourceAddr string
|
||
|
|
||
|
// SourceAddrRange is the source range for the SourceAddr value as it
|
||
|
// was provided in configuration. This can and should be used to generate
|
||
|
// diagnostics about the source address having invalid syntax, referring
|
||
|
// to a non-existent object, etc.
|
||
|
SourceAddrRange hcl.Range
|
||
|
|
||
|
// VersionConstraints are the constraints applied to the module in
|
||
|
// configuration. This data structure includes the source range for
|
||
|
// each constraint, which can and should be used to generate diagnostics
|
||
|
// about constraint-related issues, such as constraints that eliminate all
|
||
|
// available versions of a module whose source is otherwise valid.
|
||
|
VersionConstraints []VersionConstraint
|
||
|
|
||
|
// Parent is the partially-constructed module tree node that the loaded
|
||
|
// module will be added to. Callers may refer to any field of this
|
||
|
// structure except Children, which is still under construction when
|
||
|
// ModuleRequest objects are created and thus has undefined content.
|
||
|
// The main reason this is provided is so that full module paths can
|
||
|
// be constructed for uniqueness.
|
||
|
Parent *Config
|
||
|
}
|