217 lines
5.7 KiB
Go
217 lines
5.7 KiB
Go
|
package command
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"text/template"
|
||
|
|
||
|
"github.com/hashicorp/terraform/configs"
|
||
|
"github.com/hashicorp/terraform/internal/initwd"
|
||
|
"github.com/hashicorp/terraform/moduledeps"
|
||
|
"github.com/hashicorp/terraform/tfdiags"
|
||
|
)
|
||
|
|
||
|
// ZeroThirteenUpgradeCommand upgrades configuration files for a module
|
||
|
// to include explicit provider source settings
|
||
|
type ZeroThirteenUpgradeCommand struct {
|
||
|
Meta
|
||
|
}
|
||
|
|
||
|
func (c *ZeroThirteenUpgradeCommand) Run(args []string) int {
|
||
|
args, err := c.Meta.process(args, true)
|
||
|
if err != nil {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
flags := c.Meta.defaultFlagSet("0.13upgrade")
|
||
|
flags.Usage = func() { c.Ui.Error(c.Help()) }
|
||
|
if err := flags.Parse(args); err != nil {
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
var diags tfdiags.Diagnostics
|
||
|
|
||
|
var dir string
|
||
|
args = flags.Args()
|
||
|
switch len(args) {
|
||
|
case 0:
|
||
|
dir = "."
|
||
|
case 1:
|
||
|
dir = args[0]
|
||
|
default:
|
||
|
diags = diags.Append(tfdiags.Sourceless(
|
||
|
tfdiags.Error,
|
||
|
"Too many arguments",
|
||
|
"The command 0.13upgrade expects only a single argument, giving the directory containing the module to upgrade.",
|
||
|
))
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// Check for user-supplied plugin path
|
||
|
if c.pluginPath, err = c.loadPluginPath(); err != nil {
|
||
|
c.Ui.Error(fmt.Sprintf("Error loading plugin path: %s", err))
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
dir = c.normalizePath(dir)
|
||
|
|
||
|
// Upgrade only if some configuration is present
|
||
|
empty, err := configs.IsEmptyDir(dir)
|
||
|
if err != nil {
|
||
|
diags = diags.Append(fmt.Errorf("Error checking configuration: %s", err))
|
||
|
return 1
|
||
|
}
|
||
|
if empty {
|
||
|
diags = diags.Append(tfdiags.Sourceless(
|
||
|
tfdiags.Error,
|
||
|
"Not a module directory",
|
||
|
fmt.Sprintf("The given directory %s does not contain any Terraform configuration files.", dir),
|
||
|
))
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// Early-load the config so that we can check provider dependencies
|
||
|
earlyConfig, earlyConfDiags := c.loadConfigEarly(dir)
|
||
|
if earlyConfDiags.HasErrors() {
|
||
|
c.Ui.Error(strings.TrimSpace("Failed to load configuration"))
|
||
|
diags = diags.Append(earlyConfDiags)
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
{
|
||
|
// Before we go further, we'll check to make sure none of the modules
|
||
|
// in the configuration declare that they don't support this Terraform
|
||
|
// version, so we can produce a version-related error message rather
|
||
|
// than potentially-confusing downstream errors.
|
||
|
versionDiags := initwd.CheckCoreVersionRequirements(earlyConfig)
|
||
|
diags = diags.Append(versionDiags)
|
||
|
if versionDiags.HasErrors() {
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Find the provider dependencies
|
||
|
configDeps, depsDiags := earlyConfig.ProviderDependencies()
|
||
|
if depsDiags.HasErrors() {
|
||
|
c.Ui.Error(strings.TrimSpace("Could not detect provider dependencies"))
|
||
|
diags = diags.Append(depsDiags)
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
// Detect source for each provider
|
||
|
providerSources, detectDiags := detectProviderSources(configDeps.Providers)
|
||
|
if detectDiags.HasErrors() {
|
||
|
c.Ui.Error(strings.TrimSpace("Unable to detect sources for providers"))
|
||
|
diags = diags.Append(detectDiags)
|
||
|
c.showDiagnostics(diags)
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
if len(providerSources) == 0 {
|
||
|
c.Ui.Output("No non-default providers found. Your configuration is ready to use!")
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// Generate the required providers configuration
|
||
|
genDiags := generateRequiredProviders(providerSources, dir)
|
||
|
diags = diags.Append(genDiags)
|
||
|
|
||
|
c.showDiagnostics(diags)
|
||
|
if diags.HasErrors() {
|
||
|
return 2
|
||
|
}
|
||
|
|
||
|
if len(diags) != 0 {
|
||
|
c.Ui.Output(`-----------------------------------------------------------------------------`)
|
||
|
}
|
||
|
c.Ui.Output(c.Colorize().Color(`
|
||
|
[bold][green]Upgrade complete![reset]
|
||
|
|
||
|
Use your version control system to review the proposed changes, make any
|
||
|
necessary adjustments, and then commit.
|
||
|
`))
|
||
|
|
||
|
return 0
|
||
|
}
|
||
|
|
||
|
// For providers which need a source attribute, detect and return source
|
||
|
// FIXME: currently does not filter or detect sources
|
||
|
func detectProviderSources(providers moduledeps.Providers) (map[string]string, tfdiags.Diagnostics) {
|
||
|
sources := make(map[string]string)
|
||
|
for provider := range providers {
|
||
|
sources[provider.Type] = provider.String()
|
||
|
}
|
||
|
return sources, nil
|
||
|
}
|
||
|
|
||
|
var providersTemplate = template.Must(template.New("providers.tf").Parse(`terraform {
|
||
|
required_providers {
|
||
|
{{- range $type, $source := .}}
|
||
|
{{$type}} = {
|
||
|
source = "{{$source}}"
|
||
|
}
|
||
|
{{- end}}
|
||
|
}
|
||
|
}
|
||
|
`))
|
||
|
|
||
|
// Generate a file with terraform.required_providers blocks for each provider
|
||
|
func generateRequiredProviders(providerSources map[string]string, dir string) tfdiags.Diagnostics {
|
||
|
var diags tfdiags.Diagnostics
|
||
|
|
||
|
// Find unused file named "providers.tf", or fall back to e.g. "providers-1.tf"
|
||
|
path := filepath.Join(dir, "providers.tf")
|
||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||
|
for i := 1; ; i++ {
|
||
|
path = filepath.Join(dir, fmt.Sprintf("providers-%d.tf", i))
|
||
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
f, err := os.Create(path)
|
||
|
if err != nil {
|
||
|
diags = diags.Append(tfdiags.Sourceless(
|
||
|
tfdiags.Error,
|
||
|
"Unable to create providers file",
|
||
|
fmt.Sprintf("Error when generating providers configuration at '%s': %s", path, err),
|
||
|
))
|
||
|
return diags
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
err = providersTemplate.Execute(f, providerSources)
|
||
|
if err != nil {
|
||
|
diags = diags.Append(tfdiags.Sourceless(
|
||
|
tfdiags.Error,
|
||
|
"Unable to create providers file",
|
||
|
fmt.Sprintf("Error when generating providers configuration at '%s': %s", path, err),
|
||
|
))
|
||
|
return diags
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (c *ZeroThirteenUpgradeCommand) Help() string {
|
||
|
helpText := `
|
||
|
Usage: terraform 0.13upgrade [module-dir]
|
||
|
|
||
|
Generates a "providers.tf" configuration file which includes source
|
||
|
configuration for every non-default provider.
|
||
|
`
|
||
|
return strings.TrimSpace(helpText)
|
||
|
}
|
||
|
|
||
|
func (c *ZeroThirteenUpgradeCommand) Synopsis() string {
|
||
|
return "Rewrites pre-0.13 module source code for v0.13"
|
||
|
}
|