github.com/nathanielks/terraform@v0.6.1-0.20170509030759-13e1a62319dc/backend/local/backend_apply.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/errwrap"
    10  	"github.com/hashicorp/go-multierror"
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/command/clistate"
    13  	"github.com/hashicorp/terraform/config/module"
    14  	"github.com/hashicorp/terraform/state"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  func (b *Local) opApply(
    19  	ctx context.Context,
    20  	op *backend.Operation,
    21  	runningOp *backend.RunningOperation) {
    22  	log.Printf("[INFO] backend/local: starting Apply operation")
    23  
    24  	// If we have a nil module at this point, then set it to an empty tree
    25  	// to avoid any potential crashes.
    26  	if op.Plan == nil && op.Module == nil && !op.Destroy {
    27  		runningOp.Err = fmt.Errorf(strings.TrimSpace(applyErrNoConfig))
    28  		return
    29  	}
    30  
    31  	// If we have a nil module at this point, then set it to an empty tree
    32  	// to avoid any potential crashes.
    33  	if op.Module == nil {
    34  		op.Module = module.NewEmptyTree()
    35  	}
    36  
    37  	// Setup our count hook that keeps track of resource changes
    38  	countHook := new(CountHook)
    39  	stateHook := new(StateHook)
    40  	if b.ContextOpts == nil {
    41  		b.ContextOpts = new(terraform.ContextOpts)
    42  	}
    43  	old := b.ContextOpts.Hooks
    44  	defer func() { b.ContextOpts.Hooks = old }()
    45  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
    46  
    47  	// Get our context
    48  	tfCtx, opState, err := b.context(op)
    49  	if err != nil {
    50  		runningOp.Err = err
    51  		return
    52  	}
    53  
    54  	if op.LockState {
    55  		lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
    56  		defer cancel()
    57  
    58  		lockInfo := state.NewLockInfo()
    59  		lockInfo.Operation = op.Type.String()
    60  		lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
    61  		if err != nil {
    62  			runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
    63  			return
    64  		}
    65  
    66  		defer func() {
    67  			if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
    68  				runningOp.Err = multierror.Append(runningOp.Err, err)
    69  			}
    70  		}()
    71  	}
    72  
    73  	// Setup the state
    74  	runningOp.State = tfCtx.State()
    75  
    76  	// If we weren't given a plan, then we refresh/plan
    77  	if op.Plan == nil {
    78  		// If we're refreshing before apply, perform that
    79  		if op.PlanRefresh {
    80  			log.Printf("[INFO] backend/local: apply calling Refresh")
    81  			_, err := tfCtx.Refresh()
    82  			if err != nil {
    83  				runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    84  				return
    85  			}
    86  		}
    87  
    88  		// Perform the plan
    89  		log.Printf("[INFO] backend/local: apply calling Plan")
    90  		if _, err := tfCtx.Plan(); err != nil {
    91  			runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
    92  			return
    93  		}
    94  	}
    95  
    96  	// Setup our hook for continuous state updates
    97  	stateHook.State = opState
    98  
    99  	// Start the apply in a goroutine so that we can be interrupted.
   100  	var applyState *terraform.State
   101  	var applyErr error
   102  	doneCh := make(chan struct{})
   103  	go func() {
   104  		defer close(doneCh)
   105  		_, applyErr = tfCtx.Apply()
   106  		// we always want the state, even if apply failed
   107  		applyState = tfCtx.State()
   108  
   109  		/*
   110  			// Record any shadow errors for later
   111  			if err := ctx.ShadowError(); err != nil {
   112  				shadowErr = multierror.Append(shadowErr, multierror.Prefix(
   113  					err, "apply operation:"))
   114  			}
   115  		*/
   116  	}()
   117  
   118  	// Wait for the apply to finish or for us to be interrupted so
   119  	// we can handle it properly.
   120  	err = nil
   121  	select {
   122  	case <-ctx.Done():
   123  		if b.CLI != nil {
   124  			b.CLI.Output("stopping apply operation...")
   125  		}
   126  
   127  		// Stop execution
   128  		go tfCtx.Stop()
   129  
   130  		// Wait for completion still
   131  		<-doneCh
   132  	case <-doneCh:
   133  	}
   134  
   135  	// Store the final state
   136  	runningOp.State = applyState
   137  
   138  	// Persist the state
   139  	if err := opState.WriteState(applyState); err != nil {
   140  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   141  		return
   142  	}
   143  	if err := opState.PersistState(); err != nil {
   144  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   145  		return
   146  	}
   147  
   148  	if applyErr != nil {
   149  		runningOp.Err = fmt.Errorf(
   150  			"Error applying plan:\n\n"+
   151  				"%s\n\n"+
   152  				"Terraform does not automatically rollback in the face of errors.\n"+
   153  				"Instead, your Terraform state file has been partially updated with\n"+
   154  				"any resources that successfully completed. Please address the error\n"+
   155  				"above and apply again to incrementally change your infrastructure.",
   156  			multierror.Flatten(applyErr))
   157  		return
   158  	}
   159  
   160  	// If we have a UI, output the results
   161  	if b.CLI != nil {
   162  		if op.Destroy {
   163  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   164  				"[reset][bold][green]\n"+
   165  					"Destroy complete! Resources: %d destroyed.",
   166  				countHook.Removed)))
   167  		} else {
   168  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   169  				"[reset][bold][green]\n"+
   170  					"Apply complete! Resources: %d added, %d changed, %d destroyed.",
   171  				countHook.Added,
   172  				countHook.Changed,
   173  				countHook.Removed)))
   174  		}
   175  
   176  		if countHook.Added > 0 || countHook.Changed > 0 {
   177  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   178  				"[reset]\n"+
   179  					"The state of your infrastructure has been saved to the path\n"+
   180  					"below. This state is required to modify and destroy your\n"+
   181  					"infrastructure, so keep it safe. To inspect the complete state\n"+
   182  					"use the `terraform show` command.\n\n"+
   183  					"State path: %s",
   184  				b.StateOutPath)))
   185  		}
   186  	}
   187  }
   188  
   189  const applyErrNoConfig = `
   190  No configuration files found!
   191  
   192  Apply requires configuration to be present. Applying without a configuration
   193  would mark everything for destruction, which is normally not what is desired.
   194  If you would like to destroy everything, please run 'terraform destroy' instead
   195  which does not require any configuration files.
   196  `