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