diff --git a/command/providers.go b/command/providers.go new file mode 100644 index 000000000..23221603f --- /dev/null +++ b/command/providers.go @@ -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. + +` diff --git a/command/providers_test.go b/command/providers_test.go new file mode 100644 index 000000000..eb6c0dfc2 --- /dev/null +++ b/command/providers_test.go @@ -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) + } +} diff --git a/command/test-fixtures/providers/main.tf b/command/test-fixtures/providers/main.tf new file mode 100644 index 000000000..d22a2b509 --- /dev/null +++ b/command/test-fixtures/providers/main.tf @@ -0,0 +1,11 @@ +provider "foo" { + +} + +resource "bar_instance" "test" { + +} + +provider "baz" { + version = "1.2.0" +} diff --git a/commands.go b/commands.go index c4dca2d67..780b4652e 100644 --- a/commands.go +++ b/commands.go @@ -149,6 +149,12 @@ func init() { }, nil }, + "providers": func() (cli.Command, error) { + return &command.ProvidersCommand{ + Meta: meta, + }, nil + }, + "push": func() (cli.Command, error) { return &command.PushCommand{ Meta: meta,