github.com/ibm-cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/command/init.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/go-getter"
    10  	"github.com/hashicorp/terraform/config"
    11  	"github.com/hashicorp/terraform/config/module"
    12  	"github.com/hashicorp/terraform/helper/variables"
    13  )
    14  
    15  // InitCommand is a Command implementation that takes a Terraform
    16  // module and clones it to the working directory.
    17  type InitCommand struct {
    18  	Meta
    19  }
    20  
    21  func (c *InitCommand) Run(args []string) int {
    22  	var flagBackend, flagGet bool
    23  	var flagConfigExtra map[string]interface{}
    24  
    25  	args = c.Meta.process(args, false)
    26  	cmdFlags := c.flagSet("init")
    27  	cmdFlags.BoolVar(&flagBackend, "backend", true, "")
    28  	cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
    29  	cmdFlags.BoolVar(&flagGet, "get", true, "")
    30  	cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
    31  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    32  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    33  	cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
    34  
    35  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    36  	if err := cmdFlags.Parse(args); err != nil {
    37  		return 1
    38  	}
    39  
    40  	// Validate the arg count
    41  	args = cmdFlags.Args()
    42  	if len(args) > 2 {
    43  		c.Ui.Error("The init command expects at most two arguments.\n")
    44  		cmdFlags.Usage()
    45  		return 1
    46  	}
    47  
    48  	// Get our pwd. We don't always need it but always getting it is easier
    49  	// than the logic to determine if it is or isn't needed.
    50  	pwd, err := os.Getwd()
    51  	if err != nil {
    52  		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    53  		return 1
    54  	}
    55  
    56  	// Get the path and source module to copy
    57  	var path string
    58  	var source string
    59  	switch len(args) {
    60  	case 0:
    61  		path = pwd
    62  	case 1:
    63  		path = pwd
    64  		source = args[0]
    65  	case 2:
    66  		source = args[0]
    67  		path = args[1]
    68  	default:
    69  		panic("assertion failed on arg count")
    70  	}
    71  
    72  	// Set the state out path to be the path requested for the module
    73  	// to be copied. This ensures any remote states gets setup in the
    74  	// proper directory.
    75  	c.Meta.dataDir = filepath.Join(path, DefaultDataDir)
    76  
    77  	// This will track whether we outputted anything so that we know whether
    78  	// to output a newline before the success message
    79  	var header bool
    80  
    81  	// If we have a source, copy it
    82  	if source != "" {
    83  		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
    84  			"[reset][bold]"+
    85  				"Initializing configuration from: %q...", source)))
    86  		if err := c.copySource(path, source, pwd); err != nil {
    87  			c.Ui.Error(fmt.Sprintf(
    88  				"Error copying source: %s", err))
    89  			return 1
    90  		}
    91  
    92  		header = true
    93  	}
    94  
    95  	// If our directory is empty, then we're done. We can't get or setup
    96  	// the backend with an empty directory.
    97  	if empty, err := config.IsEmptyDir(path); err != nil {
    98  		c.Ui.Error(fmt.Sprintf(
    99  			"Error checking configuration: %s", err))
   100  		return 1
   101  	} else if empty {
   102  		c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
   103  		return 0
   104  	}
   105  
   106  	// If we're performing a get or loading the backend, then we perform
   107  	// some extra tasks.
   108  	if flagGet || flagBackend {
   109  		// Load the configuration in this directory so that we can know
   110  		// if we have anything to get or any backend to configure. We do
   111  		// this to improve the UX. Practically, we could call the functions
   112  		// below without checking this to the same effect.
   113  		conf, err := config.LoadDir(path)
   114  		if err != nil {
   115  			c.Ui.Error(fmt.Sprintf(
   116  				"Error loading configuration: %s", err))
   117  			return 1
   118  		}
   119  
   120  		// If we requested downloading modules and have modules in the config
   121  		if flagGet && len(conf.Modules) > 0 {
   122  			header = true
   123  
   124  			c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   125  				"[reset][bold]" +
   126  					"Downloading modules (if any)...")))
   127  			if err := getModules(&c.Meta, path, module.GetModeGet); err != nil {
   128  				c.Ui.Error(fmt.Sprintf(
   129  					"Error downloading modules: %s", err))
   130  				return 1
   131  			}
   132  		}
   133  
   134  		// If we're requesting backend configuration and configure it
   135  		if flagBackend {
   136  			header = true
   137  
   138  			// Only output that we're initializing a backend if we have
   139  			// something in the config. We can be UNSETTING a backend as well
   140  			// in which case we choose not to show this.
   141  			if conf.Terraform != nil && conf.Terraform.Backend != nil {
   142  				c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   143  					"[reset][bold]" +
   144  						"Initializing the backend...")))
   145  			}
   146  
   147  			opts := &BackendOpts{
   148  				ConfigPath:  path,
   149  				ConfigExtra: flagConfigExtra,
   150  				Init:        true,
   151  			}
   152  			if _, err := c.Backend(opts); err != nil {
   153  				c.Ui.Error(err.Error())
   154  				return 1
   155  			}
   156  		}
   157  	}
   158  
   159  	// If we outputted information, then we need to output a newline
   160  	// so that our success message is nicely spaced out from prior text.
   161  	if header {
   162  		c.Ui.Output("")
   163  	}
   164  
   165  	c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
   166  
   167  	return 0
   168  }
   169  
   170  func (c *InitCommand) copySource(dst, src, pwd string) error {
   171  	// Verify the directory is empty
   172  	if empty, err := config.IsEmptyDir(dst); err != nil {
   173  		return fmt.Errorf("Error checking on destination path: %s", err)
   174  	} else if !empty {
   175  		return fmt.Errorf(strings.TrimSpace(errInitCopyNotEmpty))
   176  	}
   177  
   178  	// Detect
   179  	source, err := getter.Detect(src, pwd, getter.Detectors)
   180  	if err != nil {
   181  		return fmt.Errorf("Error with module source: %s", err)
   182  	}
   183  
   184  	// Get it!
   185  	return module.GetCopy(dst, source)
   186  }
   187  
   188  func (c *InitCommand) Help() string {
   189  	helpText := `
   190  Usage: terraform init [options] [SOURCE] [PATH]
   191  
   192    Initialize a new or existing Terraform environment by creating
   193    initial files, loading any remote state, downloading modules, etc.
   194  
   195    This is the first command that should be run for any new or existing
   196    Terraform configuration per machine. This sets up all the local data
   197    necessary to run Terraform that is typically not committed to version
   198    control.
   199  
   200    This command is always safe to run multiple times. Though subsequent runs
   201    may give errors, this command will never blow away your environment or state.
   202    Even so, if you have important information, please back it up prior to
   203    running this command just in case.
   204  
   205    If no arguments are given, the configuration in this working directory
   206    is initialized.
   207  
   208    If one or two arguments are given, the first is a SOURCE of a module to
   209    download to the second argument PATH. After downloading the module to PATH,
   210    the configuration will be initialized as if this command were called pointing
   211    only to that PATH. PATH must be empty of any Terraform files. Any
   212    conflicting non-Terraform files will be overwritten. The module download
   213    is a copy. If you're downloading a module from Git, it will not preserve
   214    Git history.
   215  
   216  Options:
   217  
   218    -backend=true        Configure the backend for this environment.
   219  
   220    -backend-config=path This can be either a path to an HCL file with key/value
   221                         assignments (same format as terraform.tfvars) or a
   222                         'key=value' format. This is merged with what is in the
   223                         configuration file. This can be specified multiple
   224                         times. The backend type must be in the configuration
   225                         itself.
   226  
   227    -force-copy          Suppress prompts about copying state data. This is
   228                         equivalent to providing a "yes" to all confirmation
   229                         prompts.
   230  
   231    -get=true            Download any modules for this configuration.
   232  
   233    -input=true          Ask for input if necessary. If false, will error if
   234                         input was required.
   235  
   236    -lock=true           Lock the state file when locking is supported.
   237  
   238    -lock-timeout=0s     Duration to retry a state lock.
   239  
   240    -no-color            If specified, output won't contain any color.
   241  
   242    -reconfigure          Reconfigure the backend, ignoring any saved configuration.
   243  `
   244  	return strings.TrimSpace(helpText)
   245  }
   246  
   247  func (c *InitCommand) Synopsis() string {
   248  	return "Initialize a new or existing Terraform configuration"
   249  }
   250  
   251  const errInitCopyNotEmpty = `
   252  The destination path contains Terraform configuration files. The init command
   253  with a SOURCE parameter can only be used on a directory without existing
   254  Terraform files.
   255  
   256  Please resolve this issue and try again.
   257  `
   258  
   259  const outputInitEmpty = `
   260  [reset][bold]Terraform initialized in an empty directory![reset]
   261  
   262  The directory has no Terraform configuration files. You may begin working
   263  with Terraform immediately by creating Terraform configuration files.
   264  `
   265  
   266  const outputInitSuccess = `
   267  [reset][bold][green]Terraform has been successfully initialized![reset][green]
   268  
   269  You may now begin working with Terraform. Try running "terraform plan" to see
   270  any changes that are required for your infrastructure. All Terraform commands
   271  should now work.
   272  
   273  If you ever set or change modules or backend configuration for Terraform,
   274  rerun this command to reinitialize your environment. If you forget, other
   275  commands will detect it and remind you to do so if necessary.
   276  `