github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/providers.go (about)

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