github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/local/backend_apply.go (about)

     1  package local
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  
     8  	"github.com/hashicorp/errwrap"
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/hashicorp/terraform/backend"
    11  	clistate "github.com/hashicorp/terraform/command/state"
    12  	"github.com/hashicorp/terraform/terraform"
    13  )
    14  
    15  func (b *Local) opApply(
    16  	ctx context.Context,
    17  	op *backend.Operation,
    18  	runningOp *backend.RunningOperation) {
    19  	log.Printf("[INFO] backend/local: starting Apply operation")
    20  
    21  	// Setup our count hook that keeps track of resource changes
    22  	countHook := new(CountHook)
    23  	stateHook := new(StateHook)
    24  	if b.ContextOpts == nil {
    25  		b.ContextOpts = new(terraform.ContextOpts)
    26  	}
    27  	old := b.ContextOpts.Hooks
    28  	defer func() { b.ContextOpts.Hooks = old }()
    29  	b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
    30  
    31  	// Get our context
    32  	tfCtx, opState, err := b.context(op)
    33  	if err != nil {
    34  		runningOp.Err = err
    35  		return
    36  	}
    37  
    38  	// If we're locking state, unlock when we're done
    39  	if op.LockState {
    40  		defer func() {
    41  			if err := clistate.Unlock(opState, b.CLI, b.Colorize()); err != nil {
    42  				runningOp.Err = multierror.Append(runningOp.Err, err)
    43  			}
    44  		}()
    45  	}
    46  
    47  	// Setup the state
    48  	runningOp.State = tfCtx.State()
    49  
    50  	// If we weren't given a plan, then we refresh/plan
    51  	if op.Plan == nil {
    52  		// If we're refreshing before apply, perform that
    53  		if op.PlanRefresh {
    54  			log.Printf("[INFO] backend/local: apply calling Refresh")
    55  			_, err := tfCtx.Refresh()
    56  			if err != nil {
    57  				runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    58  				return
    59  			}
    60  		}
    61  
    62  		// Perform the plan
    63  		log.Printf("[INFO] backend/local: apply calling Plan")
    64  		if _, err := tfCtx.Plan(); err != nil {
    65  			runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
    66  			return
    67  		}
    68  	}
    69  
    70  	// Setup our hook for continuous state updates
    71  	stateHook.State = opState
    72  
    73  	// Start the apply in a goroutine so that we can be interrupted.
    74  	var applyState *terraform.State
    75  	var applyErr error
    76  	doneCh := make(chan struct{})
    77  	go func() {
    78  		defer close(doneCh)
    79  		applyState, applyErr = tfCtx.Apply()
    80  
    81  		/*
    82  			// Record any shadow errors for later
    83  			if err := ctx.ShadowError(); err != nil {
    84  				shadowErr = multierror.Append(shadowErr, multierror.Prefix(
    85  					err, "apply operation:"))
    86  			}
    87  		*/
    88  	}()
    89  
    90  	// Wait for the apply to finish or for us to be interrupted so
    91  	// we can handle it properly.
    92  	err = nil
    93  	select {
    94  	case <-ctx.Done():
    95  		if b.CLI != nil {
    96  			b.CLI.Output("Interrupt received. Gracefully shutting down...")
    97  		}
    98  
    99  		// Stop execution
   100  		go tfCtx.Stop()
   101  
   102  		// Wait for completion still
   103  		<-doneCh
   104  	case <-doneCh:
   105  	}
   106  
   107  	// Store the final state
   108  	runningOp.State = applyState
   109  
   110  	// Persist the state
   111  	if err := opState.WriteState(applyState); err != nil {
   112  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   113  		return
   114  	}
   115  	if err := opState.PersistState(); err != nil {
   116  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   117  		return
   118  	}
   119  
   120  	if applyErr != nil {
   121  		runningOp.Err = fmt.Errorf(
   122  			"Error applying plan:\n\n"+
   123  				"%s\n\n"+
   124  				"Terraform does not automatically rollback in the face of errors.\n"+
   125  				"Instead, your Terraform state file has been partially updated with\n"+
   126  				"any resources that successfully completed. Please address the error\n"+
   127  				"above and apply again to incrementally change your infrastructure.",
   128  			multierror.Flatten(applyErr))
   129  		return
   130  	}
   131  
   132  	// If we have a UI, output the results
   133  	if b.CLI != nil {
   134  		if op.Destroy {
   135  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   136  				"[reset][bold][green]\n"+
   137  					"Destroy complete! Resources: %d destroyed.",
   138  				countHook.Removed)))
   139  		} else {
   140  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   141  				"[reset][bold][green]\n"+
   142  					"Apply complete! Resources: %d added, %d changed, %d destroyed.",
   143  				countHook.Added,
   144  				countHook.Changed,
   145  				countHook.Removed)))
   146  		}
   147  
   148  		if countHook.Added > 0 || countHook.Changed > 0 {
   149  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   150  				"[reset]\n"+
   151  					"The state of your infrastructure has been saved to the path\n"+
   152  					"below. This state is required to modify and destroy your\n"+
   153  					"infrastructure, so keep it safe. To inspect the complete state\n"+
   154  					"use the `terraform show` command.\n\n"+
   155  					"State path: %s",
   156  				b.StateOutPath)))
   157  		}
   158  	}
   159  }