github.com/anuaimi/terraform@v0.6.4-0.20150904235404-2bf9aec61da8/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 !c.Destroy {
   234  		if outputs := outputsAsString(state); outputs != "" {
   235  			c.Ui.Output(c.Colorize().Color(outputs))
   236  		}
   237  	}
   238  
   239  	return 0
   240  }
   241  
   242  func (c *ApplyCommand) Help() string {
   243  	if c.Destroy {
   244  		return c.helpDestroy()
   245  	}
   246  
   247  	return c.helpApply()
   248  }
   249  
   250  func (c *ApplyCommand) Synopsis() string {
   251  	if c.Destroy {
   252  		return "Destroy Terraform-managed infrastructure"
   253  	}
   254  
   255  	return "Builds or changes infrastructure"
   256  }
   257  
   258  func (c *ApplyCommand) helpApply() string {
   259  	helpText := `
   260  Usage: terraform apply [options] [DIR]
   261  
   262    Builds or changes infrastructure according to Terraform configuration
   263    files in DIR.
   264  
   265    DIR can also be a SOURCE as given to the "init" command. In this case,
   266    apply behaves as though "init" was called followed by "apply". This only
   267    works for sources that aren't files, and only if the current working
   268    directory is empty of Terraform files. This is a shortcut for getting
   269    started.
   270  
   271  Options:
   272  
   273    -backup=path           Path to backup the existing state file before
   274                           modifying. Defaults to the "-state-out" path with
   275                           ".backup" extension. Set to "-" to disable backup.
   276  
   277    -input=true            Ask for input for variables if not directly set.
   278  
   279    -no-color              If specified, output won't contain any color.
   280  
   281    -refresh=true          Update state prior to checking for differences. This
   282                           has no effect if a plan file is given to apply.
   283  
   284    -state=path            Path to read and save state (unless state-out
   285                           is specified). Defaults to "terraform.tfstate".
   286  
   287    -state-out=path        Path to write state to that is different than
   288                           "-state". This can be used to preserve the old
   289                           state.
   290  
   291    -target=resource       Resource to target. Operation will be limited to this
   292                           resource and its dependencies. This flag can be used
   293                           multiple times.
   294  
   295    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   296                           flag can be set multiple times.
   297  
   298    -var-file=foo          Set variables in the Terraform configuration from
   299                           a file. If "terraform.tfvars" is present, it will be
   300                           automatically loaded if this flag is not specified.
   301  
   302  
   303  `
   304  	return strings.TrimSpace(helpText)
   305  }
   306  
   307  func (c *ApplyCommand) helpDestroy() string {
   308  	helpText := `
   309  Usage: terraform destroy [options] [DIR]
   310  
   311    Destroy Terraform-managed infrastructure.
   312  
   313  Options:
   314  
   315    -backup=path           Path to backup the existing state file before
   316                           modifying. Defaults to the "-state-out" path with
   317                           ".backup" extension. Set to "-" to disable backup.
   318  
   319    -force                 Don't ask for input for destroy confirmation.
   320  
   321    -no-color              If specified, output won't contain any color.
   322  
   323    -refresh=true          Update state prior to checking for differences. This
   324                           has no effect if a plan file is given to apply.
   325  
   326    -state=path            Path to read and save state (unless state-out
   327                           is specified). Defaults to "terraform.tfstate".
   328  
   329    -state-out=path        Path to write state to that is different than
   330                           "-state". This can be used to preserve the old
   331                           state.
   332  
   333    -target=resource       Resource to target. Operation will be limited to this
   334                           resource and its dependencies. This flag can be used
   335                           multiple times.
   336  
   337    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   338                           flag can be set multiple times.
   339  
   340    -var-file=foo          Set variables in the Terraform configuration from
   341                           a file. If "terraform.tfvars" is present, it will be
   342                           automatically loaded if this flag is not specified.
   343  
   344  
   345  `
   346  	return strings.TrimSpace(helpText)
   347  }
   348  
   349  func outputsAsString(state *terraform.State) string {
   350  	if state == nil {
   351  		return ""
   352  	}
   353  
   354  	outputs := state.RootModule().Outputs
   355  	outputBuf := new(bytes.Buffer)
   356  	if len(outputs) > 0 {
   357  		outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
   358  
   359  		// Output the outputs in alphabetical order
   360  		keyLen := 0
   361  		keys := make([]string, 0, len(outputs))
   362  		for key, _ := range outputs {
   363  			keys = append(keys, key)
   364  			if len(key) > keyLen {
   365  				keyLen = len(key)
   366  			}
   367  		}
   368  		sort.Strings(keys)
   369  
   370  		for _, k := range keys {
   371  			v := outputs[k]
   372  
   373  			outputBuf.WriteString(fmt.Sprintf(
   374  				"  %s%s = %s\n",
   375  				k,
   376  				strings.Repeat(" ", keyLen-len(k)),
   377  				v))
   378  		}
   379  	}
   380  
   381  	return strings.TrimSpace(outputBuf.String())
   382  }