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