コード例 #1
0
ファイル: context.go プロジェクト: anthcor/terraform
// Apply applies the changes represented by this context and returns
// the resulting state.
//
// In addition to returning the resulting state, this context is updated
// with the latest state.
func (c *Context) Apply() (*State, error) {
	v := c.acquireRun("apply")
	defer c.releaseRun(v)

	// Copy our own state
	c.state = c.state.DeepCopy()

	// Enable the new graph by default
	X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)

	// Build the graph.
	var graph *Graph
	var err error
	if !X_legacyGraph {
		graph, err = (&ApplyGraphBuilder{
			Module:       c.module,
			Diff:         c.diff,
			State:        c.state,
			Providers:    c.components.ResourceProviders(),
			Provisioners: c.components.ResourceProvisioners(),
			Destroy:      c.destroy,
		}).Build(RootModulePath)
	} else {
		graph, err = c.Graph(&ContextGraphOpts{Validate: true})
	}
	if err != nil {
		return nil, err
	}

	// Determine the operation
	operation := walkApply
	if c.destroy {
		operation = walkDestroy
	}

	// Walk the graph
	walker, err := c.walk(graph, graph, operation)
	if len(walker.ValidationErrors) > 0 {
		err = multierror.Append(err, walker.ValidationErrors...)
	}

	// Clean out any unused things
	c.state.prune()

	return c.state, err
}
コード例 #2
0
ファイル: apply.go プロジェクト: jmasseo/terraform
func (c *ApplyCommand) Run(args []string) int {
	var destroyForce, refresh bool
	args = c.Meta.process(args, true)

	cmdName := "apply"
	if c.Destroy {
		cmdName = "destroy"
	}

	cmdFlags := c.Meta.flagSet(cmdName)
	if c.Destroy {
		cmdFlags.BoolVar(&destroyForce, "force", false, "force")
	}
	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
	cmdFlags.IntVar(
		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	pwd, err := os.Getwd()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		return 1
	}

	var configPath string
	maybeInit := true
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error("The apply command expects at most one argument.")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		configPath = args[0]
	} else {
		configPath = pwd
		maybeInit = false
	}

	// Prepare the extra hooks to count resources
	countHook := new(CountHook)
	stateHook := new(StateHook)
	c.Meta.extraHooks = []terraform.Hook{countHook, stateHook}

	if !c.Destroy && maybeInit {
		// Do a detect to determine if we need to do an init + apply.
		if detected, err := getter.Detect(configPath, pwd, getter.Detectors); err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Invalid path: %s", err))
			return 1
		} else if !strings.HasPrefix(detected, "file") {
			// If this isn't a file URL then we're doing an init +
			// apply.
			var init InitCommand
			init.Meta = c.Meta
			if code := init.Run([]string{detected}); code != 0 {
				return code
			}

			// Change the config path to be the cwd
			configPath = pwd
		}
	}

	// Check for the new apply
	if experiment.Enabled(experiment.X_newApply) && !experiment.Force() {
		desc := "Experimental new apply graph has been enabled. This may still\n" +
			"have bugs, and should be used with care. If you'd like to continue,\n" +
			"you must enter exactly 'yes' as a response."
		v, err := c.UIInput().Input(&terraform.InputOpts{
			Id:          "Xnew-apply",
			Query:       "Experimental feature enabled: new apply graph. Continue?",
			Description: desc,
		})
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
			return 1
		}
		if v != "yes" {
			c.Ui.Output("Apply cancelled.")
			return 1
		}
	}

	// Check for the new destroy
	if experiment.Enabled(experiment.X_newDestroy) && !experiment.Force() {
		desc := "Experimental new destroy graph has been enabled. This may still\n" +
			"have bugs, and should be used with care. If you'd like to continue,\n" +
			"you must enter exactly 'yes' as a response."
		v, err := c.UIInput().Input(&terraform.InputOpts{
			Id:          "Xnew-destroy",
			Query:       "Experimental feature enabled: new destroy graph. Continue?",
			Description: desc,
		})
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
			return 1
		}
		if v != "yes" {
			c.Ui.Output("Apply cancelled.")
			return 1
		}
	}

	// Build the context based on the arguments given
	ctx, planned, err := c.Context(contextOpts{
		Destroy:     c.Destroy,
		Path:        configPath,
		StatePath:   c.Meta.statePath,
		Parallelism: c.Meta.parallelism,
	})
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if c.Destroy && planned {
		c.Ui.Error(fmt.Sprintf(
			"Destroy can't be called with a plan file."))
		return 1
	}
	if !destroyForce && c.Destroy {
		// Default destroy message
		desc := "Terraform will delete all your managed infrastructure.\n" +
			"There is no undo. Only 'yes' will be accepted to confirm."

		// If targets are specified, list those to user
		if c.Meta.targets != nil {
			var descBuffer bytes.Buffer
			descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
			for _, target := range c.Meta.targets {
				descBuffer.WriteString("\t")
				descBuffer.WriteString(target)
				descBuffer.WriteString("\n")
			}
			descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
			desc = descBuffer.String()
		}

		v, err := c.UIInput().Input(&terraform.InputOpts{
			Id:          "destroy",
			Query:       "Do you really want to destroy?",
			Description: desc,
		})
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error asking for confirmation: %s", err))
			return 1
		}
		if v != "yes" {
			c.Ui.Output("Destroy cancelled.")
			return 1
		}
	}
	if !planned {
		if err := ctx.Input(c.InputMode()); err != nil {
			c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
			return 1
		}
	}
	if !validateContext(ctx, c.Ui) {
		return 1
	}

	// Plan if we haven't already
	if !planned {
		if refresh {
			if _, err := ctx.Refresh(); err != nil {
				c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
				return 1
			}
		}

		if _, err := ctx.Plan(); err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error creating plan: %s", err))
			return 1
		}
	}

	// Setup the state hook for continuous state updates
	{
		state, err := c.State()
		if err != nil {
			c.Ui.Error(fmt.Sprintf(
				"Error reading state: %s", err))
			return 1
		}

		stateHook.State = state
	}

	// Start the apply in a goroutine so that we can be interrupted.
	var state *terraform.State
	var applyErr error
	doneCh := make(chan struct{})
	go func() {
		defer close(doneCh)
		state, applyErr = ctx.Apply()
	}()

	// Wait for the apply to finish or for us to be interrupted so
	// we can handle it properly.
	err = nil
	select {
	case <-c.ShutdownCh:
		c.Ui.Output("Interrupt received. Gracefully shutting down...")

		// Stop execution
		go ctx.Stop()

		// Still get the result, since there is still one
		select {
		case <-c.ShutdownCh:
			c.Ui.Error(
				"Two interrupts received. Exiting immediately. Note that data\n" +
					"loss may have occurred.")
			return 1
		case <-doneCh:
		}
	case <-doneCh:
	}

	// Persist the state
	if state != nil {
		if err := c.Meta.PersistState(state); err != nil {
			c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err))
			return 1
		}
	}

	if applyErr != nil {
		c.Ui.Error(fmt.Sprintf(
			"Error applying plan:\n\n"+
				"%s\n\n"+
				"Terraform does not automatically rollback in the face of errors.\n"+
				"Instead, your Terraform state file has been partially updated with\n"+
				"any resources that successfully completed. Please address the error\n"+
				"above and apply again to incrementally change your infrastructure.",
			multierror.Flatten(applyErr)))
		return 1
	}

	if c.Destroy {
		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
			"[reset][bold][green]\n"+
				"Destroy complete! Resources: %d destroyed.",
			countHook.Removed)))
	} else {
		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
			"[reset][bold][green]\n"+
				"Apply complete! Resources: %d added, %d changed, %d destroyed.",
			countHook.Added,
			countHook.Changed,
			countHook.Removed)))
	}

	if countHook.Added > 0 || countHook.Changed > 0 {
		c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
			"[reset]\n"+
				"The state of your infrastructure has been saved to the path\n"+
				"below. This state is required to modify and destroy your\n"+
				"infrastructure, so keep it safe. To inspect the complete state\n"+
				"use the `terraform show` command.\n\n"+
				"State path: %s",
			c.Meta.StateOutPath())))
	}

	if !c.Destroy {
		if outputs := outputsAsString(state, terraform.RootModulePath, ctx.Module().Config().Outputs, true); outputs != "" {
			c.Ui.Output(c.Colorize().Color(outputs))
		}
	}

	return 0
}
コード例 #3
0
ファイル: context.go プロジェクト: anthcor/terraform
func (c *Context) walk(
	graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) {
	// Keep track of the "real" context which is the context that does
	// the real work: talking to real providers, modifying real state, etc.
	realCtx := c

	// If we don't want shadowing, remove it
	if !experiment.Enabled(experiment.X_shadow) {
		shadow = nil
	}

	// If we have a shadow graph, walk that as well
	var shadowCtx *Context
	var shadowCloser Shadow
	if c.shadow && shadow != nil {
		// Build the shadow context. In the process, override the real context
		// with the one that is wrapped so that the shadow context can verify
		// the results of the real.
		realCtx, shadowCtx, shadowCloser = newShadowContext(c)
	}

	// Just log this so we can see it in a debug log
	if !c.shadow {
		log.Printf("[WARN] terraform: shadow graph disabled")
	}

	log.Printf("[DEBUG] Starting graph walk: %s", operation.String())

	walker := &ContextGraphWalker{
		Context:   realCtx,
		Operation: operation,
	}

	// Watch for a stop so we can call the provider Stop() API.
	doneCh := make(chan struct{})
	go c.watchStop(walker, c.stopCh, doneCh)

	// Walk the real graph, this will block until it completes
	realErr := graph.Walk(walker)

	// Close the done channel so the watcher stops
	close(doneCh)

	// If we have a shadow graph and we interrupted the real graph, then
	// we just close the shadow and never verify it. It is non-trivial to
	// recreate the exact execution state up until an interruption so this
	// isn't supported with shadows at the moment.
	if shadowCloser != nil && c.sh.Stopped() {
		// Ignore the error result, there is nothing we could care about
		shadowCloser.CloseShadow()

		// Set it to nil so we don't do anything
		shadowCloser = nil
	}

	// If we have a shadow graph, wait for that to complete.
	if shadowCloser != nil {
		// Build the graph walker for the shadow. We also wrap this in
		// a panicwrap so that panics are captured. For the shadow graph,
		// we just want panics to be normal errors rather than to crash
		// Terraform.
		shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
			Context:   shadowCtx,
			Operation: operation,
		})

		// Kick off the shadow walk. This will block on any operations
		// on the real walk so it is fine to start first.
		log.Printf("[INFO] Starting shadow graph walk: %s", operation.String())
		shadowCh := make(chan error)
		go func() {
			shadowCh <- shadow.Walk(shadowWalker)
		}()

		// Notify the shadow that we're done
		if err := shadowCloser.CloseShadow(); err != nil {
			c.shadowErr = multierror.Append(c.shadowErr, err)
		}

		// Wait for the walk to end
		log.Printf("[DEBUG] Waiting for shadow graph to complete...")
		shadowWalkErr := <-shadowCh

		// Get any shadow errors
		if err := shadowCloser.ShadowError(); err != nil {
			c.shadowErr = multierror.Append(c.shadowErr, err)
		}

		// Verify the contexts (compare)
		if err := shadowContextVerify(realCtx, shadowCtx); err != nil {
			c.shadowErr = multierror.Append(c.shadowErr, err)
		}

		// At this point, if we're supposed to fail on error, then
		// we PANIC. Some tests just verify that there is an error,
		// so simply appending it to realErr and returning could hide
		// shadow problems.
		//
		// This must be done BEFORE appending shadowWalkErr since the
		// shadowWalkErr may include expected errors.
		//
		// We only do this if we don't have a real error. In the case of
		// a real error, we can't guarantee what nodes were and weren't
		// traversed in parallel scenarios so we can't guarantee no
		// shadow errors.
		if c.shadowErr != nil && contextFailOnShadowError && realErr == nil {
			panic(multierror.Prefix(c.shadowErr, "shadow graph:"))
		}

		// Now, if we have a walk error, we append that through
		if shadowWalkErr != nil {
			c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr)
		}

		if c.shadowErr == nil {
			log.Printf("[INFO] Shadow graph success!")
		} else {
			log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr)

			// If we're supposed to fail on shadow errors, then report it
			if contextFailOnShadowError {
				realErr = multierror.Append(realErr, multierror.Prefix(
					c.shadowErr, "shadow graph:"))
			}
		}
	}

	return walker, realErr
}
コード例 #4
0
ファイル: context.go プロジェクト: anthcor/terraform
// Plan generates an execution plan for the given context.
//
// The execution plan encapsulates the context and can be stored
// in order to reinstantiate a context later for Apply.
//
// Plan also updates the diff of this context to be the diff generated
// by the plan, so Apply can be called after.
func (c *Context) Plan() (*Plan, error) {
	v := c.acquireRun("plan")
	defer c.releaseRun(v)

	p := &Plan{
		Module:  c.module,
		Vars:    c.variables,
		State:   c.state,
		Targets: c.targets,
	}

	var operation walkOperation
	if c.destroy {
		operation = walkPlanDestroy
	} else {
		// Set our state to be something temporary. We do this so that
		// the plan can update a fake state so that variables work, then
		// we replace it back with our old state.
		old := c.state
		if old == nil {
			c.state = &State{}
			c.state.init()
		} else {
			c.state = old.DeepCopy()
		}
		defer func() {
			c.state = old
		}()

		operation = walkPlan
	}

	// Setup our diff
	c.diffLock.Lock()
	c.diff = new(Diff)
	c.diff.init()
	c.diffLock.Unlock()

	// Used throughout below
	X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)

	// Build the graph.
	var graph *Graph
	var err error
	if !X_legacyGraph {
		if c.destroy {
			graph, err = (&DestroyPlanGraphBuilder{
				Module:  c.module,
				State:   c.state,
				Targets: c.targets,
			}).Build(RootModulePath)
		} else {
			graph, err = (&PlanGraphBuilder{
				Module:    c.module,
				State:     c.state,
				Providers: c.components.ResourceProviders(),
				Targets:   c.targets,
			}).Build(RootModulePath)
		}
	} else {
		graph, err = c.Graph(&ContextGraphOpts{Validate: true})
	}
	if err != nil {
		return nil, err
	}

	// Do the walk
	walker, err := c.walk(graph, graph, operation)
	if err != nil {
		return nil, err
	}
	p.Diff = c.diff

	// If this is true, it means we're running unit tests. In this case,
	// we perform a deep copy just to ensure that all context tests also
	// test that a diff is copy-able. This will panic if it fails. This
	// is enabled during unit tests.
	//
	// This should never be true during production usage, but even if it is,
	// it can't do any real harm.
	if contextTestDeepCopyOnPlan {
		p.Diff.DeepCopy()
	}

	// We don't do the reverification during the new destroy plan because
	// it will use a different apply process.
	if X_legacyGraph {
		// Now that we have a diff, we can build the exact graph that Apply will use
		// and catch any possible cycles during the Plan phase.
		if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
			return nil, err
		}
	}

	var errs error
	if len(walker.ValidationErrors) > 0 {
		errs = multierror.Append(errs, walker.ValidationErrors...)
	}
	return p, errs
}
コード例 #5
0
ファイル: context.go プロジェクト: jmasseo/terraform
// Apply applies the changes represented by this context and returns
// the resulting state.
//
// In addition to returning the resulting state, this context is updated
// with the latest state.
func (c *Context) Apply() (*State, error) {
	v := c.acquireRun()
	defer c.releaseRun(v)

	// Copy our own state
	c.state = c.state.DeepCopy()

	X_newApply := experiment.Enabled(experiment.X_newApply)
	X_newDestroy := experiment.Enabled(experiment.X_newDestroy)
	newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply)

	// Build the original graph. This is before the new graph builders
	// coming in 0.8. We do this for shadow graphing.
	oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
	if err != nil && X_newApply {
		// If we had an error graphing but we're using the new graph,
		// just set it to nil and let it go. There are some features that
		// may work with the new graph that don't with the old.
		oldGraph = nil
		err = nil
	}
	if err != nil {
		return nil, err
	}

	// Build the new graph. We do this no matter what so we can shadow it.
	newGraph, err := (&ApplyGraphBuilder{
		Module:       c.module,
		Diff:         c.diff,
		State:        c.state,
		Providers:    c.components.ResourceProviders(),
		Provisioners: c.components.ResourceProvisioners(),
		Destroy:      c.destroy,
	}).Build(RootModulePath)
	if err != nil && !newGraphEnabled {
		// If we had an error graphing but we're not using this graph, just
		// set it to nil and record it as a shadow error.
		c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
			"Error building new graph: %s", err))

		newGraph = nil
		err = nil
	}
	if err != nil {
		return nil, err
	}

	// Determine what is the real and what is the shadow. The logic here
	// is straightforward though the if statements are not:
	//
	//   * Destroy mode - always use original, shadow with nothing because
	//     we're only testing the new APPLY graph.
	//   * Apply with new apply - use new graph, shadow is new graph. We can't
	//     shadow with the old graph because the old graph does a lot more
	//     that it shouldn't.
	//   * Apply with old apply - use old graph, shadow with new graph.
	//
	real := oldGraph
	shadow := newGraph
	if newGraphEnabled {
		log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment")
		real = shadow
	} else {
		log.Printf("[WARN] terraform: real graph is original, shadow is experiment")
	}

	// Determine the operation
	operation := walkApply
	if c.destroy {
		operation = walkDestroy
	}

	// This shouldn't happen, so assert it. This is before any state changes
	// so it is safe to crash here.
	if real == nil {
		panic("nil real graph")
	}

	// Walk the graph
	walker, err := c.walk(real, shadow, operation)
	if len(walker.ValidationErrors) > 0 {
		err = multierror.Append(err, walker.ValidationErrors...)
	}

	// Clean out any unused things
	c.state.prune()

	return c.state, err
}
コード例 #6
0
// Plan generates an execution plan for the given context.
//
// The execution plan encapsulates the context and can be stored
// in order to reinstantiate a context later for Apply.
//
// Plan also updates the diff of this context to be the diff generated
// by the plan, so Apply can be called after.
func (c *Context) Plan() (*Plan, error) {
	v := c.acquireRun("plan")
	defer c.releaseRun(v)

	p := &Plan{
		Module:  c.module,
		Vars:    c.variables,
		State:   c.state,
		Targets: c.targets,
	}

	var operation walkOperation
	if c.destroy {
		operation = walkPlanDestroy
	} else {
		// Set our state to be something temporary. We do this so that
		// the plan can update a fake state so that variables work, then
		// we replace it back with our old state.
		old := c.state
		if old == nil {
			c.state = &State{}
			c.state.init()
		} else {
			c.state = old.DeepCopy()
		}
		defer func() {
			c.state = old
		}()

		operation = walkPlan
	}

	// Setup our diff
	c.diffLock.Lock()
	c.diff = new(Diff)
	c.diff.init()
	c.diffLock.Unlock()

	// Used throughout below
	X_newApply := experiment.Enabled(experiment.X_newApply)
	X_newDestroy := experiment.Enabled(experiment.X_newDestroy)
	newGraphEnabled := (c.destroy && X_newDestroy) || (!c.destroy && X_newApply)

	// Build the original graph. This is before the new graph builders
	// coming in 0.8. We do this for shadow graphing.
	oldGraph, err := c.Graph(&ContextGraphOpts{Validate: true})
	if err != nil && newGraphEnabled {
		// If we had an error graphing but we're using the new graph,
		// just set it to nil and let it go. There are some features that
		// may work with the new graph that don't with the old.
		oldGraph = nil
		err = nil
	}
	if err != nil {
		return nil, err
	}

	// Build the new graph. We do this no matter wht so we can shadow it.
	var newGraph *Graph
	err = nil
	if c.destroy {
		newGraph, err = (&DestroyPlanGraphBuilder{
			Module:  c.module,
			State:   c.state,
			Targets: c.targets,
		}).Build(RootModulePath)
	} else {
		newGraph, err = (&PlanGraphBuilder{
			Module:    c.module,
			State:     c.state,
			Providers: c.components.ResourceProviders(),
			Targets:   c.targets,
		}).Build(RootModulePath)
	}
	if err != nil && !newGraphEnabled {
		// If we had an error graphing but we're not using this graph, just
		// set it to nil and record it as a shadow error.
		c.shadowErr = multierror.Append(c.shadowErr, fmt.Errorf(
			"Error building new graph: %s", err))

		newGraph = nil
		err = nil
	}
	if err != nil {
		return nil, err
	}

	// Determine what is the real and what is the shadow. The logic here
	// is straightforward though the if statements are not:
	//
	//  * If the new graph, shadow with experiment in both because the
	//    experiment has less nodes so the original can't shadow.
	//  * If not the new graph, shadow with the experiment
	//
	real := oldGraph
	shadow := newGraph
	if newGraphEnabled {
		log.Printf("[WARN] terraform: real graph is experiment, shadow is experiment")
		real = shadow
	} else {
		log.Printf("[WARN] terraform: real graph is original, shadow is experiment")
	}

	// Special case here: if we're using destroy don't shadow it because
	// the new destroy graph behaves a bit differently on purpose by not
	// setting the module destroy flag.
	if c.destroy && !newGraphEnabled {
		shadow = nil
	}

	// Do the walk
	walker, err := c.walk(real, shadow, operation)
	if err != nil {
		return nil, err
	}
	p.Diff = c.diff

	// If this is true, it means we're running unit tests. In this case,
	// we perform a deep copy just to ensure that all context tests also
	// test that a diff is copy-able. This will panic if it fails. This
	// is enabled during unit tests.
	//
	// This should never be true during production usage, but even if it is,
	// it can't do any real harm.
	if contextTestDeepCopyOnPlan {
		p.Diff.DeepCopy()
	}

	// We don't do the reverification during the new destroy plan because
	// it will use a different apply process.
	if !newGraphEnabled {
		// Now that we have a diff, we can build the exact graph that Apply will use
		// and catch any possible cycles during the Plan phase.
		if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
			return nil, err
		}
	}

	var errs error
	if len(walker.ValidationErrors) > 0 {
		errs = multierror.Append(errs, walker.ValidationErrors...)
	}
	return p, errs
}