From 60ea68edc7d4a7f1ab53fad4ba700631316ca252 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 3 Mar 2023 22:10:09 -0500 Subject: [PATCH] Remove earlyconfig --- internal/earlyconfig/config.go | 210 ------------------ internal/earlyconfig/config_build.go | 173 --------------- internal/earlyconfig/config_test.go | 84 ------- internal/earlyconfig/diagnostics.go | 82 ------- internal/earlyconfig/doc.go | 20 -- internal/earlyconfig/module.go | 13 -- .../child/provider-reqs-child.tf | 11 - .../provider-reqs/provider-reqs-root.tf | 21 -- 8 files changed, 614 deletions(-) delete mode 100644 internal/earlyconfig/config.go delete mode 100644 internal/earlyconfig/config_build.go delete mode 100644 internal/earlyconfig/config_test.go delete mode 100644 internal/earlyconfig/diagnostics.go delete mode 100644 internal/earlyconfig/doc.go delete mode 100644 internal/earlyconfig/module.go delete mode 100644 internal/earlyconfig/testdata/provider-reqs/child/provider-reqs-child.tf delete mode 100644 internal/earlyconfig/testdata/provider-reqs/provider-reqs-root.tf diff --git a/internal/earlyconfig/config.go b/internal/earlyconfig/config.go deleted file mode 100644 index 86d93c27ba..0000000000 --- a/internal/earlyconfig/config.go +++ /dev/null @@ -1,210 +0,0 @@ -package earlyconfig - -import ( - "fmt" - "sort" - - version "github.com/hashicorp/go-version" - "github.com/hashicorp/terraform-config-inspect/tfconfig" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/getproviders" - "github.com/hashicorp/terraform/internal/moduledeps" - "github.com/hashicorp/terraform/internal/plugin/discovery" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -// A Config is a node in the tree of modules within a configuration. -// -// The module tree is constructed by following ModuleCall instances recursively -// through the root module transitively into descendent modules. -type Config struct { - // RootModule points to the Config for the root module within the same - // module tree as this module. If this module _is_ the root module then - // this is self-referential. - Root *Config - - // ParentModule points to the Config for the module that directly calls - // this module. If this is the root module then this field is nil. - Parent *Config - - // Path is a sequence of module logical names that traverse from the root - // module to this config. Path is empty for the root module. - // - // This should only be used to display paths to the end-user in rare cases - // where we are talking about the static module tree, before module calls - // have been resolved. In most cases, an addrs.ModuleInstance describing - // a node in the dynamic module tree is better, since it will then include - // any keys resulting from evaluating "count" and "for_each" arguments. - Path addrs.Module - - // ChildModules points to the Config for each of the direct child modules - // called from this module. The keys in this map match the keys in - // Module.ModuleCalls. - Children map[string]*Config - - // Module points to the object describing the configuration for the - // various elements (variables, resources, etc) defined by this module. - Module *tfconfig.Module - - // CallPos is the source position for the header of the module block that - // requested this module. - // - // This field is meaningless for the root module, where its contents are undefined. - CallPos tfconfig.SourcePos - - // SourceAddr is the source address that the referenced module was requested - // from, as specified in configuration. - // - // This field is meaningless for the root module, where its contents are undefined. - SourceAddr addrs.ModuleSource - - // Version is the specific version that was selected for this module, - // based on version constraints given in configuration. - // - // This field is nil if the module was loaded from a non-registry source, - // since versions are not supported for other sources. - // - // This field is meaningless for the root module, where it will always - // be nil. - Version *version.Version -} - -// ProviderRequirements searches the full tree of modules under the receiver -// for both explicit and implicit dependencies on providers. -// -// The result is a full manifest of all of the providers that must be available -// in order to work with the receiving configuration. -// -// If the returned diagnostics includes errors then the resulting Requirements -// may be incomplete. -func (c *Config) ProviderRequirements() (getproviders.Requirements, tfdiags.Diagnostics) { - reqs := make(getproviders.Requirements) - diags := c.addProviderRequirements(reqs) - return reqs, diags -} - -// addProviderRequirements is the main part of the ProviderRequirements -// implementation, gradually mutating a shared requirements object to -// eventually return. -func (c *Config) addProviderRequirements(reqs getproviders.Requirements) tfdiags.Diagnostics { - var diags tfdiags.Diagnostics - - // First we'll deal with the requirements directly in _our_ module... - for localName, providerReqs := range c.Module.RequiredProviders { - var fqn addrs.Provider - if source := providerReqs.Source; source != "" { - addr, moreDiags := addrs.ParseProviderSourceString(source) - if moreDiags.HasErrors() { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider source address", - fmt.Sprintf("Invalid source %q for provider %q in %s", source, localName, c.Path), - )) - continue - } - fqn = addr - } - if fqn.IsZero() { - fqn = addrs.ImpliedProviderForUnqualifiedType(localName) - } - if _, ok := reqs[fqn]; !ok { - // We'll at least have an unconstrained dependency then, but might - // add to this in the loop below. - reqs[fqn] = nil - } - for _, constraintsStr := range providerReqs.VersionConstraints { - if constraintsStr != "" { - constraints, err := getproviders.ParseVersionConstraints(constraintsStr) - if err != nil { - diags = diags.Append(tfdiags.Sourceless( - tfdiags.Error, - "Invalid provider version constraint", - fmt.Sprintf("Provider %q in %s has invalid version constraint %q: %s.", localName, c.Path, constraintsStr, err), - )) - continue - } - reqs[fqn] = append(reqs[fqn], constraints...) - } - } - } - - // ...and now we'll recursively visit all of the child modules to merge - // in their requirements too. - for _, childConfig := range c.Children { - moreDiags := childConfig.addProviderRequirements(reqs) - diags = diags.Append(moreDiags) - } - - return diags -} - -// ProviderDependencies is a deprecated variant of ProviderRequirements which -// uses the moduledeps models for representation. This is preserved to allow -// a gradual transition over to ProviderRequirements, but note that its -// support for fully-qualified provider addresses has some idiosyncracies. -func (c *Config) ProviderDependencies() (*moduledeps.Module, tfdiags.Diagnostics) { - var diags tfdiags.Diagnostics - - var name string - if len(c.Path) > 0 { - name = c.Path[len(c.Path)-1] - } - - ret := &moduledeps.Module{ - Name: name, - } - - providers := make(moduledeps.Providers) - for name, reqs := range c.Module.RequiredProviders { - var fqn addrs.Provider - if source := reqs.Source; source != "" { - addr, parseDiags := addrs.ParseProviderSourceString(source) - if parseDiags.HasErrors() { - diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ - Severity: tfconfig.DiagError, - Summary: "Invalid provider source", - Detail: fmt.Sprintf("Invalid source %q for provider", name), - })) - continue - } - fqn = addr - } - if fqn.IsZero() { - fqn = addrs.NewDefaultProvider(name) - } - var constraints version.Constraints - for _, reqStr := range reqs.VersionConstraints { - if reqStr != "" { - constraint, err := version.NewConstraint(reqStr) - if err != nil { - diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ - Severity: tfconfig.DiagError, - Summary: "Invalid provider version constraint", - Detail: fmt.Sprintf("Invalid version constraint %q for provider %s.", reqStr, fqn.String()), - })) - continue - } - constraints = append(constraints, constraint...) - } - } - providers[fqn] = moduledeps.ProviderDependency{ - Constraints: discovery.NewConstraints(constraints), - Reason: moduledeps.ProviderDependencyExplicit, - } - } - ret.Providers = providers - - childNames := make([]string, 0, len(c.Children)) - for name := range c.Children { - childNames = append(childNames, name) - } - sort.Strings(childNames) - - for _, name := range childNames { - child, childDiags := c.Children[name].ProviderDependencies() - ret.Children = append(ret.Children, child) - diags = diags.Append(childDiags) - } - - return ret, diags -} diff --git a/internal/earlyconfig/config_build.go b/internal/earlyconfig/config_build.go deleted file mode 100644 index dd84cf9ccc..0000000000 --- a/internal/earlyconfig/config_build.go +++ /dev/null @@ -1,173 +0,0 @@ -package earlyconfig - -import ( - "fmt" - "sort" - "strings" - - version "github.com/hashicorp/go-version" - "github.com/hashicorp/terraform-config-inspect/tfconfig" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -// BuildConfig constructs a Config from a root module by loading all of its -// descendent modules via the given ModuleWalker. -func BuildConfig(root *tfconfig.Module, walker ModuleWalker) (*Config, tfdiags.Diagnostics) { - var diags tfdiags.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, tfdiags.Diagnostics) { - var diags tfdiags.Diagnostics - ret := map[string]*Config{} - calls := parent.Module.ModuleCalls - - // We'll sort the calls by their local names so that they'll appear in a - // predictable order in any logging that's produced during the walk. - callNames := make([]string, 0, len(calls)) - for k := range calls { - callNames = append(callNames, k) - } - sort.Strings(callNames) - - for _, callName := range callNames { - call := calls[callName] - path := make([]string, len(parent.Path)+1) - copy(path, parent.Path) - path[len(path)-1] = call.Name - - var vc version.Constraints - haveVersionArg := false - if strings.TrimSpace(call.Version) != "" { - haveVersionArg = true - - var err error - vc, err = version.NewConstraint(call.Version) - if err != nil { - diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ - Severity: tfconfig.DiagError, - Summary: "Invalid version constraint", - 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), - })) - continue - } - } - - var sourceAddr addrs.ModuleSource - var err error - if haveVersionArg { - sourceAddr, err = addrs.ParseModuleSourceRegistry(call.Source) - } else { - sourceAddr, err = addrs.ParseModuleSource(call.Source) - } - if err != nil { - if haveVersionArg { - diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ - Severity: tfconfig.DiagError, - Summary: "Invalid registry module source address", - Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.\n\nTerraform assumed that you intended a module registry source address because you also set the argument \"version\", which applies only to registry modules.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err), - })) - } else { - diags = diags.Append(wrapDiagnostic(tfconfig.Diagnostic{ - Severity: tfconfig.DiagError, - Summary: "Invalid module source address", - Detail: fmt.Sprintf("Module %q (declared at %s line %d) has invalid source address %q: %s.", callName, call.Pos.Filename, call.Pos.Line, call.Source, err), - })) - } - // If we didn't have a valid source address then we can't continue - // down the module tree with this one. - continue - } - - req := ModuleRequest{ - Name: call.Name, - Path: path, - SourceAddr: sourceAddr, - VersionConstraints: vc, - Parent: parent, - CallPos: call.Pos, - } - - 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, - Path: path, - Module: mod, - CallPos: call.Pos, - SourceAddr: sourceAddr, - Version: ver, - } - - child.Children, modDiags = buildChildModules(child, walker) - diags = diags.Append(modDiags) - - ret[call.Name] = child - } - - return ret, diags -} - -// ModuleRequest is used as part of the ModuleWalker interface used with -// function BuildConfig. -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 - - // Path is a list of logical names that traverse from the root module to - // this module. This can be used, for example, to form a lookup key for - // each distinct module call in a configuration, allowing for multiple - // calls with the same name at different points in the tree. - Path addrs.Module - - // SourceAddr is the source address string provided by the user in - // configuration. - SourceAddr addrs.ModuleSource - - // VersionConstraint is the version constraint applied to the module in - // configuration. - VersionConstraints version.Constraints - - // 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 - - // CallRange is the source position for the header of the "module" block - // in configuration that prompted this request. - CallPos tfconfig.SourcePos -} - -// ModuleWalker is an interface used with BuildConfig. -type ModuleWalker interface { - LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.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) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) - -func (f ModuleWalkerFunc) LoadModule(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) { - return f(req) -} diff --git a/internal/earlyconfig/config_test.go b/internal/earlyconfig/config_test.go deleted file mode 100644 index 21aa71beea..0000000000 --- a/internal/earlyconfig/config_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package earlyconfig - -import ( - "log" - "path/filepath" - "testing" - - "github.com/google/go-cmp/cmp" - version "github.com/hashicorp/go-version" - "github.com/hashicorp/terraform-config-inspect/tfconfig" - svchost "github.com/hashicorp/terraform-svchost" - "github.com/hashicorp/terraform/internal/addrs" - "github.com/hashicorp/terraform/internal/getproviders" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -func TestConfigProviderRequirements(t *testing.T) { - cfg := testConfig(t, "testdata/provider-reqs") - - impliedProvider := addrs.NewProvider( - addrs.DefaultProviderRegistryHost, - "hashicorp", "implied", - ) - nullProvider := addrs.NewProvider( - addrs.DefaultProviderRegistryHost, - "hashicorp", "null", - ) - randomProvider := addrs.NewProvider( - addrs.DefaultProviderRegistryHost, - "hashicorp", "random", - ) - tlsProvider := addrs.NewProvider( - addrs.DefaultProviderRegistryHost, - "hashicorp", "tls", - ) - happycloudProvider := addrs.NewProvider( - svchost.Hostname("tf.example.com"), - "awesomecorp", "happycloud", - ) - - got, diags := cfg.ProviderRequirements() - if diags.HasErrors() { - t.Fatalf("unexpected diagnostics: %s", diags.Err().Error()) - } - want := getproviders.Requirements{ - // the nullProvider constraints from the two modules are merged - nullProvider: getproviders.MustParseVersionConstraints("~> 2.0.0, 2.0.1"), - randomProvider: getproviders.MustParseVersionConstraints("~> 1.2.0"), - tlsProvider: getproviders.MustParseVersionConstraints("~> 3.0"), - impliedProvider: nil, - happycloudProvider: nil, - } - - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("wrong result\n%s", diff) - } -} - -func testConfig(t *testing.T, baseDir string) *Config { - rootMod, diags := LoadModule(baseDir) - if diags.HasErrors() { - t.Fatalf("unexpected diagnostics: %s", diags.Err().Error()) - } - - cfg, diags := BuildConfig(rootMod, ModuleWalkerFunc(testModuleWalkerFunc)) - if diags.HasErrors() { - t.Fatalf("unexpected diagnostics: %s", diags.Err().Error()) - } - - return cfg -} - -// testModuleWalkerFunc is a simple implementation of ModuleWalkerFunc that -// only understands how to resolve relative filesystem paths, using source -// location information from the call. -func testModuleWalkerFunc(req *ModuleRequest) (*tfconfig.Module, *version.Version, tfdiags.Diagnostics) { - callFilename := req.CallPos.Filename - sourcePath := req.SourceAddr.String() - finalPath := filepath.Join(filepath.Dir(callFilename), sourcePath) - log.Printf("[TRACE] %s in %s -> %s", sourcePath, callFilename, finalPath) - - newMod, diags := LoadModule(finalPath) - return newMod, version.Must(version.NewVersion("0.0.0")), diags -} diff --git a/internal/earlyconfig/diagnostics.go b/internal/earlyconfig/diagnostics.go deleted file mode 100644 index 15adad5638..0000000000 --- a/internal/earlyconfig/diagnostics.go +++ /dev/null @@ -1,82 +0,0 @@ -package earlyconfig - -import ( - "fmt" - - "github.com/hashicorp/terraform-config-inspect/tfconfig" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -func wrapDiagnostics(diags tfconfig.Diagnostics) tfdiags.Diagnostics { - ret := make(tfdiags.Diagnostics, len(diags)) - for i, diag := range diags { - ret[i] = wrapDiagnostic(diag) - } - return ret -} - -func wrapDiagnostic(diag tfconfig.Diagnostic) tfdiags.Diagnostic { - return wrappedDiagnostic{ - d: diag, - } -} - -type wrappedDiagnostic struct { - d tfconfig.Diagnostic -} - -func (d wrappedDiagnostic) Severity() tfdiags.Severity { - switch d.d.Severity { - case tfconfig.DiagError: - return tfdiags.Error - case tfconfig.DiagWarning: - return tfdiags.Warning - default: - // Should never happen since there are no other severities - return 0 - } -} - -func (d wrappedDiagnostic) Description() tfdiags.Description { - // Since the inspect library doesn't produce precise source locations, - // we include the position information as part of the error message text. - // See the comment inside method "Source" for more information. - switch { - case d.d.Pos == nil: - return tfdiags.Description{ - Summary: d.d.Summary, - Detail: d.d.Detail, - } - case d.d.Detail != "": - return tfdiags.Description{ - Summary: d.d.Summary, - Detail: fmt.Sprintf("On %s line %d: %s", d.d.Pos.Filename, d.d.Pos.Line, d.d.Detail), - } - default: - return tfdiags.Description{ - Summary: fmt.Sprintf("%s (on %s line %d)", d.d.Summary, d.d.Pos.Filename, d.d.Pos.Line), - } - } -} - -func (d wrappedDiagnostic) Source() tfdiags.Source { - // Since the inspect library is constrained by the lowest common denominator - // between legacy HCL and modern HCL, it only returns ranges at whole-line - // granularity, and that isn't sufficient to populate a tfdiags.Source - // and so we'll just omit ranges altogether and include the line number in - // the Description text. - // - // Callers that want to return nicer errors should consider reacting to - // earlyconfig errors by attempting a follow-up parse with the normal - // config loader, which can produce more precise source location - // information. - return tfdiags.Source{} -} - -func (d wrappedDiagnostic) FromExpr() *tfdiags.FromExpr { - return nil -} - -func (d wrappedDiagnostic) ExtraInfo() interface{} { - return nil -} diff --git a/internal/earlyconfig/doc.go b/internal/earlyconfig/doc.go deleted file mode 100644 index a9cf10f37c..0000000000 --- a/internal/earlyconfig/doc.go +++ /dev/null @@ -1,20 +0,0 @@ -// Package earlyconfig is a specialized alternative to the top-level "configs" -// package that does only shallow processing of configuration and is therefore -// able to be much more liberal than the full config loader in what it accepts. -// -// In particular, it can accept both current and legacy HCL syntax, and it -// ignores top-level blocks that it doesn't recognize. These two characteristics -// make this package ideal for dependency-checking use-cases so that we are -// more likely to be able to return an error message about an explicit -// incompatibility than to return a less-actionable message about a construct -// not being supported. -// -// However, its liberal approach also means it should be used sparingly. It -// exists primarily for "terraform init", so that it is able to detect -// incompatibilities more robustly when installing dependencies. For most -// other use-cases, use the "configs" and "configs/configload" packages. -// -// Package earlyconfig is a wrapper around the terraform-config-inspect -// codebase, adding to it just some helper functionality for Terraform's own -// use-cases. -package earlyconfig diff --git a/internal/earlyconfig/module.go b/internal/earlyconfig/module.go deleted file mode 100644 index e4edba0e05..0000000000 --- a/internal/earlyconfig/module.go +++ /dev/null @@ -1,13 +0,0 @@ -package earlyconfig - -import ( - "github.com/hashicorp/terraform-config-inspect/tfconfig" - "github.com/hashicorp/terraform/internal/tfdiags" -) - -// LoadModule loads some top-level metadata for the module in the given -// directory. -func LoadModule(dir string) (*tfconfig.Module, tfdiags.Diagnostics) { - mod, diags := tfconfig.LoadModule(dir) - return mod, wrapDiagnostics(diags) -} diff --git a/internal/earlyconfig/testdata/provider-reqs/child/provider-reqs-child.tf b/internal/earlyconfig/testdata/provider-reqs/child/provider-reqs-child.tf deleted file mode 100644 index ff03ded90e..0000000000 --- a/internal/earlyconfig/testdata/provider-reqs/child/provider-reqs-child.tf +++ /dev/null @@ -1,11 +0,0 @@ -terraform { - required_providers { - cloud = { - source = "tf.example.com/awesomecorp/happycloud" - } - null = { - # This should merge with the null provider constraint in the root module - version = "2.0.1" - } - } -} diff --git a/internal/earlyconfig/testdata/provider-reqs/provider-reqs-root.tf b/internal/earlyconfig/testdata/provider-reqs/provider-reqs-root.tf deleted file mode 100644 index 7325a26d15..0000000000 --- a/internal/earlyconfig/testdata/provider-reqs/provider-reqs-root.tf +++ /dev/null @@ -1,21 +0,0 @@ -terraform { - required_providers { - null = "~> 2.0.0" - random = { - version = "~> 1.2.0" - } - tls = { - source = "hashicorp/tls" - version = "~> 3.0" - } - } -} - -# There is no provider in required_providers called "implied", so this -# implicitly declares a dependency on "hashicorp/implied". -resource "implied_foo" "bar" { -} - -module "child" { - source = "./child" -}