mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
command: terraform init -from-module=...
This restores the earlier behavior of the first positional argument to terraform init in 0.9, but as a command line option. The positional argument was removed to improve consistency with other commands that take a working directory as their first positional argument. It was originally intended that this functionality would return in a later release along with some other general improvements to Terraform's module handling, but we're introducing here an interim solution that uses the existing module source concept, to allow for easier porting of workflows that previously depended on the automatic copy behavior. In a future release this feature may change again as the module improvements design firms up, but we expect it to be broadly compatible with this temporary state.
This commit is contained in:
parent
870617d22d
commit
8a7a0a7459
@ -7,6 +7,8 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-getter"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
@ -33,6 +35,7 @@ type InitCommand struct {
|
||||
}
|
||||
|
||||
func (c *InitCommand) Run(args []string) int {
|
||||
var flagFromModule string
|
||||
var flagBackend, flagGet, flagUpgrade bool
|
||||
var flagConfigExtra map[string]interface{}
|
||||
var flagPluginPath FlagStringSlice
|
||||
@ -45,6 +48,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
cmdFlags := c.flagSet("init")
|
||||
cmdFlags.BoolVar(&flagBackend, "backend", true, "")
|
||||
cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
|
||||
cmdFlags.StringVar(&flagFromModule, "from-module", "", "copy the source of the given module into the directory before init")
|
||||
cmdFlags.BoolVar(&flagGet, "get", true, "")
|
||||
cmdFlags.BoolVar(&c.getPlugins, "get-plugins", true, "")
|
||||
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
|
||||
@ -95,7 +99,7 @@ func (c *InitCommand) Run(args []string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Get the path and source module to copy
|
||||
// If an argument is provided then it overrides our working directory.
|
||||
path := pwd
|
||||
if len(args) == 1 {
|
||||
path = args[0]
|
||||
@ -105,6 +109,30 @@ func (c *InitCommand) Run(args []string) int {
|
||||
// to output a newline before the success message
|
||||
var header bool
|
||||
|
||||
if flagFromModule != "" {
|
||||
src := flagFromModule
|
||||
|
||||
empty, err := config.IsEmptyDir(path)
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error validating destination directory: %s", err))
|
||||
return 1
|
||||
}
|
||||
if !empty {
|
||||
c.Ui.Error(strings.TrimSpace(errInitCopyNotEmpty))
|
||||
return 1
|
||||
}
|
||||
|
||||
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
||||
"[reset][bold]Copying configuration[reset] from %q...", src,
|
||||
)))
|
||||
header = true
|
||||
|
||||
if err := c.copyConfigFromModule(path, src, pwd); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error copying source module: %s", err))
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// If our directory is empty, then we're done. We can't get or setup
|
||||
// the backend with an empty directory.
|
||||
if empty, err := config.IsEmptyDir(path); err != nil {
|
||||
@ -232,6 +260,19 @@ func (c *InitCommand) Run(args []string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (c *InitCommand) copyConfigFromModule(dst, src, pwd string) error {
|
||||
// errors from this function will be prefixed with "Error copying source module: "
|
||||
// when returned to the user.
|
||||
var err error
|
||||
|
||||
src, err = getter.Detect(src, pwd, getter.Detectors)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid module source: %s", err)
|
||||
}
|
||||
|
||||
return module.GetCopy(dst, src)
|
||||
}
|
||||
|
||||
// Load the complete module tree, and fetch any missing providers.
|
||||
// This method outputs its own Ui.
|
||||
func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error {
|
||||
@ -430,6 +471,9 @@ Options:
|
||||
equivalent to providing a "yes" to all confirmation
|
||||
prompts.
|
||||
|
||||
-from-module=SOURCE Copy the contents of the given module into the target
|
||||
directory before initialization.
|
||||
|
||||
-get=true Download any modules for this configuration.
|
||||
|
||||
-get-plugins=true Download any missing plugins for this configuration.
|
||||
@ -462,15 +506,15 @@ Options:
|
||||
}
|
||||
|
||||
func (c *InitCommand) Synopsis() string {
|
||||
return "Initialize a new or existing Terraform configuration"
|
||||
return "Initialize a Terraform working directory"
|
||||
}
|
||||
|
||||
const errInitCopyNotEmpty = `
|
||||
The destination path contains Terraform configuration files. The init command
|
||||
with a SOURCE parameter can only be used on a directory without existing
|
||||
Terraform files.
|
||||
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.
|
||||
|
||||
Please resolve this issue and try again.
|
||||
To initialize the configuration already in this working directory, omit the
|
||||
-from-module option.
|
||||
`
|
||||
|
||||
const outputInitEmpty = `
|
||||
|
@ -55,6 +55,99 @@ func TestInit_multipleArgs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_fromModule_explicitDest(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-from-module=" + testFixturePath("init"),
|
||||
dir,
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "hello.tf")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_fromModule_cwdDest(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
os.MkdirAll(td, os.ModePerm)
|
||||
defer os.RemoveAll(td)
|
||||
defer testChdir(t, td)()
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-from-module=" + testFixturePath("init"),
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(td, "hello.tf")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/hashicorp/terraform/issues/518
|
||||
func TestInit_fromModule_dstInSrc(t *testing.T) {
|
||||
dir := tempDir(t)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
// Change to the temporary directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Chdir(cwd)
|
||||
|
||||
if _, err := os.Create("issue518.tf"); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
c := &InitCommand{
|
||||
Meta: Meta{
|
||||
testingOverrides: metaOverridesForProvider(testProvider()),
|
||||
Ui: ui,
|
||||
},
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-from-module=.",
|
||||
"foo",
|
||||
}
|
||||
if code := c.Run(args); code != 0 {
|
||||
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(dir, "foo", "issue518.tf")); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInit_get(t *testing.T) {
|
||||
// Create a temporary working directory that is empty
|
||||
td := tempDir(t)
|
||||
|
@ -50,6 +50,31 @@ The following options apply to all of (or several of) the initialization steps:
|
||||
* `-upgrade` Opt to upgrade modules and plugins as part of their respective
|
||||
installation steps. See the seconds below for more details.
|
||||
|
||||
## Copy a Source Module
|
||||
|
||||
By default, `terraform init` assumes that the working directory already
|
||||
contains a configuration and will attempt to initialize that configuration.
|
||||
|
||||
Optionally, init can be run against an empty directory with the
|
||||
`-with-module=MODULE-SOURCE` option, in which case the given module will be
|
||||
copied into the target directory before any other initialization steps are
|
||||
run.
|
||||
|
||||
This special mode of operation supports two use-cases:
|
||||
|
||||
* Given a version control source, it can serve as a shorthand for checking out
|
||||
a configuration from version control and then initializing the work directory
|
||||
for it.
|
||||
|
||||
* If the source refers to an _example_ configuration, it can be copied into
|
||||
a local directory to be used as a basis for a new configuration.
|
||||
|
||||
For routine use it's recommended to check out configuration from version
|
||||
control separately, using the version control system's own commands. This way
|
||||
it's possible to pass extra flags to the version control system when necessary,
|
||||
and to perform other preparation steps (such as configuration generation, or
|
||||
activating credentials) before running `terraform init`.
|
||||
|
||||
## Backend Initialization
|
||||
|
||||
During init, the root configuration directory is consulted for
|
||||
|
Loading…
Reference in New Issue
Block a user