package terraform import ( version "github.com/hashicorp/go-version" "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/moduledeps" "github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/states" ) // ConfigTreeDependencies returns the dependencies of the tree of modules // described by the given configuration and state. // // Both configuration and state are required because there can be resources // implied by instances in the state that no longer exist in config. func ConfigTreeDependencies(root *configs.Config, state *states.State) *moduledeps.Module { // First we walk the configuration tree to build the overall structure // and capture the explicit/implicit/inherited provider dependencies. deps := configTreeConfigDependencies(root, nil) // Next we walk over the resources in the state to catch any additional // dependencies created by existing resources that are no longer in config. // Most things we find in state will already be present in 'deps', but // we're interested in the rare thing that isn't. configTreeMergeStateDependencies(deps, state) return deps } func configTreeConfigDependencies(root *configs.Config, inheritProviders map[string]*configs.Provider) *moduledeps.Module { if root == nil { // If no config is provided, we'll make a synthetic root. // This isn't necessarily correct if we're called with a nil that // *isn't* at the root, but in practice that can never happen. return &moduledeps.Module{ Name: "root", Providers: make(moduledeps.Providers), } } name := "root" if len(root.Path) != 0 { name = root.Path[len(root.Path)-1] } ret := &moduledeps.Module{ Name: name, } module := root.Module // Provider dependencies { providers := make(moduledeps.Providers) // The main way to declare a provider dependency is explicitly inside // the "terraform" block, which allows declaring a requirement without // also creating a configuration. for _, req := range module.ProviderRequirements { // The handling here is a bit fiddly because the moduledeps package // was designed around the legacy (pre-0.12) configuration model // and hasn't yet been revised to handle the new model. As a result, // we need to do some translation here. // FIXME: Eventually we should adjust the underlying model so we // can also retain the source location of each constraint, for // more informative output from the "terraform providers" command. var rawConstraints version.Constraints for _, constraint := range req.VersionConstraints { rawConstraints = append(rawConstraints, constraint.Required...) } discoConstraints := discovery.NewConstraints(rawConstraints) providers[req.Type] = moduledeps.ProviderDependency{ Constraints: discoConstraints, Reason: moduledeps.ProviderDependencyExplicit, } } // Provider configurations can also include version constraints, // allowing for more terse declaration in situations where both a // configuration and a constraint are defined in the same module. for _, pCfg := range module.ProviderConfigs { var fqn addrs.Provider if existing, exists := module.ProviderRequirements[pCfg.Name]; exists { fqn = existing.Type } else { fqn = addrs.NewLegacyProvider(pCfg.Name) } discoConstraints := discovery.AllVersions if pCfg.Version.Required != nil { discoConstraints = discovery.NewConstraints(pCfg.Version.Required) } if existing, exists := providers[fqn]; exists { constraints := existing.Constraints.Append(discoConstraints) providers[fqn] = moduledeps.ProviderDependency{ Constraints: constraints, Reason: moduledeps.ProviderDependencyExplicit, } } else { providers[fqn] = moduledeps.ProviderDependency{ Constraints: discoConstraints, Reason: moduledeps.ProviderDependencyExplicit, } } } // Each resource in the configuration creates an *implicit* provider // dependency, though we'll only record it if there isn't already // an explicit dependency on the same provider. for _, rc := range module.ManagedResources { addr := rc.ProviderConfigAddr() //look up the provider localname in the provider requirements map and see if //there is a non-default FQN associated var fqn addrs.Provider if existing, exists := module.ProviderRequirements[addr.LocalName]; exists { fqn = existing.Type } else { fqn = addrs.NewLegacyProvider(addr.LocalName) } if _, exists := providers[fqn]; exists { // Explicit dependency already present continue } reason := moduledeps.ProviderDependencyImplicit if _, inherited := inheritProviders[addr.StringCompact()]; inherited { reason = moduledeps.ProviderDependencyInherited } providers[fqn] = moduledeps.ProviderDependency{ Constraints: discovery.AllVersions, Reason: reason, } } for _, rc := range module.DataResources { addr := rc.ProviderConfigAddr() //look up the provider localname in the provider requirements map and see if //there is a non-default FQN associated var fqn addrs.Provider if existing, exists := module.ProviderRequirements[addr.LocalName]; exists { fqn = existing.Type } else { fqn = addrs.NewLegacyProvider(addr.LocalName) } if _, exists := providers[fqn]; exists { // Explicit dependency already present continue } reason := moduledeps.ProviderDependencyImplicit if _, inherited := inheritProviders[addr.String()]; inherited { reason = moduledeps.ProviderDependencyInherited } providers[fqn] = moduledeps.ProviderDependency{ Constraints: discovery.AllVersions, Reason: reason, } } ret.Providers = providers } childInherit := make(map[string]*configs.Provider) for k, v := range inheritProviders { childInherit[k] = v } for k, v := range module.ProviderConfigs { childInherit[k] = v } for _, c := range root.Children { ret.Children = append(ret.Children, configTreeConfigDependencies(c, childInherit)) } return ret } func configTreeMergeStateDependencies(root *moduledeps.Module, state *states.State) { if state == nil { return } findModule := func(path addrs.ModuleInstance) *moduledeps.Module { module := root for _, step := range path { var next *moduledeps.Module for _, cm := range module.Children { if cm.Name == step.Name { next = cm break } } if next == nil { // If we didn't find a next node, we'll need to make one next = &moduledeps.Module{ Name: step.Name, Providers: make(moduledeps.Providers), } module.Children = append(module.Children, next) } module = next } return module } for _, ms := range state.Modules { module := findModule(ms.Addr) for _, rs := range ms.Resources { //FIXME: lookup the provider localname in the TBD map and see if //there is an FQN associated fqn := addrs.NewLegacyProvider(rs.ProviderConfig.ProviderConfig.LocalName) if _, exists := module.Providers[fqn]; !exists { module.Providers[fqn] = moduledeps.ProviderDependency{ Constraints: discovery.AllVersions, Reason: moduledeps.ProviderDependencyFromState, } } } } }