Merge pull request #15666 from hashicorp/f-init-from-module
command: terraform init -from-module=...
This commit is contained in:
commit
efb007542b
|
@ -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