github.com/kevholditch/terraform@v0.9.7-0.20170613192930-9706042ddd51/command/init.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  
    11  	multierror "github.com/hashicorp/go-multierror"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/config"
    14  	"github.com/hashicorp/terraform/config/module"
    15  	"github.com/hashicorp/terraform/helper/variables"
    16  	"github.com/hashicorp/terraform/plugin"
    17  	"github.com/hashicorp/terraform/plugin/discovery"
    18  	"github.com/hashicorp/terraform/terraform"
    19  )
    20  
    21  // InitCommand is a Command implementation that takes a Terraform
    22  // module and clones it to the working directory.
    23  type InitCommand struct {
    24  	Meta
    25  
    26  	// providerInstaller is used to download and install providers that
    27  	// aren't found locally. This uses a discovery.ProviderInstaller instance
    28  	// by default, but it can be overridden here as a way to mock fetching
    29  	// providers for tests.
    30  	providerInstaller discovery.Installer
    31  }
    32  
    33  func (c *InitCommand) Run(args []string) int {
    34  	var flagBackend, flagGet, flagGetPlugins, flagUpgrade bool
    35  	var flagConfigExtra map[string]interface{}
    36  
    37  	args = c.Meta.process(args, false)
    38  	cmdFlags := c.flagSet("init")
    39  	cmdFlags.BoolVar(&flagBackend, "backend", true, "")
    40  	cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
    41  	cmdFlags.BoolVar(&flagGet, "get", true, "")
    42  	cmdFlags.BoolVar(&flagGetPlugins, "get-plugins", true, "")
    43  	cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
    44  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    45  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    46  	cmdFlags.BoolVar(&c.reconfigure, "reconfigure", false, "reconfigure")
    47  	cmdFlags.BoolVar(&flagUpgrade, "upgrade", false, "")
    48  
    49  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    50  	if err := cmdFlags.Parse(args); err != nil {
    51  		return 1
    52  	}
    53  
    54  	// set getProvider if we don't have a test version already
    55  	if c.providerInstaller == nil {
    56  		c.providerInstaller = &discovery.ProviderInstaller{
    57  			Dir: c.pluginDir(),
    58  
    59  			PluginProtocolVersion: plugin.Handshake.ProtocolVersion,
    60  		}
    61  	}
    62  
    63  	// Validate the arg count
    64  	args = cmdFlags.Args()
    65  	if len(args) > 1 {
    66  		c.Ui.Error("The init command expects at most one argument.\n")
    67  		cmdFlags.Usage()
    68  		return 1
    69  	}
    70  
    71  	// Get our pwd. We don't always need it but always getting it is easier
    72  	// than the logic to determine if it is or isn't needed.
    73  	pwd, err := os.Getwd()
    74  	if err != nil {
    75  		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    76  		return 1
    77  	}
    78  
    79  	// Get the path and source module to copy
    80  	path := pwd
    81  	if len(args) == 1 {
    82  		path = args[0]
    83  	}
    84  	// Set the state out path to be the path requested for the module
    85  	// to be copied. This ensures any remote states gets setup in the
    86  	// proper directory.
    87  	c.Meta.dataDir = filepath.Join(path, DefaultDataDir)
    88  
    89  	// This will track whether we outputted anything so that we know whether
    90  	// to output a newline before the success message
    91  	var header bool
    92  
    93  	// If our directory is empty, then we're done. We can't get or setup
    94  	// the backend with an empty directory.
    95  	if empty, err := config.IsEmptyDir(path); err != nil {
    96  		c.Ui.Error(fmt.Sprintf(
    97  			"Error checking configuration: %s", err))
    98  		return 1
    99  	} else if empty {
   100  		c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitEmpty)))
   101  		return 0
   102  	}
   103  
   104  	var back backend.Backend
   105  
   106  	// If we're performing a get or loading the backend, then we perform
   107  	// some extra tasks.
   108  	if flagGet || flagBackend {
   109  		conf, err := c.Config(path)
   110  		if err != nil {
   111  			c.Ui.Error(fmt.Sprintf(
   112  				"Error loading configuration: %s", err))
   113  			return 1
   114  		}
   115  
   116  		// If we requested downloading modules and have modules in the config
   117  		if flagGet && len(conf.Modules) > 0 {
   118  			header = true
   119  
   120  			getMode := module.GetModeGet
   121  			if flagUpgrade {
   122  				getMode = module.GetModeUpdate
   123  				c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   124  					"[reset][bold]Upgrading modules...")))
   125  			} else {
   126  				c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   127  					"[reset][bold]Downloading modules...")))
   128  			}
   129  
   130  			if err := getModules(&c.Meta, path, getMode); err != nil {
   131  				c.Ui.Error(fmt.Sprintf(
   132  					"Error downloading modules: %s", err))
   133  				return 1
   134  			}
   135  
   136  		}
   137  
   138  		// If we're requesting backend configuration or looking for required
   139  		// plugins, load the backend
   140  		if flagBackend || flagGetPlugins {
   141  			header = true
   142  
   143  			// Only output that we're initializing a backend if we have
   144  			// something in the config. We can be UNSETTING a backend as well
   145  			// in which case we choose not to show this.
   146  			if conf.Terraform != nil && conf.Terraform.Backend != nil {
   147  				c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   148  					"[reset][bold]" +
   149  						"Initializing the backend...")))
   150  			}
   151  
   152  			opts := &BackendOpts{
   153  				Config:      conf,
   154  				ConfigExtra: flagConfigExtra,
   155  				Init:        true,
   156  			}
   157  			if back, err = c.Backend(opts); err != nil {
   158  				c.Ui.Error(err.Error())
   159  				return 1
   160  			}
   161  		}
   162  	}
   163  
   164  	// Now that we have loaded all modules, check the module tree for missing providers
   165  	if flagGetPlugins {
   166  		sMgr, err := back.State(c.Workspace())
   167  		if err != nil {
   168  			c.Ui.Error(fmt.Sprintf(
   169  				"Error loading state: %s", err))
   170  			return 1
   171  		}
   172  
   173  		if err := sMgr.RefreshState(); err != nil {
   174  			c.Ui.Error(fmt.Sprintf(
   175  				"Error refreshing state: %s", err))
   176  			return 1
   177  		}
   178  
   179  		c.Ui.Output(c.Colorize().Color(
   180  			"[reset][bold]Initializing provider plugins...",
   181  		))
   182  
   183  		err = c.getProviders(path, sMgr.State(), flagUpgrade)
   184  		if err != nil {
   185  			// this function provides its own output
   186  			log.Printf("[ERROR] %s", err)
   187  			return 1
   188  		}
   189  	}
   190  
   191  	// If we outputted information, then we need to output a newline
   192  	// so that our success message is nicely spaced out from prior text.
   193  	if header {
   194  		c.Ui.Output("")
   195  	}
   196  
   197  	c.Ui.Output(c.Colorize().Color(strings.TrimSpace(outputInitSuccess)))
   198  
   199  	return 0
   200  }
   201  
   202  // Load the complete module tree, and fetch any missing providers.
   203  // This method outputs its own Ui.
   204  func (c *InitCommand) getProviders(path string, state *terraform.State, upgrade bool) error {
   205  	mod, err := c.Module(path)
   206  	if err != nil {
   207  		c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err))
   208  		return err
   209  	}
   210  
   211  	if err := mod.Validate(); err != nil {
   212  		c.Ui.Error(fmt.Sprintf("Error getting plugins: %s", err))
   213  		return err
   214  	}
   215  
   216  	var available discovery.PluginMetaSet
   217  	if upgrade {
   218  		// If we're in upgrade mode, we ignore any auto-installed plugins
   219  		// in "available", causing us to reinstall and possibly upgrade them.
   220  		available = c.providerPluginManuallyInstalledSet()
   221  	} else {
   222  		available = c.providerPluginSet()
   223  	}
   224  	requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements()
   225  	missing := c.missingPlugins(available, requirements)
   226  
   227  	var errs error
   228  	for provider, reqd := range missing {
   229  		c.Ui.Output(fmt.Sprintf("- downloading plugin for provider %q...", provider))
   230  		_, err := c.providerInstaller.Get(provider, reqd.Versions)
   231  
   232  		if err != nil {
   233  			c.Ui.Error(fmt.Sprintf(errProviderNotFound, err, provider, reqd.Versions))
   234  			errs = multierror.Append(errs, err)
   235  		}
   236  	}
   237  
   238  	if errs != nil {
   239  		return errs
   240  	}
   241  
   242  	// With all the providers downloaded, we'll generate our lock file
   243  	// that ensures the provider binaries remain unchanged until we init
   244  	// again. If anything changes, other commands that use providers will
   245  	// fail with an error instructing the user to re-run this command.
   246  	available = c.providerPluginSet() // re-discover to see newly-installed plugins
   247  	chosen := choosePlugins(available, requirements)
   248  	digests := map[string][]byte{}
   249  	for name, meta := range chosen {
   250  		digest, err := meta.SHA256()
   251  		if err != nil {
   252  			c.Ui.Error(fmt.Sprintf("failed to read provider plugin %s: %s", meta.Path, err))
   253  			return err
   254  		}
   255  		digests[name] = digest
   256  	}
   257  	err = c.providerPluginsLock().Write(digests)
   258  	if err != nil {
   259  		c.Ui.Error(fmt.Sprintf("failed to save provider manifest: %s", err))
   260  		return err
   261  	}
   262  
   263  	if upgrade {
   264  		// Purge any auto-installed plugins that aren't being used.
   265  		purged, err := c.providerInstaller.PurgeUnused(chosen)
   266  		if err != nil {
   267  			// Failure to purge old plugins is not a fatal error
   268  			c.Ui.Warn(fmt.Sprintf("failed to purge unused plugins: %s", err))
   269  		}
   270  		if purged != nil {
   271  			for meta := range purged {
   272  				log.Printf("[DEBUG] Purged unused %s plugin %s", meta.Name, meta.Path)
   273  			}
   274  		}
   275  	}
   276  
   277  	// If any providers have "floating" versions (completely unconstrained)
   278  	// we'll suggest the user constrain with a pessimistic constraint to
   279  	// avoid implicitly adopting a later major release.
   280  	constraintSuggestions := make(map[string]discovery.ConstraintStr)
   281  	for name, meta := range chosen {
   282  		req := requirements[name]
   283  		if req == nil {
   284  			// should never happen, but we don't want to crash here, so we'll
   285  			// be cautious.
   286  			continue
   287  		}
   288  
   289  		if req.Versions.Unconstrained() {
   290  			// meta.Version.MustParse is safe here because our "chosen" metas
   291  			// were already filtered for validity of versions.
   292  			constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr()
   293  		}
   294  	}
   295  	if len(constraintSuggestions) != 0 {
   296  		names := make([]string, 0, len(constraintSuggestions))
   297  		for name := range constraintSuggestions {
   298  			names = append(names, name)
   299  		}
   300  		sort.Strings(names)
   301  
   302  		c.Ui.Output(outputInitProvidersUnconstrained)
   303  		for _, name := range names {
   304  			c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name]))
   305  		}
   306  	}
   307  
   308  	return nil
   309  }
   310  
   311  func (c *InitCommand) Help() string {
   312  	helpText := `
   313  Usage: terraform init [options] [DIR]
   314  
   315    Initialize a new or existing Terraform working directory by creating
   316    initial files, loading any remote state, downloading modules, etc.
   317  
   318    This is the first command that should be run for any new or existing
   319    Terraform configuration per machine. This sets up all the local data
   320    necessary to run Terraform that is typically not committed to version
   321    control.
   322  
   323    This command is always safe to run multiple times. Though subsequent runs
   324    may give errors, this command will never delete your configuration or
   325    state. Even so, if you have important information, please back it up prior
   326    to running this command, just in case.
   327  
   328    If no arguments are given, the configuration in this working directory
   329    is initialized.
   330  
   331  Options:
   332  
   333    -backend=true        Configure the backend for this configuration.
   334  
   335    -backend-config=path This can be either a path to an HCL file with key/value
   336                         assignments (same format as terraform.tfvars) or a
   337                         'key=value' format. This is merged with what is in the
   338                         configuration file. This can be specified multiple
   339                         times. The backend type must be in the configuration
   340                         itself.
   341  
   342    -force-copy          Suppress prompts about copying state data. This is
   343                         equivalent to providing a "yes" to all confirmation
   344                         prompts.
   345  
   346    -get=true            Download any modules for this configuration.
   347  
   348    -get-plugins=true    Download any missing plugins for this configuration.
   349  
   350    -input=true          Ask for input if necessary. If false, will error if
   351                         input was required.
   352  
   353    -lock=true           Lock the state file when locking is supported.
   354  
   355    -lock-timeout=0s     Duration to retry a state lock.
   356  
   357    -no-color            If specified, output won't contain any color.
   358  
   359    -reconfigure         Reconfigure the backend, ignoring any saved configuration.
   360  
   361    -upgrade=false       If installing modules (-get) or plugins (-get-plugins),
   362                         ignore previously-downloaded objects and install the
   363                         latest version allowed within configured constraints.
   364  `
   365  	return strings.TrimSpace(helpText)
   366  }
   367  
   368  func (c *InitCommand) Synopsis() string {
   369  	return "Initialize a new or existing Terraform configuration"
   370  }
   371  
   372  const errInitCopyNotEmpty = `
   373  The destination path contains Terraform configuration files. The init command
   374  with a SOURCE parameter can only be used on a directory without existing
   375  Terraform files.
   376  
   377  Please resolve this issue and try again.
   378  `
   379  
   380  const outputInitEmpty = `
   381  [reset][bold]Terraform initialized in an empty directory![reset]
   382  
   383  The directory has no Terraform configuration files. You may begin working
   384  with Terraform immediately by creating Terraform configuration files.
   385  `
   386  
   387  const outputInitSuccess = `
   388  [reset][bold][green]Terraform has been successfully initialized![reset][green]
   389  
   390  You may now begin working with Terraform. Try running "terraform plan" to see
   391  any changes that are required for your infrastructure. All Terraform commands
   392  should now work.
   393  
   394  If you ever set or change modules or backend configuration for Terraform,
   395  rerun this command to reinitialize your working directory. If you forget, other
   396  commands will detect it and remind you to do so if necessary.
   397  `
   398  
   399  const outputInitProvidersUnconstrained = `
   400  The following providers do not have any version constraints in configuration,
   401  so the latest version was installed.
   402  
   403  To prevent automatic upgrades to new major versions that may contain breaking
   404  changes, it is recommended to add version = "..." constraints to the
   405  corresponding provider blocks in configuration, with the constraint strings
   406  suggested below.
   407  `
   408  
   409  const errProviderNotFound = `
   410  [reset][red]%[1]s
   411  
   412  [reset][bold][red]Error: Satisfying %[2]q, provider not found
   413  
   414  [reset][red]A version of the %[2]q provider that satisfies all version
   415  constraints could not be found. The requested version
   416  constraints are shown below.
   417  
   418  %[2]s = %[3]q[reset]
   419  `