command: "terraform providers" command
This new command prints out the tree of modules annotated with their associated required providers. The purpose of this command is to help users answer questions such as "why is this provider required?", "why is Terraform using an older version of this provider?", and "what combination of modules is creating an impossible provider version situation?" For configurations using many modules this sort of question is likely to come up a lot once we support versioned providers. As a bonus use-case, this command also shows explicitly when a provider configuration is being inherited from a parent module, to help users to understand where the configuration is coming from for each module when some child modules provide their own provider configurations.
This commit is contained in:
parent
8b037432e7
commit
3af0ecdf01
|
@ -0,0 +1,125 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/moduledeps"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/xlab/treeprint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProvidersCommand is a Command implementation that prints out information
|
||||||
|
// about the providers used in the current configuration/state.
|
||||||
|
type ProvidersCommand struct {
|
||||||
|
Meta
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProvidersCommand) Help() string {
|
||||||
|
return providersCommandHelp
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProvidersCommand) Synopsis() string {
|
||||||
|
return "Prints a tree of the providers used in the configuration"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ProvidersCommand) Run(args []string) int {
|
||||||
|
|
||||||
|
cmdFlags := c.Meta.flagSet("providers")
|
||||||
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||||
|
if err := cmdFlags.Parse(args); err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
configPath, err := ModulePath(cmdFlags.Args())
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the config
|
||||||
|
root, err := c.Module(configPath)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the config (to ensure the version constraints are valid)
|
||||||
|
err = root.Validate()
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(err.Error())
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the backend
|
||||||
|
b, err := c.Backend(&BackendOpts{ConfigPath: configPath})
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the state
|
||||||
|
env := c.Env()
|
||||||
|
state, err := b.State(env)
|
||||||
|
if err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if err := state.RefreshState(); err != nil {
|
||||||
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
s := state.State()
|
||||||
|
|
||||||
|
depTree := terraform.ModuleTreeDependencies(root, s)
|
||||||
|
depTree.SortDescendents()
|
||||||
|
|
||||||
|
printRoot := treeprint.New()
|
||||||
|
providersCommandPopulateTreeNode(printRoot, depTree)
|
||||||
|
|
||||||
|
c.Ui.Output(printRoot.String())
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func providersCommandPopulateTreeNode(node treeprint.Tree, deps *moduledeps.Module) {
|
||||||
|
names := make([]string, 0, len(deps.Providers))
|
||||||
|
for name := range deps.Providers {
|
||||||
|
names = append(names, string(name))
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
dep := deps.Providers[moduledeps.ProviderInstance(name)]
|
||||||
|
versionsStr := dep.Versions.String()
|
||||||
|
if versionsStr != "" {
|
||||||
|
versionsStr = " " + versionsStr
|
||||||
|
}
|
||||||
|
var reasonStr string
|
||||||
|
switch dep.Reason {
|
||||||
|
case moduledeps.ProviderDependencyInherited:
|
||||||
|
reasonStr = " (inherited)"
|
||||||
|
case moduledeps.ProviderDependencyFromState:
|
||||||
|
reasonStr = " (from state)"
|
||||||
|
}
|
||||||
|
node.AddNode(fmt.Sprintf("provider.%s%s%s", name, versionsStr, reasonStr))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range deps.Children {
|
||||||
|
childNode := node.AddBranch(fmt.Sprintf("module.%s", child.Name))
|
||||||
|
providersCommandPopulateTreeNode(childNode, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const providersCommandHelp = `
|
||||||
|
Usage: terraform providers [dir]
|
||||||
|
|
||||||
|
Prints out a tree of modules in the referenced configuration annotated with
|
||||||
|
their provider requirements.
|
||||||
|
|
||||||
|
This provides an overview of all of the provider requirements across all
|
||||||
|
referenced modules, as an aid to understanding why particular provider
|
||||||
|
plugins are needed and why particular versions are selected.
|
||||||
|
|
||||||
|
`
|
|
@ -0,0 +1,43 @@
|
||||||
|
package command
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mitchellh/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProviders(t *testing.T) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(testFixturePath("providers")); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Chdir(cwd)
|
||||||
|
|
||||||
|
ui := new(cli.MockUi)
|
||||||
|
c := &ProvidersCommand{
|
||||||
|
Meta: Meta{
|
||||||
|
Ui: ui,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
if code := c.Run(args); code != 0 {
|
||||||
|
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
output := ui.OutputWriter.String()
|
||||||
|
if !strings.Contains(output, "provider.foo") {
|
||||||
|
t.Errorf("output missing provider.foo\n\n%s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "provider.bar") {
|
||||||
|
t.Errorf("output missing provider.bar\n\n%s", output)
|
||||||
|
}
|
||||||
|
if !strings.Contains(output, "provider.baz") {
|
||||||
|
t.Errorf("output missing provider.baz\n\n%s", output)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
provider "foo" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "bar_instance" "test" {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "baz" {
|
||||||
|
version = "1.2.0"
|
||||||
|
}
|
|
@ -149,6 +149,12 @@ func init() {
|
||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"providers": func() (cli.Command, error) {
|
||||||
|
return &command.ProvidersCommand{
|
||||||
|
Meta: meta,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
|
||||||
"push": func() (cli.Command, error) {
|
"push": func() (cli.Command, error) {
|
||||||
return &command.PushCommand{
|
return &command.PushCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
|
|
Loading…
Reference in New Issue