2014-09-27 01:03:39 +02:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
2016-01-20 02:13:19 +01:00
|
|
|
"path/filepath"
|
2014-09-27 01:03:39 +02:00
|
|
|
"strings"
|
|
|
|
|
2015-10-15 22:48:58 +02:00
|
|
|
"github.com/hashicorp/go-getter"
|
2014-09-27 01:03:39 +02:00
|
|
|
"github.com/hashicorp/terraform/config"
|
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2017-03-17 07:27:05 +01:00
|
|
|
"github.com/hashicorp/terraform/helper/variables"
|
2014-09-27 01:03:39 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// InitCommand is a Command implementation that takes a Terraform
|
|
|
|
// module and clones it to the working directory.
|
|
|
|
type InitCommand struct {
|
|
|
|
Meta
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *InitCommand) Run(args []string) int {
|
2017-01-19 05:50:45 +01:00
|
|
|
var flagBackend, flagGet bool
|
2017-03-17 07:27:05 +01:00
|
|
|
var flagConfigExtra map[string]interface{}
|
2014-09-27 01:03:39 +02:00
|
|
|
args = c.Meta.process(args, false)
|
2017-01-19 05:50:45 +01:00
|
|
|
cmdFlags := c.flagSet("init")
|
|
|
|
cmdFlags.BoolVar(&flagBackend, "backend", true, "")
|
2017-03-17 07:27:05 +01:00
|
|
|
cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
|
2017-01-19 05:50:45 +01:00
|
|
|
cmdFlags.BoolVar(&flagGet, "get", true, "")
|
2014-09-27 01:03:39 +02:00
|
|
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
|
|
if err := cmdFlags.Parse(args); err != nil {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// Validate the arg count
|
2014-09-27 01:03:39 +02:00
|
|
|
args = cmdFlags.Args()
|
|
|
|
if len(args) > 2 {
|
|
|
|
c.Ui.Error("The init command expects at most two arguments.\n")
|
|
|
|
cmdFlags.Usage()
|
|
|
|
return 1
|
2017-01-19 05:50:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get our pwd. We don't always need it but always getting it is easier
|
|
|
|
// than the logic to determine if it is or isn't needed.
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
|
2014-09-27 01:03:39 +02:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// Get the path and source module to copy
|
|
|
|
var path string
|
|
|
|
var source string
|
|
|
|
switch len(args) {
|
|
|
|
case 0:
|
|
|
|
path = pwd
|
|
|
|
case 1:
|
|
|
|
path = pwd
|
|
|
|
source = args[0]
|
|
|
|
case 2:
|
|
|
|
source = args[0]
|
2014-09-27 01:03:39 +02:00
|
|
|
path = args[1]
|
2017-01-19 05:50:45 +01:00
|
|
|
default:
|
|
|
|
panic("assertion failed on arg count")
|
2014-09-27 01:03:39 +02:00
|
|
|
}
|
|
|
|
|
2016-01-20 02:13:19 +01:00
|
|
|
// Set the state out path to be the path requested for the module
|
|
|
|
// to be copied. This ensures any remote states gets setup in the
|
|
|
|
// proper directory.
|
2016-07-21 00:55:05 +02:00
|
|
|
c.Meta.dataDir = filepath.Join(path, DefaultDataDir)
|
2016-01-20 02:13:19 +01:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// This will track whether we outputted anything so that we know whether
|
|
|
|
// to output a newline before the success message
|
|
|
|
var header bool
|
|
|
|
|
|
|
|
// If we have a source, copy it
|
|
|
|
if source != "" {
|
|
|
|
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
|
|
|
"[reset][bold]"+
|
|
|
|
"Initializing configuration from: %q...", source)))
|
|
|
|
if err := c.copySource(path, source, pwd); err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"Error copying source: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
2014-09-27 01:03:39 +02:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
header = true
|
2014-09-27 01:30:49 +02:00
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// If our directory is empty, then we're done. We can't get or setup
|
|
|
|
// the backend with an empty directory.
|
2014-09-27 01:03:39 +02:00
|
|
|
if empty, err := config.IsEmptyDir(path); err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
2017-01-19 05:50:45 +01:00
|
|
|
"Error checking configuration: %s", err))
|
2014-09-27 01:03:39 +02:00
|
|
|
return 1
|
2017-01-19 05:50:45 +01:00
|
|
|
} else if empty {
|
|
|
|
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
|
|
|
|
return 0
|
2014-09-27 01:03:39 +02:00
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// If we're performing a get or loading the backend, then we perform
|
|
|
|
// some extra tasks.
|
|
|
|
if flagGet || flagBackend {
|
|
|
|
// Load the configuration in this directory so that we can know
|
|
|
|
// if we have anything to get or any backend to configure. We do
|
|
|
|
// this to improve the UX. Practically, we could call the functions
|
|
|
|
// below without checking this to the same effect.
|
|
|
|
conf, err := config.LoadDir(path)
|
2014-10-01 01:05:40 +02:00
|
|
|
if err != nil {
|
2017-01-19 05:50:45 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"Error loading configuration: %s", err))
|
2014-10-10 02:16:17 +02:00
|
|
|
return 1
|
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
|
|
|
|
// If we requested downloading modules and have modules in the config
|
|
|
|
if flagGet && len(conf.Modules) > 0 {
|
|
|
|
header = true
|
|
|
|
|
|
|
|
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
|
|
|
"[reset][bold]" +
|
|
|
|
"Downloading modules (if any)...")))
|
|
|
|
if err := getModules(&c.Meta, path, module.GetModeGet); err != nil {
|
2015-02-22 19:49:31 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf(
|
2017-01-19 05:50:45 +01:00
|
|
|
"Error downloading modules: %s", err))
|
2015-02-22 19:49:31 +01:00
|
|
|
return 1
|
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// If we're requesting backend configuration and configure it
|
2017-02-16 00:44:53 +01:00
|
|
|
if flagBackend {
|
2017-01-19 05:50:45 +01:00
|
|
|
header = true
|
|
|
|
|
2017-02-16 00:44:53 +01:00
|
|
|
// Only output that we're initializing a backend if we have
|
|
|
|
// something in the config. We can be UNSETTING a backend as well
|
|
|
|
// in which case we choose not to show this.
|
|
|
|
if conf.Terraform != nil && conf.Terraform.Backend != nil {
|
|
|
|
c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
|
|
|
|
"[reset][bold]" +
|
|
|
|
"Initializing the backend...")))
|
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
|
|
|
|
opts := &BackendOpts{
|
2017-03-17 07:27:05 +01:00
|
|
|
ConfigPath: path,
|
|
|
|
ConfigExtra: flagConfigExtra,
|
|
|
|
Init: true,
|
2017-01-19 05:50:45 +01:00
|
|
|
}
|
|
|
|
if _, err := c.Backend(opts); err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
2015-02-22 19:49:31 +01:00
|
|
|
return 1
|
|
|
|
}
|
2014-10-01 01:05:40 +02:00
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
}
|
2014-10-10 02:16:17 +02:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// If we outputted information, then we need to output a newline
|
|
|
|
// so that our success message is nicely spaced out from prior text.
|
|
|
|
if header {
|
|
|
|
c.Ui.Output("")
|
2014-10-01 01:05:40 +02:00
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
|
|
|
|
c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
|
|
|
|
|
2014-09-27 01:03:39 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
func (c *InitCommand) copySource(dst, src, pwd string) error {
|
|
|
|
// Verify the directory is empty
|
|
|
|
if empty, err := config.IsEmptyDir(dst); err != nil {
|
|
|
|
return fmt.Errorf("Error checking on destination path: %s", err)
|
|
|
|
} else if !empty {
|
|
|
|
return fmt.Errorf(strings.TrimSpace(errInitCopyNotEmpty))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Detect
|
|
|
|
source, err := getter.Detect(src, pwd, getter.Detectors)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error with module source: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get it!
|
|
|
|
return module.GetCopy(dst, source)
|
|
|
|
}
|
|
|
|
|
2014-09-27 01:03:39 +02:00
|
|
|
func (c *InitCommand) Help() string {
|
|
|
|
helpText := `
|
2017-01-19 05:50:45 +01:00
|
|
|
Usage: terraform init [options] [SOURCE] [PATH]
|
|
|
|
|
|
|
|
Initialize a new or existing Terraform environment by creating
|
|
|
|
initial files, loading any remote state, downloading modules, etc.
|
|
|
|
|
|
|
|
This is the first command that should be run for any new or existing
|
|
|
|
Terraform configuration per machine. This sets up all the local data
|
|
|
|
necessary to run Terraform that is typically not comitted to version
|
|
|
|
control.
|
|
|
|
|
|
|
|
This command is always safe to run multiple times. Though subsequent runs
|
|
|
|
may give errors, this command will never blow away your environment or state.
|
|
|
|
Even so, if you have important information, please back it up prior to
|
|
|
|
running this command just in case.
|
2014-09-27 01:03:39 +02:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
If no arguments are given, the configuration in this working directory
|
|
|
|
is initialized.
|
2014-09-27 01:03:39 +02:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
If one or two arguments are given, the first is a SOURCE of a module to
|
|
|
|
download to the second argument PATH. After downloading the module to PATH,
|
|
|
|
the configuration will be initialized as if this command were called pointing
|
|
|
|
only to that PATH. PATH must be empty of any Terraform files. Any
|
|
|
|
conflicting non-Terraform files will be overwritten. The module download
|
|
|
|
is a copy. If you're downloading a module from Git, it will not preserve
|
|
|
|
Git history.
|
2014-09-27 01:03:39 +02:00
|
|
|
|
2014-10-01 01:05:40 +02:00
|
|
|
Options:
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
-backend=true Configure the backend for this environment.
|
2014-12-05 04:06:47 +01:00
|
|
|
|
2017-03-17 07:27:05 +01:00
|
|
|
-backend-config=path This can be either a path to an HCL file with key/value
|
|
|
|
assignments (same format as terraform.tfvars) or a
|
|
|
|
'key=value' format. This is merged with what is in the
|
|
|
|
configuration file. This can be specified multiple
|
|
|
|
times. The backend type must be in the configuration
|
|
|
|
itself.
|
2014-10-01 01:05:40 +02:00
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
-get=true Download any modules for this configuration.
|
|
|
|
|
|
|
|
-input=true Ask for input if necessary. If false, will error if
|
|
|
|
input was required.
|
|
|
|
|
|
|
|
-no-color If specified, output won't contain any color.
|
2015-06-22 14:14:01 +02:00
|
|
|
|
2014-09-27 01:03:39 +02:00
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *InitCommand) Synopsis() string {
|
2017-01-19 05:50:45 +01:00
|
|
|
return "Initialize a new or existing Terraform configuration"
|
2014-09-27 01:03:39 +02:00
|
|
|
}
|
2017-01-19 05:50:45 +01:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
Please resolve this issue and try again.
|
|
|
|
`
|
|
|
|
|
|
|
|
const outputInitEmpty = `
|
|
|
|
[reset][bold]Terraform initialized in an empty directory![reset]
|
|
|
|
|
|
|
|
The directory has no Terraform configuration files. You may begin working
|
|
|
|
with Terraform immediately by creating Terraform configuration files.
|
|
|
|
`
|
|
|
|
|
|
|
|
const outputInitSuccess = `
|
|
|
|
[reset][bold][green]Terraform has been successfully initialized![reset][green]
|
|
|
|
|
|
|
|
You may now begin working with Terraform. Try running "terraform plan" to see
|
|
|
|
any changes that are required for your infrastructure. All Terraform commands
|
|
|
|
should now work.
|
|
|
|
|
|
|
|
If you ever set or change modules or backend configuration for Terraform,
|
|
|
|
rerun this command to reinitialize your environment. If you forget, other
|
|
|
|
commands will detect it and remind you to do so if necessary.
|
|
|
|
`
|