github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/command/apply.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"os"
     8  	"sort"
     9  	"strings"
    10  
    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  	ShutdownCh <-chan struct{}
    20  }
    21  
    22  func (c *ApplyCommand) Run(args []string) int {
    23  	var refresh bool
    24  	var statePath, stateOutPath, backupPath string
    25  
    26  	args = c.Meta.process(args)
    27  
    28  	cmdFlags := c.Meta.flagSet("apply")
    29  	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
    30  	cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
    31  	cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
    32  	cmdFlags.StringVar(&backupPath, "backup", "", "path")
    33  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    34  	if err := cmdFlags.Parse(args); err != nil {
    35  		return 1
    36  	}
    37  
    38  	var configPath string
    39  	args = cmdFlags.Args()
    40  	if len(args) > 1 {
    41  		c.Ui.Error("The apply command expacts at most one argument.")
    42  		cmdFlags.Usage()
    43  		return 1
    44  	} else if len(args) == 1 {
    45  		configPath = args[0]
    46  	} else {
    47  		var err error
    48  		configPath, err = os.Getwd()
    49  		if err != nil {
    50  			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
    51  		}
    52  	}
    53  
    54  	// Prepare the extra hooks to count resources
    55  	countHook := new(CountHook)
    56  	c.Meta.extraHooks = []terraform.Hook{countHook}
    57  
    58  	// If we don't specify an output path, default to out normal state
    59  	// path.
    60  	if stateOutPath == "" {
    61  		stateOutPath = statePath
    62  	}
    63  
    64  	// If we don't specify a backup path, default to state out with
    65  	// the extention
    66  	if backupPath == "" {
    67  		backupPath = stateOutPath + DefaultBackupExtention
    68  	}
    69  
    70  	// Build the context based on the arguments given
    71  	ctx, planned, err := c.Context(configPath, statePath)
    72  	if err != nil {
    73  		c.Ui.Error(err.Error())
    74  		return 1
    75  	}
    76  	if !validateContext(ctx, c.Ui) {
    77  		return 1
    78  	}
    79  
    80  	// Create a backup of the state before updating
    81  	if backupPath != "-" && c.state != nil {
    82  		log.Printf("[INFO] Writing backup state to: %s", backupPath)
    83  		f, err := os.Create(backupPath)
    84  		if err == nil {
    85  			err = terraform.WriteState(c.state, f)
    86  			f.Close()
    87  		}
    88  		if err != nil {
    89  			c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err))
    90  			return 1
    91  		}
    92  	}
    93  
    94  	// Plan if we haven't already
    95  	if !planned {
    96  		if refresh {
    97  			if _, err := ctx.Refresh(); err != nil {
    98  				c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
    99  				return 1
   100  			}
   101  		}
   102  
   103  		if _, err := ctx.Plan(nil); err != nil {
   104  			c.Ui.Error(fmt.Sprintf(
   105  				"Error creating plan: %s", err))
   106  			return 1
   107  		}
   108  	}
   109  
   110  	// Start the apply in a goroutine so that we can be interrupted.
   111  	var state *terraform.State
   112  	var applyErr error
   113  	doneCh := make(chan struct{})
   114  	go func() {
   115  		defer close(doneCh)
   116  		state, applyErr = ctx.Apply()
   117  	}()
   118  
   119  	// Wait for the apply to finish or for us to be interrupted so
   120  	// we can handle it properly.
   121  	err = nil
   122  	select {
   123  	case <-c.ShutdownCh:
   124  		c.Ui.Output("Interrupt received. Gracefully shutting down...")
   125  
   126  		// Stop execution
   127  		ctx.Stop()
   128  
   129  		// Still get the result, since there is still one
   130  		select {
   131  		case <-c.ShutdownCh:
   132  			c.Ui.Error(
   133  				"Two interrupts received. Exiting immediately. Note that data\n" +
   134  					"loss may have occurred.")
   135  			return 1
   136  		case <-doneCh:
   137  		}
   138  	case <-doneCh:
   139  	}
   140  
   141  	if state != nil {
   142  		// Write state out to the file
   143  		f, err := os.Create(stateOutPath)
   144  		if err == nil {
   145  			err = terraform.WriteState(state, f)
   146  			f.Close()
   147  		}
   148  		if err != nil {
   149  			c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err))
   150  			return 1
   151  		}
   152  	}
   153  
   154  	if applyErr != nil {
   155  		c.Ui.Error(fmt.Sprintf(
   156  			"Error applying plan:\n\n"+
   157  				"%s\n\n"+
   158  				"Terraform does not automatically rollback in the face of errors.\n"+
   159  				"Instead, your Terraform state file has been partially updated with\n"+
   160  				"any resources that successfully completed. Please address the error\n"+
   161  				"above and apply again to incrementally change your infrastructure.",
   162  			applyErr))
   163  		return 1
   164  	}
   165  
   166  	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   167  		"[reset][bold][green]\n"+
   168  			"Apply complete! Resources: %d added, %d changed, %d destroyed.",
   169  		countHook.Added,
   170  		countHook.Changed,
   171  		countHook.Removed)))
   172  
   173  	if countHook.Added > 0 || countHook.Changed > 0 {
   174  		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
   175  			"[reset]\n"+
   176  				"The state of your infrastructure has been saved to the path\n"+
   177  				"below. This state is required to modify and destroy your\n"+
   178  				"infrastructure, so keep it safe. To inspect the complete state\n"+
   179  				"use the `terraform show` command.\n\n"+
   180  				"State path: %s",
   181  			stateOutPath)))
   182  	}
   183  
   184  	// If we have outputs, then output those at the end.
   185  	if state != nil && len(state.Outputs) > 0 {
   186  		outputBuf := new(bytes.Buffer)
   187  		outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n")
   188  
   189  		// Output the outputs in alphabetical order
   190  		keyLen := 0
   191  		keys := make([]string, 0, len(state.Outputs))
   192  		for key, _ := range state.Outputs {
   193  			keys = append(keys, key)
   194  			if len(key) > keyLen {
   195  				keyLen = len(key)
   196  			}
   197  		}
   198  		sort.Strings(keys)
   199  
   200  		for _, k := range keys {
   201  			v := state.Outputs[k]
   202  
   203  			outputBuf.WriteString(fmt.Sprintf(
   204  				"  %s%s = %s\n",
   205  				k,
   206  				strings.Repeat(" ", keyLen-len(k)),
   207  				v))
   208  		}
   209  
   210  		c.Ui.Output(c.Colorize().Color(
   211  			strings.TrimSpace(outputBuf.String())))
   212  	}
   213  
   214  	return 0
   215  }
   216  
   217  func (c *ApplyCommand) Help() string {
   218  	helpText := `
   219  Usage: terraform apply [options] [dir]
   220  
   221    Builds or changes infrastructure according to Terraform configuration
   222    files .
   223  
   224  Options:
   225  
   226    -backup=path           Path to backup the existing state file before
   227                           modifying. Defaults to the "-state-out" path with
   228                           ".backup" extention. Set to "-" to disable backup.
   229  
   230    -no-color              If specified, output won't contain any color.
   231  
   232    -refresh=true          Update state prior to checking for differences. This
   233                           has no effect if a plan file is given to apply.
   234  
   235    -state=path            Path to read and save state (unless state-out
   236                           is specified). Defaults to "terraform.tfstate".
   237  
   238    -state-out=path        Path to write state to that is different than
   239                           "-state". This can be used to preserve the old
   240                           state.
   241  
   242    -var 'foo=bar'         Set a variable in the Terraform configuration. This
   243                           flag can be set multiple times.
   244  
   245    -var-file=foo          Set variables in the Terraform configuration from
   246                           a file. If "terraform.tfvars" is present, it will be
   247                           automatically loaded if this flag is not specified.
   248  
   249  
   250  `
   251  	return strings.TrimSpace(helpText)
   252  }
   253  
   254  func (c *ApplyCommand) Synopsis() string {
   255  	return "Builds or changes infrastructure"
   256  }