github.com/sixgill/terraform@v0.9.0-beta2.0.20170316214032-033f6226ae50/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  	clistate "github.com/hashicorp/terraform/command/state"
    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  		lockInfo := state.NewLockInfo()
    56  		lockInfo.Operation = op.Type.String()
    57  		lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
    58  		if err != nil {
    59  			runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
    60  			return
    61  		}
    62  
    63  		defer func() {
    64  			if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
    65  				runningOp.Err = multierror.Append(runningOp.Err, err)
    66  			}
    67  		}()
    68  	}
    69  
    70  	// Setup the state
    71  	runningOp.State = tfCtx.State()
    72  
    73  	// If we weren't given a plan, then we refresh/plan
    74  	if op.Plan == nil {
    75  		// If we're refreshing before apply, perform that
    76  		if op.PlanRefresh {
    77  			log.Printf("[INFO] backend/local: apply calling Refresh")
    78  			_, err := tfCtx.Refresh()
    79  			if err != nil {
    80  				runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
    81  				return
    82  			}
    83  		}
    84  
    85  		// Perform the plan
    86  		log.Printf("[INFO] backend/local: apply calling Plan")
    87  		if _, err := tfCtx.Plan(); err != nil {
    88  			runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err)
    89  			return
    90  		}
    91  	}
    92  
    93  	// Setup our hook for continuous state updates
    94  	stateHook.State = opState
    95  
    96  	// Start the apply in a goroutine so that we can be interrupted.
    97  	var applyState *terraform.State
    98  	var applyErr error
    99  	doneCh := make(chan struct{})
   100  	go func() {
   101  		defer close(doneCh)
   102  		applyState, applyErr = tfCtx.Apply()
   103  
   104  		/*
   105  			// Record any shadow errors for later
   106  			if err := ctx.ShadowError(); err != nil {
   107  				shadowErr = multierror.Append(shadowErr, multierror.Prefix(
   108  					err, "apply operation:"))
   109  			}
   110  		*/
   111  	}()
   112  
   113  	// Wait for the apply to finish or for us to be interrupted so
   114  	// we can handle it properly.
   115  	err = nil
   116  	select {
   117  	case <-ctx.Done():
   118  		if b.CLI != nil {
   119  			b.CLI.Output("Interrupt received. Gracefully shutting down...")
   120  		}
   121  
   122  		// Stop execution
   123  		go tfCtx.Stop()
   124  
   125  		// Wait for completion still
   126  		<-doneCh
   127  	case <-doneCh:
   128  	}
   129  
   130  	// Store the final state
   131  	runningOp.State = applyState
   132  
   133  	// Persist the state
   134  	if err := opState.WriteState(applyState); err != nil {
   135  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   136  		return
   137  	}
   138  	if err := opState.PersistState(); err != nil {
   139  		runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
   140  		return
   141  	}
   142  
   143  	if applyErr != nil {
   144  		runningOp.Err = fmt.Errorf(
   145  			"Error applying plan:\n\n"+
   146  				"%s\n\n"+
   147  				"Terraform does not automatically rollback in the face of errors.\n"+
   148  				"Instead, your Terraform state file has been partially updated with\n"+
   149  				"any resources that successfully completed. Please address the error\n"+
   150  				"above and apply again to incrementally change your infrastructure.",
   151  			multierror.Flatten(applyErr))
   152  		return
   153  	}
   154  
   155  	// If we have a UI, output the results
   156  	if b.CLI != nil {
   157  		if op.Destroy {
   158  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   159  				"[reset][bold][green]\n"+
   160  					"Destroy complete! Resources: %d destroyed.",
   161  				countHook.Removed)))
   162  		} else {
   163  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   164  				"[reset][bold][green]\n"+
   165  					"Apply complete! Resources: %d added, %d changed, %d destroyed.",
   166  				countHook.Added,
   167  				countHook.Changed,
   168  				countHook.Removed)))
   169  		}
   170  
   171  		if countHook.Added > 0 || countHook.Changed > 0 {
   172  			b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
   173  				"[reset]\n"+
   174  					"The state of your infrastructure has been saved to the path\n"+
   175  					"below. This state is required to modify and destroy your\n"+
   176  					"infrastructure, so keep it safe. To inspect the complete state\n"+
   177  					"use the `terraform show` command.\n\n"+
   178  					"State path: %s",
   179  				b.StateOutPath)))
   180  		}
   181  	}
   182  }
   183  
   184  const applyErrNoConfig = `
   185  No configuration files found!
   186  
   187  Apply requires configuration to be present. Applying without a configuration
   188  would mark everything for destruction, which is normally not what is desired.
   189  If you would like to destroy everything, please run 'terraform destroy' instead
   190  which does not require any configuration files.
   191  `