github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/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  		hasBackend := conf.Terraform != nil && conf.Terraform.Backend != nil
   129  		if flagBackend && hasBackend {
   130  			header = true
   131  
   132  			c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   133  				"[reset][bold]" +
   134  					"Initializing the backend...")))
   135  
   136  			opts := &BackendOpts{
   137  				ConfigPath: path,
   138  				ConfigFile: flagConfigFile,
   139  				Init:       true,
   140  			}
   141  			if _, err := c.Backend(opts); err != nil {
   142  				c.Ui.Error(err.Error())
   143  				return 1
   144  			}
   145  		}
   146  	}
   147  
   148  	// If we outputted information, then we need to output a newline
   149  	// so that our success message is nicely spaced out from prior text.
   150  	if header {
   151  		c.Ui.Output("")
   152  	}
   153  
   154  	c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
   155  
   156  	return 0
   157  }
   158  
   159  func (c *InitCommand) copySource(dst, src, pwd string) error {
   160  	// Verify the directory is empty
   161  	if empty, err := config.IsEmptyDir(dst); err != nil {
   162  		return fmt.Errorf("Error checking on destination path: %s", err)
   163  	} else if !empty {
   164  		return fmt.Errorf(strings.TrimSpace(errInitCopyNotEmpty))
   165  	}
   166  
   167  	// Detect
   168  	source, err := getter.Detect(src, pwd, getter.Detectors)
   169  	if err != nil {
   170  		return fmt.Errorf("Error with module source: %s", err)
   171  	}
   172  
   173  	// Get it!
   174  	return module.GetCopy(dst, source)
   175  }
   176  
   177  func (c *InitCommand) Help() string {
   178  	helpText := `
   179  Usage: terraform init [options] [SOURCE] [PATH]
   180  
   181    Initialize a new or existing Terraform environment by creating
   182    initial files, loading any remote state, downloading modules, etc.
   183  
   184    This is the first command that should be run for any new or existing
   185    Terraform configuration per machine. This sets up all the local data
   186    necessary to run Terraform that is typically not comitted to version
   187    control.
   188  
   189    This command is always safe to run multiple times. Though subsequent runs
   190    may give errors, this command will never blow away your environment or state.
   191    Even so, if you have important information, please back it up prior to
   192    running this command just in case.
   193  
   194    If no arguments are given, the configuration in this working directory
   195    is initialized.
   196  
   197    If one or two arguments are given, the first is a SOURCE of a module to
   198    download to the second argument PATH. After downloading the module to PATH,
   199    the configuration will be initialized as if this command were called pointing
   200    only to that PATH. PATH must be empty of any Terraform files. Any
   201    conflicting non-Terraform files will be overwritten. The module download
   202    is a copy. If you're downloading a module from Git, it will not preserve
   203    Git history.
   204  
   205  Options:
   206  
   207    -backend=true        Configure the backend for this environment.
   208  
   209    -backend-config=path A path to load additional configuration for the backend.
   210                         This is merged with what is in the configuration file.
   211  
   212    -get=true            Download any modules for this configuration.
   213  
   214    -input=true          Ask for input if necessary. If false, will error if
   215                         input was required.
   216  
   217    -no-color            If specified, output won't contain any color.
   218  
   219  `
   220  	return strings.TrimSpace(helpText)
   221  }
   222  
   223  func (c *InitCommand) Synopsis() string {
   224  	return "Initialize a new or existing Terraform configuration"
   225  }
   226  
   227  const errInitCopyNotEmpty = `
   228  The destination path contains Terraform configuration files. The init command
   229  with a SOURCE parameter can only be used on a directory without existing
   230  Terraform files.
   231  
   232  Please resolve this issue and try again.
   233  `
   234  
   235  const outputInitEmpty = `
   236  [reset][bold]Terraform initialized in an empty directory![reset]
   237  
   238  The directory has no Terraform configuration files. You may begin working
   239  with Terraform immediately by creating Terraform configuration files.
   240  `
   241  
   242  const outputInitSuccess = `
   243  [reset][bold][green]Terraform has been successfully initialized![reset][green]
   244  
   245  You may now begin working with Terraform. Try running "terraform plan" to see
   246  any changes that are required for your infrastructure. All Terraform commands
   247  should now work.
   248  
   249  If you ever set or change modules or backend configuration for Terraform,
   250  rerun this command to reinitialize your environment. If you forget, other
   251  commands will detect it and remind you to do so if necessary.
   252  `