github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/command/apply.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/go-getter"
    11  	"github.com/hashicorp/go-multierror"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  // ApplyCommand is a Command implementation that applies a Terraform
    16  // configuration and actually builds or changes infrastructure.
    17  type ApplyCommand struct {
    18  	Meta
    19  
    20  	// If true, then this apply command will become the "destroy"
    21  	// command. It is just like apply but only processes a destroy.
    22  	Destroy bool
    23  
    24  	// When this channel is closed, the apply will be cancelled.
    25  	ShutdownCh <-chan struct{}
    26  }
    27  
    28  func (c *ApplyCommand) Run(args []string) int {
    29  	var destroyForce, refresh bool
    30  	args = c.Meta.process(args, true)
    31  
    32  	cmdName := "apply"
    33  	if c.Destroy {
    34  		cmdName = "destroy"
    35  	}
    36  
    37  	cmdFlags := c.Meta.flagSet(cmdName)
    38  	if c.Destroy {
    39  		cmdFlags.BoolVar(&destroyForce, "force", false, "force")
    40  	}
    41  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    42  	cmdFlags.IntVar(
    43  		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
    44  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    45  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    46  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    47  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    48  	if err := cmdFlags.Parse(args); err != nil {
    49  		return 1
    50  	}
    51  
    52  	pwd, err := os.Getwd()
    53  	if err != nil {
    54  		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    55  		return 1
    56  	}
    57  
    58  	var configPath string
    59  	maybeInit := true
    60  	args = cmdFlags.Args()
    61  	if len(args) > 1 {
    62  		c.Ui.Error("The apply command expects at most one argument.")
    63  		cmdFlags.Usage()
    64  		return 1
    65  	} else if len(args) == 1 {
    66  		configPath = args[0]
    67  	} else {
    68  		configPath = pwd
    69  		maybeInit = false
    70  	}
    71  
    72  	// Prepare the extra hooks to count resources
    73  	countHook := new(CountHook)
    74  	stateHook := new(StateHook)
    75  	c.Meta.extraHooks = []terraform.Hook{countHook, stateHook}
    76  
    77  	if !c.Destroy && maybeInit {
    78  		// Do a detect to determine if we need to do an init + apply.
    79  		if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
    80  			c.Ui.Error(fmt.Sprintf(
    81  				"Invalid path: %s", err))
    82  			return 1
    83  		} else if !strings.HasPrefix(detected, "file") {
    84  			// If this isn't a file URL then we're doing an init +
    85  			// apply.
    86  			var init InitCommand
    87  			init.Meta = c.Meta
    88  			if code := init.Run([]string{detected}); code != 0 {
    89  				return code
    90  			}
    91  
    92  			// Change the config path to be the cwd
    93  			configPath = pwd
    94  		}
    95  	}
    96  
    97  	// Build the context based on the arguments given
    98  	ctx, planned, err := c.Context(contextOpts{
    99  		Destroy:     c.Destroy,
   100  		Path:        configPath,
   101  		StatePath:   c.Meta.statePath,
   102  		Parallelism: c.Meta.parallelism,
   103  	})
   104  	if err != nil {
   105  		c.Ui.Error(err.Error())
   106  		return 1
   107  	}
   108  	if c.Destroy && planned {
   109  		c.Ui.Error(fmt.Sprintf(
   110  			"Destroy can't be called with a plan file."))
   111  		return 1
   112  	}
   113  	if !destroyForce && c.Destroy {
   114  		// Default destroy message
   115  		desc := "Terraform will delete all your managed infrastructure.\n" +
   116  			"There is no undo. Only 'yes' will be accepted to confirm."
   117  
   118  		// If targets are specified, list those to user
   119  		if c.Meta.targets != nil {
   120  			var descBuffer bytes.Buffer
   121  			descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
   122  			for _, target := range c.Meta.targets {
   123  				descBuffer.WriteString("\t")
   124  				descBuffer.WriteString(target)
   125  				descBuffer.WriteString("\n")
   126  			}
   127  			descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
   128  			desc = descBuffer.String()
   129  		}
   130  
   131  		v, err := c.UIInput().Input(&terraform.InputOpts{
   132  			Id:          "destroy",
   133  			Query:       "Do you really want to destroy?",
   134  			Description: desc,
   135  		})
   136  		if err != nil {
   137  			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
   138  			return 1
   139  		}
   140  		if v != "yes" {
   141  			c.Ui.Output("Destroy cancelled.")
   142  			return 1
   143  		}
   144  	}
   145  	if !planned {
   146  		if err := ctx.Input(c.InputMode()); err != nil {
   147  			c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
   148  			return 1
   149  		}
   150  	}
   151  	if !validateContext(ctx, c.Ui) {
   152  		return 1
   153  	}
   154  
   155  	// Plan if we haven't already
   156  	if !planned {
   157  		if refresh {
   158  			if _, err := ctx.Refresh(); err != nil {
   159  				c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
   160  				return 1
   161  			}
   162  		}
   163  
   164  		if _, err := ctx.Plan(); err != nil {
   165  			c.Ui.Error(fmt.Sprintf(
   166  				"Error creating plan: %s", err))
   167  			return 1
   168  		}
   169  	}
   170  
   171  	// Setup the state hook for continuous state updates
   172  	{
   173  		state, err := c.State()
   174  		if err != nil {
   175  			c.Ui.Error(fmt.Sprintf(
   176  				"Error reading state: %s", err))
   177  			return 1
   178  		}
   179  
   180  		stateHook.State = state
   181  	}
   182  
   183  	// Start the apply in a goroutine so that we can be interrupted.
   184  	var state *terraform.State
   185  	var applyErr error
   186  	doneCh := make(chan struct{})
   187  	go func() {
   188  		defer close(doneCh)
   189  		state, applyErr = ctx.Apply()
   190  	}()
   191  
   192  	// Wait for the apply to finish or for us to be interrupted so
   193  	// we can handle it properly.
   194  	err = nil
   195  	select {
   196  	case <-c.ShutdownCh:
   197  		c.Ui.Output("Interrupt received. Gracefully shutting down...")
   198  
   199  		// Stop execution
   200  		go ctx.Stop()
   201  
   202  		// Still get the result, since there is still one
   203  		select {
   204  		case <-c.ShutdownCh:
   205  			c.Ui.Error(
   206  				"Two interrupts received. Exiting immediately. Note that data\n" +
   207  					"loss may have occurred.")
   208  			return 1
   209  		case <-doneCh:
   210  		}
   211  	case <-doneCh:
   212  	}
   213  
   214  	// Persist the state
   215  	if state != nil {
   216  		if err := c.Meta.PersistState(state); err != nil {
   217  			c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err))
   218  			return 1
   219  		}
   220  	}
   221  
   222  	if applyErr != nil {
   223  		c.Ui.Error(fmt.Sprintf(
   224  			"Error applying plan:\n\n"+
   225  				"%s\n\n"+
   226  				"Terraform does not automatically rollback in the face of errors.\n"+
   227  				"Instead, your Terraform state file has been partially updated with\n"+
   228  				"any resources that successfully completed. Please address the error\n"+
   229  				"above and apply again to incrementally change your infrastructure.",
   230  			multierror.Flatten(applyErr)))
   231  		return 1
   232  	}
   233  
   234  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   235  		"[reset][bold][green]\n"+
   236  			"Apply complete! Resources: %d added, %d changed, %d destroyed.",
   237  		countHook.Added,
   238  		countHook.Changed,
   239  		countHook.Removed)))
   240  
   241  	if countHook.Added > 0 || countHook.Changed > 0 {
   242  		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   243  			"[reset]\n"+
   244  				"The state of your infrastructure has been saved to the path\n"+
   245  				"below. This state is required to modify and destroy your\n"+
   246  				"infrastructure, so keep it safe. To inspect the complete state\n"+
   247  				"use the `terraform show` command.\n\n"+
   248  				"State path: %s",
   249  			c.Meta.StateOutPath())))
   250  	}
   251  
   252  	if !c.Destroy {
   253  		if outputs := outputsAsString(state); outputs != "" {
   254  			c.Ui.Output(c.Colorize().Color(outputs))
   255  		}
   256  	}
   257  
   258  	return 0
   259  }
   260  
   261  func (c *ApplyCommand) Help() string {
   262  	if c.Destroy {
   263  		return c.helpDestroy()
   264  	}
   265  
   266  	return c.helpApply()
   267  }
   268  
   269  func (c *ApplyCommand) Synopsis() string {
   270  	if c.Destroy {
   271  		return "Destroy Terraform-managed infrastructure"
   272  	}
   273  
   274  	return "Builds or changes infrastructure"
   275  }
   276  
   277  func (c *ApplyCommand) helpApply() string {
   278  	helpText := `
   279  Usage: terraform apply [options] [DIR-OR-PLAN]
   280  
   281    Builds or changes infrastructure according to Terraform configuration
   282    files in DIR.
   283  
   284    By default, apply scans the current directory for the configuration
   285    and applies the changes appropriately. However, a path to another
   286    configuration or an execution plan can be provided. Execution plans can be
   287    used to only execute a pre-determined set of actions.
   288  
   289    DIR can also be a SOURCE as given to the "init" command. In this case,
   290    apply behaves as though "init" was called followed by "apply". This only
   291    works for sources that aren't files, and only if the current working
   292    directory is empty of Terraform files. This is a shortcut for getting
   293    started.
   294  
   295  Options:
   296  
   297    -backup=path           Path to backup the existing state file before
   298                           modifying. Defaults to the "-state-out" path with
   299                           ".backup" extension. Set to "-" to disable backup.
   300  
   301    -input=true            Ask for input for variables if not directly set.
   302  
   303    -no-color              If specified, output won't contain any color.
   304  
   305    -parallelism=n         Limit the number of concurrent operations.
   306                           Defaults to 10.
   307  
   308    -refresh=true          Update state prior to checking for differences. This
   309                           has no effect if a plan file is given to apply.
   310  
   311    -state=path            Path to read and save state (unless state-out
   312                           is specified). Defaults to "terraform.tfstate".
   313  
   314    -state-out=path        Path to write state to that is different than
   315                           "-state". This can be used to preserve the old
   316                           state.
   317  
   318    -target=resource       Resource to target. Operation will be limited to this
   319                           resource and its dependencies. This flag can be used
   320                           multiple times.
   321  
   322    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   323                           flag can be set multiple times.
   324  
   325    -var-file=foo          Set variables in the Terraform configuration from
   326                           a file. If "terraform.tfvars" is present, it will be
   327                           automatically loaded if this flag is not specified.
   328  
   329  
   330  `
   331  	return strings.TrimSpace(helpText)
   332  }
   333  
   334  func (c *ApplyCommand) helpDestroy() string {
   335  	helpText := `
   336  Usage: terraform destroy [options] [DIR]
   337  
   338    Destroy Terraform-managed infrastructure.
   339  
   340  Options:
   341  
   342    -backup=path           Path to backup the existing state file before
   343                           modifying. Defaults to the "-state-out" path with
   344                           ".backup" extension. Set to "-" to disable backup.
   345  
   346    -force                 Don't ask for input for destroy confirmation.
   347  
   348    -no-color              If specified, output won't contain any color.
   349  
   350    -parallelism=n         Limit the number of concurrent operations.
   351                           Defaults to 10.
   352  
   353    -refresh=true          Update state prior to checking for differences. This
   354                           has no effect if a plan file is given to apply.
   355  
   356    -state=path            Path to read and save state (unless state-out
   357                           is specified). Defaults to "terraform.tfstate".
   358  
   359    -state-out=path        Path to write state to that is different than
   360                           "-state". This can be used to preserve the old
   361                           state.
   362  
   363    -target=resource       Resource to target. Operation will be limited to this
   364                           resource and its dependencies. This flag can be used
   365                           multiple times.
   366  
   367    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   368                           flag can be set multiple times.
   369  
   370    -var-file=foo          Set variables in the Terraform configuration from
   371                           a file. If "terraform.tfvars" is present, it will be
   372                           automatically loaded if this flag is not specified.
   373  
   374  
   375  `
   376  	return strings.TrimSpace(helpText)
   377  }
   378  
   379  func outputsAsString(state *terraform.State) string {
   380  	if state == nil {
   381  		return ""
   382  	}
   383  
   384  	outputs := state.RootModule().Outputs
   385  	outputBuf := new(bytes.Buffer)
   386  	if len(outputs) > 0 {
   387  		outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
   388  
   389  		// Output the outputs in alphabetical order
   390  		keyLen := 0
   391  		keys := make([]string, 0, len(outputs))
   392  		for key, _ := range outputs {
   393  			keys = append(keys, key)
   394  			if len(key) > keyLen {
   395  				keyLen = len(key)
   396  			}
   397  		}
   398  		sort.Strings(keys)
   399  
   400  		for _, k := range keys {
   401  			v := outputs[k]
   402  
   403  			outputBuf.WriteString(fmt.Sprintf(
   404  				"  %s%s = %s\n",
   405  				k,
   406  				strings.Repeat(" ", keyLen-len(k)),
   407  				v))
   408  		}
   409  	}
   410  
   411  	return strings.TrimSpace(outputBuf.String())
   412  }