command: remove 0.12upgrade (#24403)
* command: remove 0.12upgrade and related `configupgrade` library * leave deprecation warning for 0.12upgrade to point users to v0.12
This commit is contained in:
parent
109c4bf6ef
commit
5f313a65ad
|
@ -1,256 +1,15 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
import "fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/terraform/configs/configupgrade"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// ZeroTwelveUpgradeCommand is a Command implementation that can upgrade
|
||||
// the configuration files for a module from pre-0.11 syntax to new 0.12
|
||||
// idiom, while also flagging any suspicious constructs that will require
|
||||
// human review.
|
||||
type ZeroTwelveUpgradeCommand struct {
|
||||
Meta
|
||||
}
|
||||
|
||||
func (c *ZeroTwelveUpgradeCommand) Run(args []string) int {
|
||||
args, err := c.Meta.process(args, true)
|
||||
if err != nil {
|
||||
return 1
|
||||
}
|
||||
|
||||
var skipConfirm, force bool
|
||||
|
||||
flags := c.Meta.extendedFlagSet("0.12upgrade")
|
||||
flags.BoolVar(&skipConfirm, "yes", false, "skip confirmation prompt")
|
||||
flags.BoolVar(&force, "force", false, "override duplicate upgrade heuristic")
|
||||
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.12upgrade 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)
|
||||
|
||||
sources, err := configupgrade.LoadModule(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Module directory not found",
|
||||
fmt.Sprintf("The given directory %s does not exist.", dir),
|
||||
))
|
||||
} else {
|
||||
diags = diags.Append(err)
|
||||
}
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
if len(sources) == 0 {
|
||||
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
|
||||
}
|
||||
|
||||
// The config loader doesn't naturally populate our sources
|
||||
// map, so we'll do it manually so our diagnostics can have
|
||||
// source code snippets inside them.
|
||||
// This is weird, but this whole upgrade codepath is pretty
|
||||
// weird and temporary, so we'll accept it.
|
||||
if loader, err := c.initConfigLoader(); err == nil {
|
||||
parser := loader.Parser()
|
||||
for name, src := range sources {
|
||||
parser.ForceFileSource(filepath.Join(dir, name), src)
|
||||
}
|
||||
}
|
||||
|
||||
if !force {
|
||||
// We'll check first if this directory already looks upgraded, so we
|
||||
// don't waste the user's time dealing with an interactive prompt
|
||||
// immediately followed by an error.
|
||||
if already, rng := sources.MaybeAlreadyUpgraded(); already {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Module already upgraded",
|
||||
Detail: fmt.Sprintf("The module in directory %s has a version constraint that suggests it has already been upgraded for v0.12. If this is incorrect, either remove this constraint or override this heuristic with the -force argument. Upgrading a module that was already upgraded may change the meaning of that module.", dir),
|
||||
Subject: rng.ToHCL().Ptr(),
|
||||
})
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
if !skipConfirm {
|
||||
c.Ui.Output(fmt.Sprintf(`
|
||||
This command will rewrite the configuration files in the given directory so
|
||||
that they use the new syntax features from Terraform v0.12, and will identify
|
||||
any constructs that may need to be adjusted for correct operation with
|
||||
Terraform v0.12.
|
||||
|
||||
We recommend using this command in a clean version control work tree, so that
|
||||
you can easily see the proposed changes as a diff against the latest commit.
|
||||
If you have uncommited changes already present, we recommend aborting this
|
||||
command and dealing with them before running this command again.
|
||||
`))
|
||||
|
||||
query := "Would you like to upgrade the module in the current directory?"
|
||||
if dir != "." {
|
||||
query = fmt.Sprintf("Would you like to upgrade the module in %s?", dir)
|
||||
}
|
||||
v, err := c.UIInput().Input(context.Background(), &terraform.InputOpts{
|
||||
Id: "approve",
|
||||
Query: query,
|
||||
Description: `Only 'yes' will be accepted to confirm.`,
|
||||
})
|
||||
if err != nil {
|
||||
diags = diags.Append(err)
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
if v != "yes" {
|
||||
c.Ui.Info("Upgrade cancelled.")
|
||||
The 0.12upgrade command is deprecated. You must run this command with Terraform
|
||||
v0.12 to upgrade your configuration syntax before upgrading to the current
|
||||
version.`))
|
||||
return 0
|
||||
}
|
||||
|
||||
c.Ui.Output(`-----------------------------------------------------------------------------`)
|
||||
}
|
||||
|
||||
upgrader := &configupgrade.Upgrader{
|
||||
Providers: c.providerResolver(),
|
||||
Provisioners: c.provisionerFactories(),
|
||||
}
|
||||
newSources, upgradeDiags := upgrader.Upgrade(sources, dir)
|
||||
diags = diags.Append(upgradeDiags)
|
||||
if upgradeDiags.HasErrors() {
|
||||
c.showDiagnostics(diags)
|
||||
return 2
|
||||
}
|
||||
|
||||
// Now we'll write the contents of newSources into the filesystem.
|
||||
for name, src := range newSources {
|
||||
fn := filepath.Join(dir, name)
|
||||
if src == nil {
|
||||
// indicates a file to be deleted
|
||||
err := os.Remove(fn)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to remove file",
|
||||
fmt.Sprintf("The file %s must be renamed as part of the upgrade process, but the old file could not be deleted: %s.", fn, err),
|
||||
))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(fn, src, 0644)
|
||||
if err != nil {
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
"Failed to write file",
|
||||
fmt.Sprintf("The file %s must be updated or created as part of the upgrade process, but there was an error while writing: %s.", fn, err),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
c.showDiagnostics(diags)
|
||||
if diags.HasErrors() {
|
||||
return 2
|
||||
}
|
||||
|
||||
if !skipConfirm {
|
||||
if len(diags) != 0 {
|
||||
c.Ui.Output(`-----------------------------------------------------------------------------`)
|
||||
}
|
||||
c.Ui.Output(c.Colorize().Color(`
|
||||
[bold][green]Upgrade complete![reset]
|
||||
|
||||
The configuration files were upgraded successfully. Use your version control
|
||||
system to review the proposed changes, make any necessary adjustments, and
|
||||
then commit.
|
||||
`))
|
||||
if len(diags) != 0 {
|
||||
// We checked for errors above, so these must be warnings.
|
||||
c.Ui.Output(`Some warnings were generated during the upgrade, as shown above. These
|
||||
indicate situations where Terraform could not decide on an appropriate course
|
||||
of action without further human input.
|
||||
|
||||
Where possible, these have also been marked with TF-UPGRADE-TODO comments to
|
||||
mark the locations where a decision must be made. After reviewing and adjusting
|
||||
these, manually remove the TF-UPGRADE-TODO comment before continuing.
|
||||
`)
|
||||
}
|
||||
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *ZeroTwelveUpgradeCommand) Help() string {
|
||||
helpText := `
|
||||
Usage: terraform 0.12upgrade [module-dir]
|
||||
|
||||
Rewrites the .tf files for a single module that was written for a Terraform
|
||||
version prior to v0.12 so that it uses new syntax features from v0.12
|
||||
and later.
|
||||
|
||||
Also rewrites constructs that behave differently after v0.12, and flags any
|
||||
suspicious constructs that require human review,
|
||||
|
||||
By default, 0.12upgrade rewrites the files in the current working directory.
|
||||
However, a path to a different directory can be provided. The command will
|
||||
prompt for confirmation interactively unless the -yes option is given.
|
||||
|
||||
Options:
|
||||
|
||||
-yes Skip the initial introduction messages and interactive
|
||||
confirmation. This can be used to run this command in
|
||||
batch from a script.
|
||||
|
||||
-force Override the heuristic that attempts to detect if a
|
||||
configuration is already written for v0.12 or later.
|
||||
Some of the transformations made by this command are
|
||||
not idempotent, so re-running against the same module
|
||||
may change the meanings expressions in the module.
|
||||
`
|
||||
return strings.TrimSpace(helpText)
|
||||
}
|
||||
|
||||
func (c *ZeroTwelveUpgradeCommand) Synopsis() string {
|
||||
return "Rewrites pre-0.12 module source code for v0.12"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/cli"
|
||||
)
|
||||
|
||||
func TestZeroTwelveUpgrade_deprecated(t *testing.T) {
|
||||
ui := new(cli.MockUi)
|
||||
c := &ZeroTwelveUpgradeCommand{
|
||||
Meta: Meta{
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
if code := c.Run([]string{}); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
if !strings.Contains(output, "The 0.12upgrade command is deprecated.") {
|
||||
t.Fatal("unexpected output:", output)
|
||||
}
|
||||
}
|
143
command/init.go
143
command/init.go
|
@ -18,7 +18,6 @@ import (
|
|||
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/configs/configupgrade"
|
||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||
"github.com/hashicorp/terraform/internal/initwd"
|
||||
"github.com/hashicorp/terraform/plugin/discovery"
|
||||
|
@ -171,18 +170,9 @@ func (c *InitCommand) Run(args []string) int {
|
|||
// Before we do anything else, we'll try loading configuration with both
|
||||
// our "normal" and "early" configuration codepaths. If early succeeds
|
||||
// while normal fails, that strongly suggests that the configuration is
|
||||
// using syntax that worked in 0.11 but no longer in 0.12, which requires
|
||||
// some special behavior here to get the directory initialized just enough
|
||||
// to run "terraform 0.12upgrade".
|
||||
//
|
||||
// FIXME: Once we reach 0.13 and remove 0.12upgrade, we should rework this
|
||||
// so that we first use the early config to do a general compatibility
|
||||
// check with dependencies, producing version-oriented error messages if
|
||||
// dependencies aren't right, and only then use the real loader to deal
|
||||
// with the backend configuration.
|
||||
// using syntax that worked in 0.11 but no longer in v0.12.
|
||||
rootMod, confDiags := c.loadSingleModule(path)
|
||||
rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(path)
|
||||
configUpgradeProbablyNeeded := false
|
||||
if confDiags.HasErrors() {
|
||||
if earlyConfDiags.HasErrors() {
|
||||
// If both parsers produced errors then we'll assume the config
|
||||
|
@ -194,33 +184,25 @@ func (c *InitCommand) Run(args []string) int {
|
|||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
|
||||
// If _only_ the main loader produced errors then that suggests an
|
||||
// upgrade may help. To give us more certainty here, we'll use the
|
||||
// same heuristic that "terraform 0.12upgrade" uses to guess if a
|
||||
// configuration has already been upgraded, to reduce the risk that
|
||||
// we'll produce a misleading message if the problem is just a regular
|
||||
// syntax error that the early loader just didn't catch.
|
||||
sources, err := configupgrade.LoadModule(path)
|
||||
if err == nil {
|
||||
if already, _ := sources.MaybeAlreadyUpgraded(); already {
|
||||
// Just report the errors as normal, then.
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||
diags = diags.Append(confDiags)
|
||||
c.showDiagnostics(diags)
|
||||
// If _only_ the main loader produced errors then that suggests the
|
||||
// configuration is written in 0.11-style syntax. We will return an
|
||||
// error suggesting the user upgrade their config manually or with
|
||||
// Terraform v0.12
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigErrorMaybeLegacySyntax))
|
||||
c.showDiagnostics(earlyConfDiags)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
configUpgradeProbablyNeeded = true
|
||||
}
|
||||
if earlyConfDiags.HasErrors() {
|
||||
|
||||
// If _only_ the early loader encountered errors then that's unusual
|
||||
// (it should generally be a superset of the normal loader) but we'll
|
||||
// return those errors anyway since otherwise we'll probably get
|
||||
// some weird behavior downstream. Errors from the early loader are
|
||||
// generally not as high-quality since it has less context to work with.
|
||||
if earlyConfDiags.HasErrors() {
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||
diags = diags.Append(earlyConfDiags)
|
||||
// Errors from the early loader are generally not as high-quality since
|
||||
// it has less context to work with.
|
||||
diags = diags.Append(confDiags)
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
|
@ -237,21 +219,15 @@ func (c *InitCommand) Run(args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
// With all of the modules (hopefully) installed, we can now try to load
|
||||
// the whole configuration tree.
|
||||
// With all of the modules (hopefully) installed, we can now try to load the
|
||||
// whole configuration tree.
|
||||
//
|
||||
// Just as above, we'll try loading both with the early and normal config
|
||||
// loaders here. Subsequent work will only use the early config, but
|
||||
// loading both gives us an opportunity to prefer the better error messages
|
||||
// from the normal loader if both fail.
|
||||
_, confDiags = c.loadConfig(path)
|
||||
// loaders here. Subsequent work will only use the early config, but loading
|
||||
// both gives us an opportunity to prefer the better error messages from the
|
||||
// normal loader if both fail.
|
||||
|
||||
earlyConfig, earlyConfDiags := c.loadConfigEarly(path)
|
||||
if confDiags.HasErrors() && !configUpgradeProbablyNeeded {
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||
diags = diags.Append(confDiags)
|
||||
c.showDiagnostics(diags)
|
||||
return 1
|
||||
}
|
||||
if earlyConfDiags.HasErrors() {
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||
diags = diags.Append(earlyConfDiags)
|
||||
|
@ -259,32 +235,28 @@ func (c *InitCommand) Run(args []string) int {
|
|||
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.
|
||||
_, confDiags = c.loadConfig(path)
|
||||
if confDiags.HasErrors() {
|
||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||
diags = diags.Append(confDiags)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var back backend.Backend
|
||||
if flagBackend {
|
||||
switch {
|
||||
case configUpgradeProbablyNeeded:
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Skipping backend initialization pending configuration upgrade",
|
||||
// The "below" in this message is referring to the special
|
||||
// note about running "terraform 0.12upgrade" that we'll
|
||||
// print out at the end when configUpgradeProbablyNeeded is set.
|
||||
"The root module configuration contains errors that may be fixed by running the configuration upgrade tool, so Terraform is skipping backend initialization. See below for more information.",
|
||||
))
|
||||
default:
|
||||
|
||||
be, backendOutput, backendDiags := c.initBackend(rootMod, flagConfigExtra)
|
||||
diags = diags.Append(backendDiags)
|
||||
if backendDiags.HasErrors() {
|
||||
|
@ -295,7 +267,6 @@ func (c *InitCommand) Run(args []string) int {
|
|||
header = true
|
||||
}
|
||||
back = be
|
||||
}
|
||||
} else {
|
||||
// load the previously-stored backend config
|
||||
be, backendDiags := c.Meta.backendFromState()
|
||||
|
@ -365,16 +336,6 @@ func (c *InitCommand) Run(args []string) int {
|
|||
// by errors then we'll output them here so that the success message is
|
||||
// still the final thing shown.
|
||||
c.showDiagnostics(diags)
|
||||
|
||||
if configUpgradeProbablyNeeded {
|
||||
switch {
|
||||
case c.RunningInAutomation:
|
||||
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessConfigUpgrade)))
|
||||
default:
|
||||
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessConfigUpgradeCLI)))
|
||||
}
|
||||
return 0
|
||||
}
|
||||
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
|
||||
if !c.RunningInAutomation {
|
||||
// If we're not running in an automation wrapper, give the user
|
||||
|
@ -382,7 +343,6 @@ func (c *InitCommand) Run(args []string) int {
|
|||
// shell usage.
|
||||
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI)))
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -853,6 +813,19 @@ The Terraform configuration must be valid before initialization so that
|
|||
Terraform can determine which modules and providers need to be installed.
|
||||
`
|
||||
|
||||
const errInitConfigErrorMaybeLegacySyntax = `
|
||||
There are some problems with the configuration, described below.
|
||||
|
||||
Terraform found syntax errors in the configuration that prevented full
|
||||
initialization. If you've recently upgraded to Terraform v0.13 from Terraform
|
||||
v0.11, this may be because your configuration uses syntax constructs that are no
|
||||
longer valid, and so must be updated before full initialization is possible.
|
||||
|
||||
Manually update your configuration syntax, or install Terraform v0.12 and run
|
||||
terraform init for this configuration at a shell prompt for more information
|
||||
on how to update it for Terraform v0.12+ compatibility.
|
||||
`
|
||||
|
||||
const errInitCopyNotEmpty = `
|
||||
The working directory already contains files. The -from-module option requires
|
||||
an empty directory into which a copy of the referenced module will be placed.
|
||||
|
@ -882,34 +855,6 @@ rerun this command to reinitialize your working directory. If you forget, other
|
|||
commands will detect it and remind you to do so if necessary.
|
||||
`
|
||||
|
||||
const outputInitSuccessConfigUpgrade = `
|
||||
[reset][bold]Terraform has initialized, but configuration upgrades may be needed.[reset]
|
||||
|
||||
Terraform found syntax errors in the configuration that prevented full
|
||||
initialization. If you've recently upgraded to Terraform v0.12, this may be
|
||||
because your configuration uses syntax constructs that are no longer valid,
|
||||
and so must be updated before full initialization is possible.
|
||||
|
||||
Run terraform init for this configuration at a shell prompt for more information
|
||||
on how to update it for Terraform v0.12 compatibility.
|
||||
`
|
||||
|
||||
const outputInitSuccessConfigUpgradeCLI = `[reset][green]
|
||||
[reset][bold]Terraform has initialized, but configuration upgrades may be needed.[reset]
|
||||
|
||||
Terraform found syntax errors in the configuration that prevented full
|
||||
initialization. If you've recently upgraded to Terraform v0.12, this may be
|
||||
because your configuration uses syntax constructs that are no longer valid,
|
||||
and so must be updated before full initialization is possible.
|
||||
|
||||
Terraform has installed the required providers to support the configuration
|
||||
upgrade process. To begin upgrading your configuration, run the following:
|
||||
terraform 0.12upgrade
|
||||
|
||||
To see the full set of errors that led to this message, run:
|
||||
terraform validate
|
||||
`
|
||||
|
||||
const outputInitProvidersUnconstrained = `
|
||||
The following providers do not have any version constraints in configuration,
|
||||
so the latest version was installed.
|
||||
|
|
|
@ -1424,86 +1424,14 @@ func TestInit_pluginWithInternal(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestInit_012UpgradeNeeded(t *testing.T) {
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("init-012upgrade"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
}
|
||||
|
||||
installer := &mockProviderInstaller{
|
||||
Providers: map[string][]string{
|
||||
"null": []string{"1.0.0"},
|
||||
},
|
||||
Dir: m.pluginDir(),
|
||||
}
|
||||
|
||||
c := &InitCommand{
|
||||
Meta: m,
|
||||
providerInstaller: installer,
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Errorf("wrong exit status %d; want 0\nerror output:\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
if !strings.Contains(output, "terraform 0.12upgrade") {
|
||||
t.Errorf("doesn't look like we detected the need for config upgrade:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_012UpgradeNeededInAutomation(t *testing.T) {
|
||||
td := tempDir(t)
|
||||
copy.CopyDir(testFixturePath("init-012upgrade"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
ui := cli.NewMockUi()
|
||||
m := Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
RunningInAutomation: true,
|
||||
}
|
||||
|
||||
installer := &mockProviderInstaller{
|
||||
Providers: map[string][]string{
|
||||
"null": []string{"1.0.0"},
|
||||
},
|
||||
Dir: m.pluginDir(),
|
||||
}
|
||||
|
||||
c := &InitCommand{
|
||||
Meta: m,
|
||||
providerInstaller: installer,
|
||||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Errorf("wrong exit status %d; want 0\nerror output:\n%s", code, ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
output := ui.OutputWriter.String()
|
||||
if !strings.Contains(output, "Run terraform init for this configuration at a shell prompt") {
|
||||
t.Errorf("doesn't look like we instructed to run Terraform locally:\n%s", output)
|
||||
}
|
||||
if strings.Contains(output, "terraform 0.12upgrade") {
|
||||
// We don't prompt with an exact command in automation mode, since
|
||||
// the upgrade process is interactive and so it cannot be run in
|
||||
// automation.
|
||||
t.Errorf("looks like we incorrectly gave an upgrade command to run:\n%s", output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_syntaxErrorVersionSniff(t *testing.T) {
|
||||
// The module in this test uses terraform 0.11-style syntax. We expect that the
|
||||
// earlyconfig will succeed but the main loader fail, and return an error that
|
||||
// indicates that syntax upgrades may be required.
|
||||
func TestInit_syntaxErrorUpgradeHint(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
|
||||
// This module
|
||||
copy.CopyDir(testFixturePath("init-sniff-version-error"), td)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
@ -1517,16 +1445,13 @@ func TestInit_syntaxErrorVersionSniff(t *testing.T) {
|
|||
}
|
||||
|
||||
args := []string{}
|
||||
if code := c.Run(args); code != 0 {
|
||||
if code := c.Run(args); code != 1 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
// Check output.
|
||||
// Currently, this lands in the "upgrade may be needed" codepath, because
|
||||
// the intentional syntax error in our test fixture is something that
|
||||
// "terraform 0.12upgrade" could fix.
|
||||
output := ui.OutputWriter.String()
|
||||
if got, want := output, "Terraform has initialized, but configuration upgrades may be needed"; !strings.Contains(got, want) {
|
||||
output := ui.ErrorWriter.String()
|
||||
if got, want := output, "If you've recently upgraded to Terraform v0.13 from Terraform\nv0.11, this may be because your configuration uses syntax constructs that are no\nlonger valid"; !strings.Contains(got, want) {
|
||||
t.Fatalf("wrong output\ngot:\n%s\n\nwant: message containing %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
resource "null_resource" "foo" {
|
||||
# This construct trips up the HCL2 parser because it looks like a nested block
|
||||
# but has quoted keys like a map. The upgrade tool would add an equals sign
|
||||
# here to turn this into a map attribute, but "terraform init" must first
|
||||
# be able to install the null provider so the upgrade tool can know that
|
||||
# "triggers" is a map attribute.
|
||||
triggers {
|
||||
"foo" = "bar"
|
||||
}
|
||||
}
|
|
@ -308,7 +308,7 @@ func initCommands(config *cliconfig.Config, services *disco.Disco, providerSrc g
|
|||
//-----------------------------------------------------------
|
||||
|
||||
"0.12upgrade": func() (cli.Command, error) {
|
||||
return &command.ZeroTwelveUpgradeCommand{
|
||||
return &command.ZeroThirteenUpgradeCommand{
|
||||
Meta: meta,
|
||||
}, nil
|
||||
},
|
||||
|
|
|
@ -1,290 +0,0 @@
|
|||
package configupgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
hcl1 "github.com/hashicorp/hcl"
|
||||
hcl1ast "github.com/hashicorp/hcl/hcl/ast"
|
||||
hcl1parser "github.com/hashicorp/hcl/hcl/parser"
|
||||
hcl1token "github.com/hashicorp/hcl/hcl/token"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/moduledeps"
|
||||
"github.com/hashicorp/terraform/plugin/discovery"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// analysis is a container for the various different information gathered
|
||||
// by Upgrader.analyze.
|
||||
type analysis struct {
|
||||
ProviderSchemas map[string]*terraform.ProviderSchema
|
||||
ProvisionerSchemas map[string]*configschema.Block
|
||||
ResourceProviderType map[addrs.Resource]string
|
||||
ResourceHasCount map[addrs.Resource]bool
|
||||
VariableTypes map[string]string
|
||||
ModuleDir string
|
||||
}
|
||||
|
||||
// analyze processes the configuration files included inside the receiver
|
||||
// and returns an assortment of information required to make decisions during
|
||||
// a configuration upgrade.
|
||||
func (u *Upgrader) analyze(ms ModuleSources) (*analysis, error) {
|
||||
ret := &analysis{
|
||||
ProviderSchemas: make(map[string]*terraform.ProviderSchema),
|
||||
ProvisionerSchemas: make(map[string]*configschema.Block),
|
||||
ResourceProviderType: make(map[addrs.Resource]string),
|
||||
ResourceHasCount: make(map[addrs.Resource]bool),
|
||||
VariableTypes: make(map[string]string),
|
||||
}
|
||||
|
||||
m := &moduledeps.Module{
|
||||
Providers: make(moduledeps.Providers),
|
||||
}
|
||||
|
||||
// This is heavily based on terraform.ModuleTreeDependencies but
|
||||
// differs in that it works directly with the HCL1 AST rather than
|
||||
// the legacy config structs (and can thus outlive those) and that
|
||||
// it only works on one module at a time, and so doesn't need to
|
||||
// recurse into child calls.
|
||||
for name, src := range ms {
|
||||
if ext := fileExt(name); ext != ".tf" {
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] configupgrade: Analyzing %q", name)
|
||||
|
||||
f, err := hcl1parser.Parse(src)
|
||||
if err != nil {
|
||||
// If we encounter a syntax error then we'll just skip for now
|
||||
// and assume that we'll catch this again when we do the upgrade.
|
||||
// If not, we'll break the upgrade step of renaming .tf files to
|
||||
// .tf.json if they seem to be JSON syntax.
|
||||
log.Printf("[ERROR] Failed to parse %q: %s", name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
list, ok := f.Node.(*hcl1ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
|
||||
}
|
||||
|
||||
if providersList := list.Filter("provider"); len(providersList.Items) > 0 {
|
||||
providerObjs := providersList.Children()
|
||||
for _, providerObj := range providerObjs.Items {
|
||||
if len(providerObj.Keys) != 1 {
|
||||
return nil, fmt.Errorf("provider block has wrong number of labels")
|
||||
}
|
||||
name := providerObj.Keys[0].Token.Value().(string)
|
||||
|
||||
var listVal *hcl1ast.ObjectList
|
||||
if ot, ok := providerObj.Val.(*hcl1ast.ObjectType); ok {
|
||||
listVal = ot.List
|
||||
} else {
|
||||
return nil, fmt.Errorf("provider %q: must be a block", name)
|
||||
}
|
||||
|
||||
var versionStr string
|
||||
if a := listVal.Filter("version"); len(a.Items) > 0 {
|
||||
err := hcl1.DecodeObject(&versionStr, a.Items[0].Val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading version for provider %q: %s", name, err)
|
||||
}
|
||||
}
|
||||
var constraints discovery.Constraints
|
||||
if versionStr != "" {
|
||||
constraints, err = discovery.ConstraintStr(versionStr).Parse()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing version for provider %q: %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
var alias string
|
||||
if a := listVal.Filter("alias"); len(a.Items) > 0 {
|
||||
err := hcl1.DecodeObject(&alias, a.Items[0].Val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading alias for provider %q: %s", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
fqn := addrs.NewLegacyProvider(name)
|
||||
log.Printf("[TRACE] Provider block requires provider %q", fqn.LegacyString())
|
||||
m.Providers[fqn] = moduledeps.ProviderDependency{
|
||||
Constraints: constraints,
|
||||
Reason: moduledeps.ProviderDependencyExplicit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
resourceConfigsList := list.Filter("resource")
|
||||
dataResourceConfigsList := list.Filter("data")
|
||||
// list.Filter annoyingly strips off the key used for matching,
|
||||
// so we'll put it back here so we can distinguish our two types
|
||||
// of blocks below.
|
||||
for _, obj := range resourceConfigsList.Items {
|
||||
obj.Keys = append([]*hcl1ast.ObjectKey{
|
||||
{Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "resource"}},
|
||||
}, obj.Keys...)
|
||||
}
|
||||
for _, obj := range dataResourceConfigsList.Items {
|
||||
obj.Keys = append([]*hcl1ast.ObjectKey{
|
||||
{Token: hcl1token.Token{Type: hcl1token.IDENT, Text: "data"}},
|
||||
}, obj.Keys...)
|
||||
}
|
||||
// Now we can merge the two lists together, since we can distinguish
|
||||
// them just by their keys[0].
|
||||
resourceConfigsList.Items = append(resourceConfigsList.Items, dataResourceConfigsList.Items...)
|
||||
|
||||
resourceObjs := resourceConfigsList.Children()
|
||||
for _, resourceObj := range resourceObjs.Items {
|
||||
if len(resourceObj.Keys) != 3 {
|
||||
return nil, fmt.Errorf("resource or data block has wrong number of labels")
|
||||
}
|
||||
typeName := resourceObj.Keys[1].Token.Value().(string)
|
||||
name := resourceObj.Keys[2].Token.Value().(string)
|
||||
rAddr := addrs.Resource{
|
||||
Mode: addrs.ManagedResourceMode,
|
||||
Type: typeName,
|
||||
Name: name,
|
||||
}
|
||||
if resourceObj.Keys[0].Token.Value() == "data" {
|
||||
rAddr.Mode = addrs.DataResourceMode
|
||||
}
|
||||
|
||||
var listVal *hcl1ast.ObjectList
|
||||
if ot, ok := resourceObj.Val.(*hcl1ast.ObjectType); ok {
|
||||
listVal = ot.List
|
||||
} else {
|
||||
return nil, fmt.Errorf("config for %q must be a block", rAddr)
|
||||
}
|
||||
|
||||
if o := listVal.Filter("count"); len(o.Items) > 0 {
|
||||
ret.ResourceHasCount[rAddr] = true
|
||||
} else {
|
||||
ret.ResourceHasCount[rAddr] = false
|
||||
}
|
||||
|
||||
var providerKey string
|
||||
if o := listVal.Filter("provider"); len(o.Items) > 0 {
|
||||
err := hcl1.DecodeObject(&providerKey, o.Items[0].Val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading provider for resource %s: %s", rAddr, err)
|
||||
}
|
||||
}
|
||||
|
||||
var fqn addrs.Provider
|
||||
if providerKey == "" {
|
||||
// we are assuming a default, legacy provider for 012
|
||||
// configurations
|
||||
fqn = addrs.NewLegacyProvider(rAddr.ImpliedProvider())
|
||||
} else {
|
||||
// ProviderDependencies only need to know the provider FQN
|
||||
// strip any alias from the providerKey
|
||||
parts := strings.Split(providerKey, ".")
|
||||
fqn = addrs.NewLegacyProvider(parts[0])
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] Resource block for %s requires provider %q", rAddr, fqn)
|
||||
if _, exists := m.Providers[fqn]; !exists {
|
||||
m.Providers[fqn] = moduledeps.ProviderDependency{
|
||||
Reason: moduledeps.ProviderDependencyImplicit,
|
||||
}
|
||||
}
|
||||
ret.ResourceProviderType[rAddr] = fqn.Type
|
||||
}
|
||||
}
|
||||
|
||||
if variablesList := list.Filter("variable"); len(variablesList.Items) > 0 {
|
||||
variableObjs := variablesList.Children()
|
||||
for _, variableObj := range variableObjs.Items {
|
||||
if len(variableObj.Keys) != 1 {
|
||||
return nil, fmt.Errorf("variable block has wrong number of labels")
|
||||
}
|
||||
name := variableObj.Keys[0].Token.Value().(string)
|
||||
|
||||
var listVal *hcl1ast.ObjectList
|
||||
if ot, ok := variableObj.Val.(*hcl1ast.ObjectType); ok {
|
||||
listVal = ot.List
|
||||
} else {
|
||||
return nil, fmt.Errorf("variable %q: must be a block", name)
|
||||
}
|
||||
|
||||
var typeStr string
|
||||
if a := listVal.Filter("type"); len(a.Items) > 0 {
|
||||
err := hcl1.DecodeObject(&typeStr, a.Items[0].Val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading type for variable %q: %s", name, err)
|
||||
}
|
||||
} else if a := listVal.Filter("default"); len(a.Items) > 0 {
|
||||
switch a.Items[0].Val.(type) {
|
||||
case *hcl1ast.ObjectType:
|
||||
typeStr = "map"
|
||||
case *hcl1ast.ListType:
|
||||
typeStr = "list"
|
||||
default:
|
||||
typeStr = "string"
|
||||
}
|
||||
} else {
|
||||
typeStr = "string"
|
||||
}
|
||||
|
||||
ret.VariableTypes[name] = strings.TrimSpace(typeStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
providerFactories, errs := u.Providers.ResolveProviders(m.PluginRequirements())
|
||||
if len(errs) > 0 {
|
||||
var errorsMsg string
|
||||
for _, err := range errs {
|
||||
errorsMsg += fmt.Sprintf("\n- %s", err)
|
||||
}
|
||||
return nil, fmt.Errorf("error resolving providers:\n%s", errorsMsg)
|
||||
}
|
||||
|
||||
for fqn, fn := range providerFactories {
|
||||
log.Printf("[TRACE] Fetching schema from provider %q", fqn.LegacyString())
|
||||
provider, err := fn()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load provider %q: %s", fqn.LegacyString(), err)
|
||||
}
|
||||
|
||||
resp := provider.GetSchema()
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
return nil, resp.Diagnostics.Err()
|
||||
}
|
||||
|
||||
schema := &terraform.ProviderSchema{
|
||||
Provider: resp.Provider.Block,
|
||||
ResourceTypes: map[string]*configschema.Block{},
|
||||
DataSources: map[string]*configschema.Block{},
|
||||
}
|
||||
for t, s := range resp.ResourceTypes {
|
||||
schema.ResourceTypes[t] = s.Block
|
||||
}
|
||||
for t, s := range resp.DataSources {
|
||||
schema.DataSources[t] = s.Block
|
||||
}
|
||||
ret.ProviderSchemas[fqn.LegacyString()] = schema
|
||||
}
|
||||
|
||||
for name, fn := range u.Provisioners {
|
||||
log.Printf("[TRACE] Fetching schema from provisioner %q", name)
|
||||
provisioner, err := fn()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load provisioner %q: %s", name, err)
|
||||
}
|
||||
|
||||
resp := provisioner.GetSchema()
|
||||
if resp.Diagnostics.HasErrors() {
|
||||
return nil, resp.Diagnostics.Err()
|
||||
}
|
||||
|
||||
ret.ProvisionerSchemas[name] = resp.Provisioner
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
package configupgrade
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
hcl2 "github.com/hashicorp/hcl/v2"
|
||||
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/lang"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
)
|
||||
|
||||
// InferExpressionType attempts to determine a result type for the given
|
||||
// expression source code, which should already have been upgraded to new
|
||||
// expression syntax.
|
||||
//
|
||||
// If self is non-nil, it will determine the meaning of the special "self"
|
||||
// reference.
|
||||
//
|
||||
// If such an inference isn't possible, either because of limitations of
|
||||
// static analysis or because of errors in the expression, the result is
|
||||
// cty.DynamicPseudoType indicating "unknown".
|
||||
func (an *analysis) InferExpressionType(src []byte, self addrs.Referenceable) cty.Type {
|
||||
expr, diags := hcl2syntax.ParseExpression(src, "", hcl2.Pos{Line: 1, Column: 1})
|
||||
if diags.HasErrors() {
|
||||
// If there's a syntax error then analysis is impossible.
|
||||
return cty.DynamicPseudoType
|
||||
}
|
||||
|
||||
data := analysisData{an}
|
||||
scope := &lang.Scope{
|
||||
Data: data,
|
||||
SelfAddr: self,
|
||||
PureOnly: false,
|
||||
BaseDir: ".",
|
||||
}
|
||||
val, _ := scope.EvalExpr(expr, cty.DynamicPseudoType)
|
||||
|
||||
// Value will be cty.DynamicVal if either inference was impossible or
|
||||
// if there was an error, leading to cty.DynamicPseudoType here.
|
||||
return val.Type()
|
||||
}
|
||||
|
||||
// analysisData is an implementation of lang.Data that returns unknown values
|
||||
// of suitable types in order to achieve approximate dynamic analysis of
|
||||
// expression result types, which we need for some upgrade rules.
|
||||
//
|
||||
// Unlike a usual implementation of this interface, this one never returns
|
||||
// errors and will instead just return cty.DynamicVal if it can't produce
|
||||
// an exact type for any reason. This can then allow partial upgrading to
|
||||
// proceed and the caller can emit warning comments for ambiguous situations.
|
||||
//
|
||||
// N.B.: Source ranges in the data methods are meaningless, since they are
|
||||
// just relative to the byte array passed to InferExpressionType, not to
|
||||
// any real input file.
|
||||
type analysisData struct {
|
||||
an *analysis
|
||||
}
|
||||
|
||||
var _ lang.Data = (*analysisData)(nil)
|
||||
|
||||
func (d analysisData) StaticValidateReferences(refs []*addrs.Reference, self addrs.Referenceable) tfdiags.Diagnostics {
|
||||
// This implementation doesn't do any static validation.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetCountAttr(addr addrs.CountAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// All valid count attributes are numbers
|
||||
return cty.UnknownVal(cty.Number), nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetForEachAttr(addr addrs.ForEachAttr, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetResource(addr addrs.Resource, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
|
||||
|
||||
// Our analysis pass should've found a suitable schema for every resource
|
||||
// type in the module.
|
||||
providerType, ok := d.an.ResourceProviderType[addr]
|
||||
if !ok {
|
||||
// Should not be possible, since analysis visits every resource block.
|
||||
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider type for %s", addr)
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
providerSchema, ok := d.an.ProviderSchemas[providerType]
|
||||
if !ok {
|
||||
// Should not be possible, since analysis loads schema for every provider.
|
||||
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a provider schema for for %q", providerType)
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
schema, _ := providerSchema.SchemaForResourceAddr(addr)
|
||||
if schema == nil {
|
||||
// Should not be possible, since analysis loads schema for every provider.
|
||||
log.Printf("[TRACE] configupgrade: analysis.GetResource doesn't have a schema for for %s", addr)
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
objTy := schema.ImpliedType()
|
||||
|
||||
// We'll emulate the normal evaluator's behavor of deciding whether to
|
||||
// return a list or a single object type depending on whether count is
|
||||
// set and whether an instance key is given in the address.
|
||||
if d.an.ResourceHasCount[addr] {
|
||||
log.Printf("[TRACE] configupgrade: %s refers to counted instance, so result is a list of %#v", addr, objTy)
|
||||
return cty.UnknownVal(cty.List(objTy)), nil
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] configupgrade: %s refers to non-counted instance, so result is single object", addr)
|
||||
return cty.UnknownVal(objTy), nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetLocalValue(addrs.LocalValue, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// We can only predict these in general by recursively evaluating their
|
||||
// expressions, which creates some undesirable complexity here so for
|
||||
// now we'll just skip analyses with locals and see if this complexity
|
||||
// is warranted with real-world testing.
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetModuleInstance(addrs.ModuleCallInstance, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// We only work on one module at a time during upgrading, so we have no
|
||||
// information about the outputs of a child module.
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetModuleInstanceOutput(addrs.ModuleCallOutput, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// We only work on one module at a time during upgrading, so we have no
|
||||
// information about the outputs of a child module.
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetPathAttr(addrs.PathAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// All valid path attributes are strings
|
||||
return cty.UnknownVal(cty.String), nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetTerraformAttr(addrs.TerraformAttr, tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// All valid "terraform" attributes are strings
|
||||
return cty.UnknownVal(cty.String), nil
|
||||
}
|
||||
|
||||
func (d analysisData) GetInputVariable(addr addrs.InputVariable, rng tfdiags.SourceRange) (cty.Value, tfdiags.Diagnostics) {
|
||||
// TODO: Collect shallow type information (list vs. map vs. string vs. unknown)
|
||||
// in analysis and then return a similarly-approximate type here.
|
||||
log.Printf("[TRACE] configupgrade: Determining type for %s", addr)
|
||||
name := addr.Name
|
||||
typeName := d.an.VariableTypes[name]
|
||||
switch typeName {
|
||||
case "list":
|
||||
return cty.UnknownVal(cty.List(cty.DynamicPseudoType)), nil
|
||||
case "map":
|
||||
return cty.UnknownVal(cty.Map(cty.DynamicPseudoType)), nil
|
||||
case "string":
|
||||
return cty.UnknownVal(cty.String), nil
|
||||
default:
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Package configupgrade upgrades configurations targeting our legacy
|
||||
// configuration loader (in package "config") to be compatible with and
|
||||
// idiomatic for the newer configuration loader (in package "configs").
|
||||
//
|
||||
// It works on one module directory at a time, producing new content for
|
||||
// each existing .tf file and possibly creating new files as needed. The
|
||||
// legacy HCL and HIL parsers are used to read the existing configuration
|
||||
// for maximum compatibility with any non-idiomatic constructs that were
|
||||
// accepted by those implementations but not accepted by the new HCL parsers.
|
||||
//
|
||||
// Unlike the loaders and validators elsewhere in Terraform, this package
|
||||
// always generates diagnostics with paths relative to the module directory
|
||||
// currently being upgraded, with no intermediate paths. This means that the
|
||||
// filenames in these ranges can be used directly as keys into the ModuleSources
|
||||
// map that the file was parsed from.
|
||||
package configupgrade
|
|
@ -1,216 +0,0 @@
|
|||
package configupgrade
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/tfdiags"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
hcl2syntax "github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
|
||||
version "github.com/hashicorp/go-version"
|
||||
)
|
||||
|
||||
type ModuleSources map[string][]byte
|
||||
|
||||
// LoadModule looks for Terraform configuration files in the given directory
|
||||
// and loads each of them into memory as source code, in preparation for
|
||||
// further analysis and conversion.
|
||||
//
|
||||
// At this stage the files are not parsed at all. Instead, we just read the
|
||||
// raw bytes from the file so that they can be passed into a parser in a
|
||||
// separate step.
|
||||
//
|
||||
// If the given directory or any of the files cannot be read, an error is
|
||||
// returned. It is not safe to proceed with processing in that case because
|
||||
// we cannot "see" all of the source code for the configuration.
|
||||
func LoadModule(dir string) (ModuleSources, error) {
|
||||
entries, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make(ModuleSources)
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
if configs.IsIgnoredFile(name) {
|
||||
continue
|
||||
}
|
||||
ext := fileExt(name)
|
||||
if ext == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
fullPath := filepath.Join(dir, name)
|
||||
src, err := ioutil.ReadFile(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret[name] = src
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// UnusedFilename finds a filename that isn't already used by a file in
|
||||
// the receiving sources and returns it.
|
||||
//
|
||||
// The given "proposed" name is returned verbatim if it isn't already used.
|
||||
// Otherwise, the function will try appending incrementing integers to the
|
||||
// proposed name until an unused name is found. Callers should propose names
|
||||
// that they do not expect to already be in use so that numeric suffixes are
|
||||
// only used in rare cases.
|
||||
//
|
||||
// The proposed name must end in either ".tf" or ".tf.json" because a
|
||||
// ModuleSources only has visibility into such files. This function will
|
||||
// panic if given a file whose name does not end with one of these
|
||||
// extensions.
|
||||
//
|
||||
// A ModuleSources only works on one directory at a time, so the proposed
|
||||
// name must not contain any directory separator characters.
|
||||
func (ms ModuleSources) UnusedFilename(proposed string) string {
|
||||
ext := fileExt(proposed)
|
||||
if ext == "" {
|
||||
panic(fmt.Errorf("method UnusedFilename used with invalid proposal %q", proposed))
|
||||
}
|
||||
|
||||
if _, exists := ms[proposed]; !exists {
|
||||
return proposed
|
||||
}
|
||||
|
||||
base := proposed[:len(proposed)-len(ext)]
|
||||
for i := 1; ; i++ {
|
||||
try := fmt.Sprintf("%s-%d%s", base, i, ext)
|
||||
if _, exists := ms[try]; !exists {
|
||||
return try
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MaybeAlreadyUpgraded is a heuristic to see if a given module may have
|
||||
// already been upgraded by this package.
|
||||
//
|
||||
// The heuristic used is to look for a Terraform Core version constraint in
|
||||
// any of the given sources that seems to be requiring a version greater than
|
||||
// or equal to v0.12.0. If true is returned then the source range of the found
|
||||
// version constraint is returned in case the caller wishes to present it to
|
||||
// the user as context for a warning message. The returned range is not
|
||||
// meaningful if false is returned.
|
||||
func (ms ModuleSources) MaybeAlreadyUpgraded() (bool, tfdiags.SourceRange) {
|
||||
for name, src := range ms {
|
||||
f, diags := hcl2syntax.ParseConfig(src, name, hcl.Pos{Line: 1, Column: 1})
|
||||
if diags.HasErrors() {
|
||||
// If we can't parse at all then that's a reasonable signal that
|
||||
// we _haven't_ been upgraded yet, but we'll continue checking
|
||||
// other files anyway.
|
||||
continue
|
||||
}
|
||||
|
||||
content, _, diags := f.Body.PartialContent(&hcl.BodySchema{
|
||||
Blocks: []hcl.BlockHeaderSchema{
|
||||
{
|
||||
Type: "terraform",
|
||||
},
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
// Suggests that the file has an invalid "terraform" block, such
|
||||
// as one with labels.
|
||||
continue
|
||||
}
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
content, _, diags := block.Body.PartialContent(&hcl.BodySchema{
|
||||
Attributes: []hcl.AttributeSchema{
|
||||
{
|
||||
Name: "required_version",
|
||||
},
|
||||
},
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
attr, present := content.Attributes["required_version"]
|
||||
if !present {
|
||||
continue
|
||||
}
|
||||
|
||||
constraintVal, diags := attr.Expr.Value(nil)
|
||||
if diags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
if constraintVal.Type() != cty.String || constraintVal.IsNull() {
|
||||
continue
|
||||
}
|
||||
|
||||
constraints, err := version.NewConstraint(constraintVal.AsString())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// The go-version package doesn't actually let us see the details
|
||||
// of the parsed constraints here, so we now need a bit of an
|
||||
// abstraction inversion to decide if any of the given constraints
|
||||
// match our heuristic. However, we do at least get to benefit
|
||||
// from go-version's ability to extract multiple constraints from
|
||||
// a single string and the fact that it's already validated each
|
||||
// constraint to match its expected pattern.
|
||||
Constraints:
|
||||
for _, constraint := range constraints {
|
||||
str := strings.TrimSpace(constraint.String())
|
||||
// Want to match >, >= and ~> here.
|
||||
if !(strings.HasPrefix(str, ">") || strings.HasPrefix(str, "~>")) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to find something in this string that'll parse as a version.
|
||||
for i := 1; i < len(str); i++ {
|
||||
candidate := str[i:]
|
||||
v, err := version.NewVersion(candidate)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.Equal(firstVersionWithNewParser) || v.GreaterThan(firstVersionWithNewParser) {
|
||||
// This constraint appears to be preventing the old
|
||||
// parser from being used, so we suspect it was
|
||||
// already upgraded.
|
||||
return true, tfdiags.SourceRangeFromHCL(attr.Range)
|
||||
}
|
||||
|
||||
// If we fall out here then we _did_ find something that
|
||||
// parses as a version, so we'll stop and move on to the
|
||||
// next constraint. (Otherwise we'll pass by 0.7.0 and find
|
||||
// 7.0, which is also a valid version.)
|
||||
continue Constraints
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, tfdiags.SourceRange{}
|
||||
}
|
||||
|
||||
var firstVersionWithNewParser = version.Must(version.NewVersion("0.12.0"))
|
||||
|
||||
// fileExt returns the Terraform configuration extension of the given
|
||||
// path, or a blank string if it is not a recognized extension.
|
||||
func fileExt(path string) string {
|
||||
if strings.HasSuffix(path, ".tf") {
|
||||
return ".tf"
|
||||
} else if strings.HasSuffix(path, ".tf.json") {
|
||||
return ".tf.json"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package configupgrade
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
func TestMaybeAlreadyUpgraded(t *testing.T) {
|
||||
t.Run("already upgraded", func(t *testing.T) {
|
||||
sources, err := LoadModule("testdata/already-upgraded")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, rng := sources.MaybeAlreadyUpgraded()
|
||||
if !got {
|
||||
t.Fatal("result is false, but want true")
|
||||
}
|
||||
gotRange := rng.ToHCL()
|
||||
wantRange := hcl.Range{
|
||||
Filename: "versions.tf",
|
||||
Start: hcl.Pos{Line: 3, Column: 3, Byte: 15},
|
||||
End: hcl.Pos{Line: 3, Column: 33, Byte: 45},
|
||||
}
|
||||
if !reflect.DeepEqual(gotRange, wantRange) {
|
||||
t.Errorf("wrong range\ngot: %#v\nwant: %#v", gotRange, wantRange)
|
||||
}
|
||||
})
|
||||
t.Run("not yet upgraded", func(t *testing.T) {
|
||||
sources, err := LoadModule("testdata/valid/noop/input")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, _ := sources.MaybeAlreadyUpgraded()
|
||||
if got {
|
||||
t.Fatal("result is true, but want false")
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
terraform {
|
||||
required_version = ">= 0.13.0"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
locals {
|
||||
foo = "bar", baz = "boop"
|
||||
}
|
||||
|
||||
resource "test_instance" "foo" {
|
||||
image = "b", type = "d"
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
locals {
|
||||
foo = "bar"
|
||||
baz = "boop"
|
||||
}
|
||||
|
||||
resource "test_instance" "foo" {
|
||||
image = "b"
|
||||
type = "d"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network = [
|
||||
{
|
||||
cidr_block = "10.1.0.0/16"
|
||||
},
|
||||
{
|
||||
cidr_block = "10.2.0.0/16"
|
||||
},
|
||||
]
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network {
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
network {
|
||||
cidr_block = "10.2.0.0/16"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network = [
|
||||
{
|
||||
cidr_block = "10.1.2.0/24"
|
||||
},
|
||||
"${var.baz}"
|
||||
]
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network {
|
||||
cidr_block = "10.1.2.0/24"
|
||||
}
|
||||
dynamic "network" {
|
||||
for_each = [var.baz]
|
||||
content {
|
||||
# TF-UPGRADE-TODO: The automatic upgrade tool can't predict
|
||||
# which keys might be set in maps assigned here, so it has
|
||||
# produced a comprehensive set here. Consider simplifying
|
||||
# this after confirming which keys can be set in practice.
|
||||
|
||||
cidr_block = lookup(network.value, "cidr_block", null)
|
||||
|
||||
dynamic "subnet" {
|
||||
for_each = lookup(network.value, "subnet", [])
|
||||
content {
|
||||
number = subnet.value.number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network {
|
||||
subnet = "${var.baz}"
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network {
|
||||
dynamic "subnet" {
|
||||
for_each = var.baz
|
||||
content {
|
||||
# TF-UPGRADE-TODO: The automatic upgrade tool can't predict
|
||||
# which keys might be set in maps assigned here, so it has
|
||||
# produced a comprehensive set here. Consider simplifying
|
||||
# this after confirming which keys can be set in practice.
|
||||
|
||||
number = subnet.value.number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network = "${var.baz}"
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
dynamic "network" {
|
||||
for_each = var.baz
|
||||
content {
|
||||
# TF-UPGRADE-TODO: The automatic upgrade tool can't predict
|
||||
# which keys might be set in maps assigned here, so it has
|
||||
# produced a comprehensive set here. Consider simplifying
|
||||
# this after confirming which keys can be set in practice.
|
||||
|
||||
cidr_block = lookup(network.value, "cidr_block", null)
|
||||
|
||||
dynamic "subnet" {
|
||||
for_each = lookup(network.value, "subnet", [])
|
||||
content {
|
||||
number = subnet.value.number
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network = {
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
network {
|
||||
cidr_block = "10.1.0.0/16"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
depends_on = [
|
||||
"test_instance.bar",
|
||||
"test_instance.bar.0",
|
||||
"test_instance.bar.*",
|
||||
"test_instance.bar.invalid",
|
||||
"data.test_instance.baz",
|
||||
"data.test_instance.baz.invalid",
|
||||
"module.foo.bar",
|
||||
"module.foo",
|
||||
]
|
||||
}
|
||||
|
||||
output "foo" {
|
||||
value = "a"
|
||||
depends_on = ["test_instance.foo"]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
depends_on = [
|
||||
test_instance.bar,
|
||||
test_instance.bar[0],
|
||||
test_instance.bar,
|
||||
test_instance.bar,
|
||||
data.test_instance.baz,
|
||||
data.test_instance.baz,
|
||||
module.foo.bar,
|
||||
module.foo,
|
||||
]
|
||||
}
|
||||
|
||||
output "foo" {
|
||||
value = "a"
|
||||
depends_on = [test_instance.foo]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
resource "test_instance" "a" {
|
||||
subnet_ids = ["boop"] # this attribute takes a set of strings
|
||||
}
|
||||
|
||||
output "b" {
|
||||
value = "${element(test_instance.a.subnet_ids, 0)}"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
resource "test_instance" "a" {
|
||||
subnet_ids = ["boop"] # this attribute takes a set of strings
|
||||
}
|
||||
|
||||
output "b" {
|
||||
value = element(tolist(test_instance.a.subnet_ids), 0)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
locals {
|
||||
list = "${list("a", "b", "c")}"
|
||||
list_concat = "${concat(list("a", "b"), list("c"))}"
|
||||
list_empty = "${list()}"
|
||||
|
||||
map = "${map("a", "b", "c", "d")}"
|
||||
map_merge = "${merge(map("a", "b"), map("c", "d"))}"
|
||||
map_empty = "${map()}"
|
||||
map_invalid = "${map("a", "b", "c")}"
|
||||
|
||||
list_of_map = "${list(map("a", "b"))}"
|
||||
map_of_list = "${map("a", list("b"))}"
|
||||
|
||||
lookup_literal = "${lookup(map("a", "b"), "a")}"
|
||||
lookup_ref = "${lookup(local.map, "a")}"
|
||||
|
||||
# Undocumented HIL implementation details that some users nonetheless relied on.
|
||||
conv_bool_to_string = "${__builtin_BoolToString(true)}"
|
||||
conv_float_to_int = "${__builtin_FloatToInt(1.5)}"
|
||||
conv_float_to_string = "${__builtin_FloatToString(1.5)}"
|
||||
conv_int_to_float = "${__builtin_IntToFloat(1)}"
|
||||
conv_int_to_string = "${__builtin_IntToString(1)}"
|
||||
conv_string_to_int = "${__builtin_StringToInt("1")}"
|
||||
conv_string_to_float = "${__builtin_StringToFloat("1.5")}"
|
||||
conv_string_to_bool = "${__builtin_StringToBool("true")}"
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
locals {
|
||||
list = ["a", "b", "c"]
|
||||
list_concat = concat(["a", "b"], ["c"])
|
||||
list_empty = []
|
||||
|
||||
map = {
|
||||
"a" = "b"
|
||||
"c" = "d"
|
||||
}
|
||||
map_merge = merge(
|
||||
{
|
||||
"a" = "b"
|
||||
},
|
||||
{
|
||||
"c" = "d"
|
||||
},
|
||||
)
|
||||
map_empty = {}
|
||||
map_invalid = map("a", "b", "c")
|
||||
|
||||
list_of_map = [
|
||||
{
|
||||
"a" = "b"
|
||||
},
|
||||
]
|
||||
map_of_list = {
|
||||
"a" = ["b"]
|
||||
}
|
||||
|
||||
lookup_literal = {
|
||||
"a" = "b"
|
||||
}["a"]
|
||||
lookup_ref = local.map["a"]
|
||||
|
||||
# Undocumented HIL implementation details that some users nonetheless relied on.
|
||||
conv_bool_to_string = tostring(tobool(true))
|
||||
conv_float_to_int = floor(1.5)
|
||||
conv_float_to_string = tostring(tonumber(1.5))
|
||||
conv_int_to_float = floor(1)
|
||||
conv_int_to_string = tostring(floor(1))
|
||||
conv_string_to_int = floor(tostring("1"))
|
||||
conv_string_to_float = tonumber(tostring("1.5"))
|
||||
conv_string_to_bool = tobool(tostring("true"))
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
image = "${sha256(file("foo.txt"))}"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
image = filesha256("foo.txt")
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
locals {
|
||||
baz = { "greeting" = "hello" }
|
||||
cert_options = <<-EOF
|
||||
A
|
||||
B ${lookup(local.baz, "greeting")}
|
||||
C
|
||||
EOF
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = "${local.cert_options}"
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
locals {
|
||||
baz = {
|
||||
"greeting" = "hello"
|
||||
}
|
||||
cert_options = <<-EOF
|
||||
A
|
||||
B ${local.baz["greeting"]}
|
||||
C
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = local.cert_options
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
variable "foo" {
|
||||
default = <<EOT
|
||||
Interpolation sequences ${are not allowed} in here.
|
||||
EOT
|
||||
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
variable "foo" {
|
||||
default = <<EOT
|
||||
Interpolation sequences $${are not allowed} in here.
|
||||
EOT
|
||||
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
locals {
|
||||
cert_options = <<EOF
|
||||
--cert-file=/etc/ssl/etcd/server.crt \
|
||||
--peer-trusted-ca-file=/etc/ssl/etcd/ca.crt \
|
||||
--peer-client-cert-auth=trueEOF
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = "${local.cert_options}"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
locals {
|
||||
cert_options = <<EOF
|
||||
--cert-file=/etc/ssl/etcd/server.crt \
|
||||
--peer-trusted-ca-file=/etc/ssl/etcd/ca.crt \
|
||||
--peer-client-cert-auth=true
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = local.cert_options
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
locals {
|
||||
baz = { "greeting" = "hello" }
|
||||
cert_options = <<EOF
|
||||
A
|
||||
B ${lookup(local.baz, "greeting")}
|
||||
C
|
||||
EOF
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = "${local.cert_options}"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
locals {
|
||||
baz = {
|
||||
"greeting" = "hello"
|
||||
}
|
||||
cert_options = <<EOF
|
||||
A
|
||||
B ${local.baz["greeting"]}
|
||||
C
|
||||
|
||||
EOF
|
||||
|
||||
}
|
||||
|
||||
output "local" {
|
||||
value = local.cert_options
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
"a.%",
|
||||
"b.#",
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
a,
|
||||
b,
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
resource "test_instance" "first_many" {
|
||||
count = 2
|
||||
}
|
||||
|
||||
resource "test_instance" "one" {
|
||||
image = "${test_instance.first_many.*.id[0]}"
|
||||
}
|
||||
|
||||
resource "test_instance" "splat_of_one" {
|
||||
image = "${test_instance.one.*.id[0]}"
|
||||
}
|
||||
|
||||
resource "test_instance" "second_many" {
|
||||
count = "${length(test_instance.first_many)}"
|
||||
security_groups = "${test_instance.first_many.*.id[count.index]}"
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
resource "test_instance" "first_many" {
|
||||
count = 2
|
||||
}
|
||||
|
||||
resource "test_instance" "one" {
|
||||
image = test_instance.first_many[0].id
|
||||
}
|
||||
|
||||
resource "test_instance" "splat_of_one" {
|
||||
image = test_instance.one.*.id[0]
|
||||
}
|
||||
|
||||
resource "test_instance" "second_many" {
|
||||
count = length(test_instance.first_many)
|
||||
security_groups = test_instance.first_many[count.index].id
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
variable "list" {
|
||||
type = "list"
|
||||
|
||||
default = [
|
||||
"foo", # I am a comment
|
||||
"bar", # I am also a comment
|
||||
"baz",
|
||||
]
|
||||
}
|
||||
|
||||
variable "list2" {
|
||||
type = "list"
|
||||
|
||||
default = [
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
]
|
||||
}
|
||||
|
||||
variable "list_the_third" {
|
||||
type = "list"
|
||||
|
||||
default = ["foo", "bar", "baz"]
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
variable "list" {
|
||||
type = list(string)
|
||||
|
||||
default = [
|
||||
"foo", # I am a comment
|
||||
"bar", # I am also a comment
|
||||
"baz",
|
||||
]
|
||||
}
|
||||
|
||||
variable "list2" {
|
||||
type = list(string)
|
||||
|
||||
default = [
|
||||
"foo",
|
||||
"bar",
|
||||
"baz",
|
||||
]
|
||||
}
|
||||
|
||||
variable "list_the_third" {
|
||||
type = list(string)
|
||||
|
||||
default = ["foo", "bar", "baz"]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
output "foo" {
|
||||
value = "${path.module}"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
output "foo" {
|
||||
value = path.module
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
resource "test_instance" "1_invalid_resource_name" {
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
# TF-UPGRADE-TODO: In Terraform v0.11 and earlier, it was possible to begin a
|
||||
# resource name with a number, but it is no longer possible in Terraform v0.12.
|
||||
#
|
||||
# Rename the resource and run `terraform state mv` to apply the rename in the
|
||||
# state. Detailed information on the `state move` command can be found in the
|
||||
# documentation online: https://www.terraform.io/docs/commands/state/mv.html
|
||||
resource "test_instance" "1_invalid_resource_name" {
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "bar" {
|
||||
lifecycle {
|
||||
ignore_changes = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "baz" {
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
"image",
|
||||
"tags.name",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "boop" {
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
"image",
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
lifecycle {
|
||||
create_before_destroy = true
|
||||
prevent_destroy = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "bar" {
|
||||
lifecycle {
|
||||
ignore_changes = all
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "baz" {
|
||||
lifecycle {
|
||||
ignore_changes = [
|
||||
image,
|
||||
tags.name,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "boop" {
|
||||
lifecycle {
|
||||
ignore_changes = [image]
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
resource "test_instance" "from_list" {
|
||||
list_of_obj = [
|
||||
{},
|
||||
{},
|
||||
]
|
||||
}
|
||||
|
||||
resource "test_instance" "already_blocks" {
|
||||
list_of_obj {}
|
||||
list_of_obj {}
|
||||
}
|
||||
|
||||
resource "test_instance" "empty" {
|
||||
list_of_obj = []
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
resource "test_instance" "from_list" {
|
||||
list_of_obj {
|
||||
}
|
||||
list_of_obj {
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "already_blocks" {
|
||||
list_of_obj {
|
||||
}
|
||||
list_of_obj {
|
||||
}
|
||||
}
|
||||
|
||||
resource "test_instance" "empty" {
|
||||
list_of_obj = []
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
type = "z1.weedy"
|
||||
image = "image-abcd"
|
||||
tags {
|
||||
name = "boop"
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
resource "test_instance" "foo" {
|
||||
type = "z1.weedy"
|
||||
image = "image-abcd"
|
||||
tags = {
|
||||
name = "boop"
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// Test note: the configupgrade tool will ignore this possibly-relative module
|
||||
// source because it does not find a local directory "foo". The example where
|
||||
// the configupgrade tool makes a recommendation about relative module sources
|
||||
// is is in relative-module-source.
|
||||
module "foo" {
|
||||
source = "foo"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// Test note: the configupgrade tool will ignore this possibly-relative module
|
||||
// source because it does not find a local directory "foo". The example where
|
||||
// the configupgrade tool makes a recommendation about relative module sources
|
||||
// is is in relative-module-source.
|
||||
module "foo" {
|
||||
source = "foo"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
locals {
|
||||
in_map = {
|
||||
foo = "${var.baz}"
|
||||
}
|
||||
in_list = [
|
||||
"${var.baz}",
|
||||
"${var.bar}",
|
||||
]
|
||||
in_list_oneline = ["${var.baz}", "${var.bar}"]
|
||||
in_map_of_list = {
|
||||
foo = ["${var.baz}"]
|
||||
}
|
||||
in_list_of_map = [
|
||||
{
|
||||
foo = "${var.baz}"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
locals {
|
||||
in_map = {
|
||||
foo = var.baz
|
||||
}
|
||||
in_list = [
|
||||
var.baz,
|
||||
var.bar,
|
||||
]
|
||||
in_list_oneline = [var.baz, var.bar]
|
||||
in_map_of_list = {
|
||||
foo = [var.baz]
|
||||
}
|
||||
in_list_of_map = [
|
||||
{
|
||||
foo = var.baz
|
||||
},
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
locals {
|
||||
# Arithmetic
|
||||
add = "${1 + 2}"
|
||||
sub = "${1 - 2}"
|
||||
mul = "${1 * 2}"
|
||||
mod = "${4 % 2}"
|
||||
and = "${true && true}"
|
||||
or = "${true || true}"
|
||||
equal = "${1 == 2}"
|
||||
not_equal = "${1 != 2}"
|
||||
less_than = "${1 < 2}"
|
||||
greater_than = "${1 > 2}"
|
||||
less_than_eq = "${1 <= 2}"
|
||||
greater_than_eq = "${1 >= 2}"
|
||||
neg = "${- local.add}"
|
||||
|
||||
# Call
|
||||
call_no_args = "${foo()}"
|
||||
call_one_arg = "${foo(1)}"
|
||||
call_two_args = "${foo(1, 2)}"
|
||||
|
||||
# Conditional
|
||||
cond = "${true ? 1 : 2}"
|
||||
|
||||
# Index
|
||||
index_str = "${foo["a"]}"
|
||||
index_num = "${foo[1]}"
|
||||
|
||||
# Variable Access
|
||||
var_access_single = "${foo}"
|
||||
var_access_dot = "${foo.bar}"
|
||||
var_access_splat = "${foo.bar.*.baz}"
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
locals {
|
||||
# Arithmetic
|
||||
add = 1 + 2
|
||||
sub = 1 - 2
|
||||
mul = 1 * 2
|
||||
mod = 4 % 2
|
||||
and = true && true
|
||||
or = true || true
|
||||
equal = 1 == 2
|
||||
not_equal = 1 != 2
|
||||
less_than = 1 < 2
|
||||
greater_than = 1 > 2
|
||||
less_than_eq = 1 <= 2
|
||||
greater_than_eq = 1 >= 2
|
||||
neg = -local.add
|
||||
|
||||
# Call
|
||||
call_no_args = foo()
|
||||
call_one_arg = foo(1)
|
||||
call_two_args = foo(1, 2)
|
||||
|
||||
# Conditional
|
||||
cond = true ? 1 : 2
|
||||
|
||||
# Index
|
||||
index_str = foo["a"]
|
||||
index_num = foo[1]
|
||||
|
||||
# Variable Access
|
||||
var_access_single = foo
|
||||
var_access_dot = foo.bar
|
||||
var_access_splat = foo.bar.*.baz
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
module "foo" {
|
||||
source = "./foo"
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
output "foo" {
|
||||
value = "jeepers ${var.bar}"
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
terraform {
|
||||
required_version = ">= 0.7.0, <0.13.0"
|
||||
|
||||
backend "local" {
|
||||
path = "foo.tfstate"
|
||||
}
|
||||
}
|
||||
|
||||
provider "test" {
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
resource "test_instance" "example" {
|
||||
tags = {
|
||||
# Thingy thing
|
||||
name = "foo bar baz" # this is a terrible name
|
||||
}
|
||||
|
||||
connection {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
provisioner "test" {
|
||||
connection {
|
||||
host = "127.0.0.2"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
|
||||
/* This multi-line comment
|
||||
should survive */
|
||||
|
||||
# This comment should survive
|
||||
variable "foo" {
|
||||
default = 1 // This comment should also survive
|
||||
}
|
||||
|
||||
// These adjacent comments should remain adjacent
|
||||
// to one another.
|
||||
|
||||
variable "bar" {
|
||||
/* This comment should survive too */
|
||||
description = "bar the baz"
|
||||
}
|
||||
|
||||
// This comment that isn't attached to anything should survive.
|
|
@ -1,3 +0,0 @@
|
|||
module "foo" {
|
||||
source = "./foo"
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
output "foo" {
|
||||
value = "jeepers ${var.bar}"
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
|
||||
terraform {
|
||||
required_version = ">= 0.7.0, <0.13.0"
|
||||
|
||||
backend "local" {
|
||||
path = "foo.tfstate"
|
||||
}
|
||||
}
|
||||
|
||||
provider "test" {
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
resource "test_instance" "example" {
|
||||
tags = {
|
||||
# Thingy thing
|
||||
name = "foo bar baz" # this is a terrible name
|
||||
}
|
||||
|
||||
connection {
|
||||
host = "127.0.0.1"
|
||||
}
|
||||
provisioner "test" {
|
||||
connection {
|
||||
host = "127.0.0.2"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
/* This multi-line comment
|
||||
should survive */
|
||||
|
||||
# This comment should survive
|
||||
variable "foo" {
|
||||
default = 1 // This comment should also survive
|
||||
}
|
||||
|
||||
// These adjacent comments should remain adjacent
|
||||
// to one another.
|
||||
|
||||
variable "bar" {
|
||||
/* This comment should survive too */
|
||||
description = "bar the baz"
|
||||
}
|
||||
|
||||
// This comment that isn't attached to anything should survive.
|
|
@ -1,3 +0,0 @@
|
|||
terraform {
|
||||
required_version = ">= 0.12"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
locals {
|
||||
decimal_int = 1
|
||||
decimal_float = 1.5
|
||||
decimal_float_tricky = 0.1
|
||||
hex_int = 0xff
|
||||
octal_int = 0777
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
locals {
|
||||
decimal_int = 1
|
||||
decimal_float = 1.5
|
||||
decimal_float_tricky = 0.1
|
||||
hex_int = 255
|
||||
octal_int = 511
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue