github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/providers.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"github.com/xlab/treeprint"
     8  
     9  	"github.com/kevinklinger/open_terraform/noninternal/configs"
    10  	"github.com/kevinklinger/open_terraform/noninternal/getproviders"
    11  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    12  )
    13  
    14  // ProvidersCommand is a Command implementation that prints out information
    15  // about the providers used in the current configuration/state.
    16  type ProvidersCommand struct {
    17  	Meta
    18  }
    19  
    20  func (c *ProvidersCommand) Help() string {
    21  	return providersCommandHelp
    22  }
    23  
    24  func (c *ProvidersCommand) Synopsis() string {
    25  	return "Show the providers required for this configuration"
    26  }
    27  
    28  func (c *ProvidersCommand) Run(args []string) int {
    29  	args = c.Meta.process(args)
    30  	cmdFlags := c.Meta.defaultFlagSet("providers")
    31  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    32  	if err := cmdFlags.Parse(args); err != nil {
    33  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    34  		return 1
    35  	}
    36  
    37  	configPath, err := ModulePath(cmdFlags.Args())
    38  	if err != nil {
    39  		c.Ui.Error(err.Error())
    40  		return 1
    41  	}
    42  
    43  	var diags tfdiags.Diagnostics
    44  
    45  	empty, err := configs.IsEmptyDir(configPath)
    46  	if err != nil {
    47  		diags = diags.Append(tfdiags.Sourceless(
    48  			tfdiags.Error,
    49  			"Error validating configuration directory",
    50  			fmt.Sprintf("Terraform encountered an unexpected error while verifying that the given configuration directory is valid: %s.", err),
    51  		))
    52  		c.showDiagnostics(diags)
    53  		return 1
    54  	}
    55  	if empty {
    56  		absPath, err := filepath.Abs(configPath)
    57  		if err != nil {
    58  			absPath = configPath
    59  		}
    60  		diags = diags.Append(tfdiags.Sourceless(
    61  			tfdiags.Error,
    62  			"No configuration files",
    63  			fmt.Sprintf("The directory %s contains no Terraform configuration files.", absPath),
    64  		))
    65  		c.showDiagnostics(diags)
    66  		return 1
    67  	}
    68  
    69  	config, configDiags := c.loadConfig(configPath)
    70  	diags = diags.Append(configDiags)
    71  	if configDiags.HasErrors() {
    72  		c.showDiagnostics(diags)
    73  		return 1
    74  	}
    75  
    76  	// Load the backend
    77  	b, backendDiags := c.Backend(&BackendOpts{
    78  		Config: config.Module.Backend,
    79  	})
    80  	diags = diags.Append(backendDiags)
    81  	if backendDiags.HasErrors() {
    82  		c.showDiagnostics(diags)
    83  		return 1
    84  	}
    85  
    86  	// This is a read-only command
    87  	c.ignoreRemoteVersionConflict(b)
    88  
    89  	// Get the state
    90  	env, err := c.Workspace()
    91  	if err != nil {
    92  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    93  		return 1
    94  	}
    95  	s, err := b.StateMgr(env)
    96  	if err != nil {
    97  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    98  		return 1
    99  	}
   100  	if err := s.RefreshState(); err != nil {
   101  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
   102  		return 1
   103  	}
   104  
   105  	reqs, reqDiags := config.ProviderRequirementsByModule()
   106  	diags = diags.Append(reqDiags)
   107  	if diags.HasErrors() {
   108  		c.showDiagnostics(diags)
   109  		return 1
   110  	}
   111  
   112  	state := s.State()
   113  	var stateReqs getproviders.Requirements
   114  	if state != nil {
   115  		stateReqs = state.ProviderRequirements()
   116  	}
   117  
   118  	printRoot := treeprint.New()
   119  	c.populateTreeNode(printRoot, reqs)
   120  
   121  	c.Ui.Output("\nProviders required by configuration:")
   122  	c.Ui.Output(printRoot.String())
   123  
   124  	if len(stateReqs) > 0 {
   125  		c.Ui.Output("Providers required by state:\n")
   126  		for fqn := range stateReqs {
   127  			c.Ui.Output(fmt.Sprintf("    provider[%s]\n", fqn.String()))
   128  		}
   129  	}
   130  
   131  	c.showDiagnostics(diags)
   132  	if diags.HasErrors() {
   133  		return 1
   134  	}
   135  	return 0
   136  }
   137  
   138  func (c *ProvidersCommand) populateTreeNode(tree treeprint.Tree, node *configs.ModuleRequirements) {
   139  	for fqn, dep := range node.Requirements {
   140  		versionsStr := getproviders.VersionConstraintsString(dep)
   141  		if versionsStr != "" {
   142  			versionsStr = " " + versionsStr
   143  		}
   144  		tree.AddNode(fmt.Sprintf("provider[%s]%s", fqn.String(), versionsStr))
   145  	}
   146  	for name, childNode := range node.Children {
   147  		branch := tree.AddBranch(fmt.Sprintf("module.%s", name))
   148  		c.populateTreeNode(branch, childNode)
   149  	}
   150  }
   151  
   152  const providersCommandHelp = `
   153  Usage: terraform [global options] providers [DIR]
   154  
   155    Prints out a tree of modules in the referenced configuration annotated with
   156    their provider requirements.
   157  
   158    This provides an overview of all of the provider requirements across all
   159    referenced modules, as an aid to understanding why particular provider
   160    plugins are needed and why particular versions are selected.
   161  `