forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
moduledeps: new package for representing module dependencies
As we add support for versioned providers, it's getting more complex to track the dependencies of each module and of the configuration as a whole, so this new package is intended to give us some room to model that nicely as a building block for the various aspects of dependency management. This package is not responsible for *building* the dependency data structure, since that requires knowledge of core Terraform and that would create cyclic package dependencies. A later change will add some logic in Terraform to create a Module tree based on the combination of a given configuration and state, returning an instance of this package's Module type. The Module.PluginRequirements method flattens the provider-oriented requirements into a set of plugin-oriented requirements (flattening any provider aliases) giving us what we need to work with the plugin/discovery package to find matching installed plugins. Other later uses of this package will include selecting plugin archives to auto-install from releases.hashicorp.com as part of "terraform init", where the module-oriented level of abstraction here should be useful for giving users good, specific feedback when constraints cannot be met. A "reason" is tracked for each provider dependency with the intent that this would later drive a UI for users to see and understand why a given dependency is present, to aid in debugging sticky issues with dependency resolution.
- Loading branch information
1 parent
a1e29ae
commit e89b539
Showing
6 changed files
with
467 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package moduledeps | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/plugin/discovery" | ||
) | ||
|
||
// Providers describes a set of provider dependencies for a given module. | ||
// | ||
// Each named provider instance can have one version constraint. | ||
type Providers map[ProviderInstance]ProviderDependency | ||
|
||
// ProviderDependency describes the dependency for a particular provider | ||
// instance, including both the set of allowed versions and the reason for | ||
// the dependency. | ||
type ProviderDependency struct { | ||
Versions discovery.VersionSet | ||
Reason ProviderDependencyReason | ||
} | ||
|
||
// ProviderDependencyReason is an enumeration of reasons why a dependency might be | ||
// present. | ||
type ProviderDependencyReason int | ||
|
||
const ( | ||
// ProviderDependencyExplicit means that there is an explicit "provider" | ||
// block in the configuration for this module. | ||
ProviderDependencyExplicit ProviderDependencyReason = iota | ||
|
||
// ProviderDependencyImplicit means that there is no explicit "provider" | ||
// block but there is at least one resource that uses this provider. | ||
ProviderDependencyImplicit | ||
|
||
// ProviderDependencyInherited is a special case of | ||
// ProviderDependencyImplicit where a parent module has defined a | ||
// configuration for the provider that has been inherited by at least one | ||
// resource in this module. | ||
ProviderDependencyInherited | ||
|
||
// ProviderDependencyFromState means that this provider is not currently | ||
// referenced by configuration at all, but some existing instances in | ||
// the state still depend on it. | ||
ProviderDependencyFromState | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
// Package moduledeps contains types that can be used to describe the | ||
// providers required for all of the modules in a module tree. | ||
// | ||
// It does not itself contain the functionality for populating such | ||
// data structures; that's in Terraform core, since this package intentionally | ||
// does not depend on terraform core to avoid package dependency cycles. | ||
package moduledeps |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package moduledeps | ||
|
||
import ( | ||
"sort" | ||
"strings" | ||
|
||
"github.com/hashicorp/terraform/plugin/discovery" | ||
) | ||
|
||
// Module represents the dependencies of a single module, as well being | ||
// a node in a tree of such structures representing the dependencies of | ||
// an entire configuration. | ||
type Module struct { | ||
Name string | ||
Providers Providers | ||
Children []*Module | ||
} | ||
|
||
// WalkFunc is a callback type for use with Module.WalkTree | ||
type WalkFunc func(path []string, parent *Module, current *Module) error | ||
|
||
// WalkTree calls the given callback once for the receiver and then | ||
// once for each descendent, in an order such that parents are called | ||
// before their children and siblings are called in the order they | ||
// appear in the Children slice. | ||
// | ||
// When calling the callback, parent will be nil for the first call | ||
// for the receiving module, and then set to the direct parent of | ||
// each module for the subsequent calls. | ||
// | ||
// The path given to the callback is valid only until the callback | ||
// returns, after which it will be mutated and reused. Callbacks must | ||
// therefore copy the path slice if they wish to retain it. | ||
// | ||
// If the given callback returns an error, the walk will be aborted at | ||
// that point and that error returned to the caller. | ||
// | ||
// This function is not thread-safe for concurrent modifications of the | ||
// data structure, so it's the caller's responsibility to arrange for that | ||
// should it be needed. | ||
// | ||
// It is safe for a callback to modify the descendents of the "current" | ||
// module, including the ordering of the Children slice itself, but the | ||
// callback MUST NOT modify the parent module. | ||
func (m *Module) WalkTree(cb WalkFunc) error { | ||
return walkModuleTree(make([]string, 0, 1), nil, m, cb) | ||
} | ||
|
||
func walkModuleTree(path []string, parent *Module, current *Module, cb WalkFunc) error { | ||
path = append(path, current.Name) | ||
err := cb(path, parent, current) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, child := range current.Children { | ||
err := walkModuleTree(path, current, child, cb) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// SortChildren sorts the Children slice into lexicographic order by | ||
// name, in-place. | ||
// | ||
// This is primarily useful prior to calling WalkTree so that the walk | ||
// will proceed in a consistent order. | ||
func (m *Module) SortChildren() { | ||
sort.Sort(sortModules{m.Children}) | ||
} | ||
|
||
// SortDescendents is a convenience wrapper for calling SortChildren on | ||
// the receiver and all of its descendent modules. | ||
func (m *Module) SortDescendents() { | ||
m.WalkTree(func(path []string, parent *Module, current *Module) error { | ||
current.SortChildren() | ||
return nil | ||
}) | ||
} | ||
|
||
type sortModules struct { | ||
modules []*Module | ||
} | ||
|
||
func (s sortModules) Len() int { | ||
return len(s.modules) | ||
} | ||
|
||
func (s sortModules) Less(i, j int) bool { | ||
cmp := strings.Compare(s.modules[i].Name, s.modules[j].Name) | ||
return cmp < 0 | ||
} | ||
|
||
func (s sortModules) Swap(i, j int) { | ||
s.modules[i], s.modules[j] = s.modules[j], s.modules[i] | ||
} | ||
|
||
// PluginRequirements produces a PluginRequirements structure that can | ||
// be used with discovery.PluginMetaSet.ConstrainVersions to identify | ||
// suitable plugins to satisfy the module's provider dependencies. | ||
// | ||
// This method only considers the direct requirements of the receiver. | ||
// Use AllPluginRequirements to flatten the dependencies for the | ||
// entire tree of modules. | ||
func (m *Module) PluginRequirements() discovery.PluginRequirements { | ||
ret := make(discovery.PluginRequirements) | ||
for inst, dep := range m.Providers { | ||
// m.Providers is keyed on provider names, such as "aws.foo". | ||
// a PluginRequirements wants keys to be provider *types*, such | ||
// as "aws". If there are multiple aliases for the same | ||
// provider then we will flatten them into a single requirement | ||
// by using Intersection to merge the version sets. | ||
pty := inst.Type() | ||
if existing, exists := ret[pty]; exists { | ||
ret[pty] = existing.Intersection(dep.Versions) | ||
} else { | ||
ret[pty] = dep.Versions | ||
} | ||
} | ||
return ret | ||
} | ||
|
||
// AllPluginRequirements calls PluginRequirements for the receiver and all | ||
// of its descendents, and merges the result into a single PluginRequirements | ||
// structure that would satisfy all of the modules together. | ||
func (m *Module) AllPluginRequirements() discovery.PluginRequirements { | ||
var ret discovery.PluginRequirements | ||
m.WalkTree(func(path []string, parent *Module, current *Module) error { | ||
ret = ret.Merge(current.PluginRequirements()) | ||
return nil | ||
}) | ||
return ret | ||
} |
Oops, something went wrong.