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