Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

command: terraform init -from-module=... #15666

Merged
merged 1 commit into from
Jul 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 50 additions & 6 deletions command/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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]
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 = `
Expand Down
93 changes: 93 additions & 0 deletions command/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions website/docs/commands/init.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down