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