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
|
package command
|
||||||
|
|
||||||
import (
|
import "fmt"
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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 {
|
type ZeroTwelveUpgradeCommand struct {
|
||||||
Meta
|
Meta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ZeroTwelveUpgradeCommand) Run(args []string) int {
|
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(`
|
c.Ui.Output(fmt.Sprintf(`
|
||||||
This command will rewrite the configuration files in the given directory so
|
The 0.12upgrade command is deprecated. You must run this command with Terraform
|
||||||
that they use the new syntax features from Terraform v0.12, and will identify
|
v0.12 to upgrade your configuration syntax before upgrading to the current
|
||||||
any constructs that may need to be adjusted for correct operation with
|
version.`))
|
||||||
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.")
|
|
||||||
return 0
|
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"
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||||
"github.com/hashicorp/terraform/configs"
|
"github.com/hashicorp/terraform/configs"
|
||||||
"github.com/hashicorp/terraform/configs/configschema"
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/configs/configupgrade"
|
|
||||||
"github.com/hashicorp/terraform/internal/earlyconfig"
|
"github.com/hashicorp/terraform/internal/earlyconfig"
|
||||||
"github.com/hashicorp/terraform/internal/initwd"
|
"github.com/hashicorp/terraform/internal/initwd"
|
||||||
"github.com/hashicorp/terraform/plugin/discovery"
|
"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
|
// Before we do anything else, we'll try loading configuration with both
|
||||||
// our "normal" and "early" configuration codepaths. If early succeeds
|
// our "normal" and "early" configuration codepaths. If early succeeds
|
||||||
// while normal fails, that strongly suggests that the configuration is
|
// 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
|
// using syntax that worked in 0.11 but no longer in v0.12.
|
||||||
// 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.
|
|
||||||
rootMod, confDiags := c.loadSingleModule(path)
|
rootMod, confDiags := c.loadSingleModule(path)
|
||||||
rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(path)
|
rootModEarly, earlyConfDiags := c.loadSingleModuleEarly(path)
|
||||||
configUpgradeProbablyNeeded := false
|
|
||||||
if confDiags.HasErrors() {
|
if confDiags.HasErrors() {
|
||||||
if earlyConfDiags.HasErrors() {
|
if earlyConfDiags.HasErrors() {
|
||||||
// If both parsers produced errors then we'll assume the config
|
// 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)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
// If _only_ the main loader produced errors then that suggests the
|
||||||
// If _only_ the main loader produced errors then that suggests an
|
// configuration is written in 0.11-style syntax. We will return an
|
||||||
// upgrade may help. To give us more certainty here, we'll use the
|
// error suggesting the user upgrade their config manually or with
|
||||||
// same heuristic that "terraform 0.12upgrade" uses to guess if a
|
// Terraform v0.12
|
||||||
// configuration has already been upgraded, to reduce the risk that
|
c.Ui.Error(strings.TrimSpace(errInitConfigErrorMaybeLegacySyntax))
|
||||||
// we'll produce a misleading message if the problem is just a regular
|
c.showDiagnostics(earlyConfDiags)
|
||||||
// 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)
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
configUpgradeProbablyNeeded = true
|
|
||||||
}
|
|
||||||
if earlyConfDiags.HasErrors() {
|
|
||||||
// If _only_ the early loader encountered errors then that's unusual
|
// If _only_ the early loader encountered errors then that's unusual
|
||||||
// (it should generally be a superset of the normal loader) but we'll
|
// (it should generally be a superset of the normal loader) but we'll
|
||||||
// return those errors anyway since otherwise we'll probably get
|
// return those errors anyway since otherwise we'll probably get
|
||||||
// some weird behavior downstream. Errors from the early loader are
|
// some weird behavior downstream. Errors from the early loader are
|
||||||
// generally not as high-quality since it has less context to work with.
|
// generally not as high-quality since it has less context to work with.
|
||||||
|
if earlyConfDiags.HasErrors() {
|
||||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
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)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
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
|
// With all of the modules (hopefully) installed, we can now try to load the
|
||||||
// the whole configuration tree.
|
// whole configuration tree.
|
||||||
//
|
//
|
||||||
// Just as above, we'll try loading both with the early and normal config
|
// 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
|
// loaders here. Subsequent work will only use the early config, but loading
|
||||||
// loading both gives us an opportunity to prefer the better error messages
|
// both gives us an opportunity to prefer the better error messages from the
|
||||||
// from the normal loader if both fail.
|
// normal loader if both fail.
|
||||||
_, confDiags = c.loadConfig(path)
|
|
||||||
earlyConfig, earlyConfDiags := c.loadConfigEarly(path)
|
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() {
|
if earlyConfDiags.HasErrors() {
|
||||||
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||||
diags = diags.Append(earlyConfDiags)
|
diags = diags.Append(earlyConfDiags)
|
||||||
|
@ -259,32 +235,28 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
_, confDiags = c.loadConfig(path)
|
||||||
// Before we go further, we'll check to make sure none of the modules
|
if confDiags.HasErrors() {
|
||||||
// in the configuration declare that they don't support this Terraform
|
c.Ui.Error(strings.TrimSpace(errInitConfigError))
|
||||||
// version, so we can produce a version-related error message rather
|
diags = diags.Append(confDiags)
|
||||||
// than potentially-confusing downstream errors.
|
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)
|
versionDiags := initwd.CheckCoreVersionRequirements(earlyConfig)
|
||||||
diags = diags.Append(versionDiags)
|
diags = diags.Append(versionDiags)
|
||||||
if versionDiags.HasErrors() {
|
if versionDiags.HasErrors() {
|
||||||
c.showDiagnostics(diags)
|
c.showDiagnostics(diags)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
var back backend.Backend
|
var back backend.Backend
|
||||||
if flagBackend {
|
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)
|
be, backendOutput, backendDiags := c.initBackend(rootMod, flagConfigExtra)
|
||||||
diags = diags.Append(backendDiags)
|
diags = diags.Append(backendDiags)
|
||||||
if backendDiags.HasErrors() {
|
if backendDiags.HasErrors() {
|
||||||
|
@ -295,7 +267,6 @@ func (c *InitCommand) Run(args []string) int {
|
||||||
header = true
|
header = true
|
||||||
}
|
}
|
||||||
back = be
|
back = be
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// load the previously-stored backend config
|
// load the previously-stored backend config
|
||||||
be, backendDiags := c.Meta.backendFromState()
|
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
|
// by errors then we'll output them here so that the success message is
|
||||||
// still the final thing shown.
|
// still the final thing shown.
|
||||||
c.showDiagnostics(diags)
|
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)))
|
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
|
||||||
if !c.RunningInAutomation {
|
if !c.RunningInAutomation {
|
||||||
// If we're not running in an automation wrapper, give the user
|
// 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.
|
// shell usage.
|
||||||
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI)))
|
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccessCLI)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
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.
|
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 = `
|
const errInitCopyNotEmpty = `
|
||||||
The working directory already contains files. The -from-module option requires
|
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.
|
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.
|
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 = `
|
const outputInitProvidersUnconstrained = `
|
||||||
The following providers do not have any version constraints in configuration,
|
The following providers do not have any version constraints in configuration,
|
||||||
so the latest version was installed.
|
so the latest version was installed.
|
||||||
|
|
|
@ -1424,86 +1424,14 @@ func TestInit_pluginWithInternal(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInit_012UpgradeNeeded(t *testing.T) {
|
// The module in this test uses terraform 0.11-style syntax. We expect that the
|
||||||
td := tempDir(t)
|
// earlyconfig will succeed but the main loader fail, and return an error that
|
||||||
copy.CopyDir(testFixturePath("init-012upgrade"), td)
|
// indicates that syntax upgrades may be required.
|
||||||
defer os.RemoveAll(td)
|
func TestInit_syntaxErrorUpgradeHint(t *testing.T) {
|
||||||
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) {
|
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
|
||||||
|
// This module
|
||||||
copy.CopyDir(testFixturePath("init-sniff-version-error"), td)
|
copy.CopyDir(testFixturePath("init-sniff-version-error"), td)
|
||||||
defer os.RemoveAll(td)
|
defer os.RemoveAll(td)
|
||||||
defer testChdir(t, td)()
|
defer testChdir(t, td)()
|
||||||
|
@ -1517,16 +1445,13 @@ func TestInit_syntaxErrorVersionSniff(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{}
|
args := []string{}
|
||||||
if code := c.Run(args); code != 0 {
|
if code := c.Run(args); code != 1 {
|
||||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check output.
|
// Check output.
|
||||||
// Currently, this lands in the "upgrade may be needed" codepath, because
|
output := ui.ErrorWriter.String()
|
||||||
// the intentional syntax error in our test fixture is something that
|
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) {
|
||||||
// "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) {
|
|
||||||
t.Fatalf("wrong output\ngot:\n%s\n\nwant: message containing %q", 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) {
|
"0.12upgrade": func() (cli.Command, error) {
|
||||||
return &command.ZeroTwelveUpgradeCommand{
|
return &command.ZeroThirteenUpgradeCommand{
|
||||||
Meta: meta,
|
Meta: meta,
|
||||||
}, nil
|
}, 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