Example #1
0
func (c *PlanCommand) Run(args []string) int {
	var destroy, refresh, detailed bool
	var outPath string
	var moduleDepth int

	args = c.Meta.process(args, true)

	cmdFlags := c.Meta.flagSet("plan")
	cmdFlags.BoolVar(&destroy, "destroy", false, "destroy")
	cmdFlags.BoolVar(&refresh, "refresh", true, "refresh")
	c.addModuleDepthFlag(cmdFlags, &moduleDepth)
	cmdFlags.StringVar(&outPath, "out", "", "path")
	cmdFlags.IntVar(
		&c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism")
	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
	cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	var path string
	args = cmdFlags.Args()
	if len(args) > 1 {
		c.Ui.Error(
			"The plan command expects at most one argument with the path\n" +
				"to a Terraform configuration.\n")
		cmdFlags.Usage()
		return 1
	} else if len(args) == 1 {
		path = args[0]
	} else {
		var err error
		path, err = os.Getwd()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
		}
	}

	countHook := new(CountHook)
	c.Meta.extraHooks = []terraform.Hook{countHook}

	// This is going to keep track of shadow errors
	var shadowErr error

	ctx, planned, err := c.Context(contextOpts{
		Destroy:     destroy,
		Path:        path,
		StatePath:   c.Meta.statePath,
		Parallelism: c.Meta.parallelism,
	})
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}
	if planned {
		c.Ui.Output(c.Colorize().Color(
			"[reset][bold][yellow]" +
				"The plan command received a saved plan file as input. This command\n" +
				"will output the saved plan. This will not modify the already-existing\n" +
				"plan. If you wish to generate a new plan, please pass in a configuration\n" +
				"directory as an argument.\n\n"))

		// Disable refreshing no matter what since we only want to show the plan
		refresh = false
	}

	err = terraform.SetDebugInfo(DefaultDataDir)
	if err != nil {
		c.Ui.Error(err.Error())
		return 1
	}

	if err := ctx.Input(c.InputMode()); err != nil {
		c.Ui.Error(fmt.Sprintf("Error configuring: %s", err))
		return 1
	}

	// Record any shadow errors for later
	if err := ctx.ShadowError(); err != nil {
		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
			err, "input operation:"))
	}

	if !validateContext(ctx, c.Ui) {
		return 1
	}

	if refresh {
		c.Ui.Output("Refreshing Terraform state in-memory prior to plan...")
		c.Ui.Output("The refreshed state will be used to calculate this plan, but")
		c.Ui.Output("will not be persisted to local or remote state storage.\n")
		_, err := ctx.Refresh()
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
			return 1
		}
		c.Ui.Output("")
	}

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

	if outPath != "" {
		log.Printf("[INFO] Writing plan output to: %s", outPath)
		f, err := os.Create(outPath)
		if err == nil {
			defer f.Close()
			err = terraform.WritePlan(plan, f)
		}
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err))
			return 1
		}
	}

	if plan.Diff.Empty() {
		c.Ui.Output(
			"No changes. Infrastructure is up-to-date. This means that Terraform\n" +
				"could not detect any differences between your configuration and\n" +
				"the real physical resources that exist. As a result, Terraform\n" +
				"doesn't need to do anything.")
		return 0
	}

	if outPath == "" {
		c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
	} else {
		c.Ui.Output(fmt.Sprintf(
			strings.TrimSpace(planHeaderYesOutput)+"\n",
			outPath))
	}

	c.Ui.Output(FormatPlan(&FormatPlanOpts{
		Plan:        plan,
		Color:       c.Colorize(),
		ModuleDepth: moduleDepth,
	}))

	c.Ui.Output(c.Colorize().Color(fmt.Sprintf(
		"[reset][bold]Plan:[reset] "+
			"%d to add, %d to change, %d to destroy.",
		countHook.ToAdd+countHook.ToRemoveAndAdd,
		countHook.ToChange,
		countHook.ToRemove+countHook.ToRemoveAndAdd)))

	// Record any shadow errors for later
	if err := ctx.ShadowError(); err != nil {
		shadowErr = multierror.Append(shadowErr, multierror.Prefix(
			err, "plan operation:"))
	}

	// If we have an error in the shadow graph, let the user know.
	c.outputShadowError(shadowErr, true)

	if detailed {
		return 2
	}
	return 0
}
Example #2
0
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
		}
	}

	terraform.SetDebugInfo(DefaultDataDir)

	// Check for the legacy graph
	if experiment.Enabled(experiment.X_legacyGraph) {
		c.Ui.Output(c.Colorize().Color(
			"[reset][bold][yellow]" +
				"Legacy graph enabled! This will use the graph from Terraform 0.7.x\n" +
				"to execute this operation. This will be removed in the future so\n" +
				"please report any issues causing you to use this to the Terraform\n" +
				"project.\n\n"))
	}

	// This is going to keep track of shadow errors
	var shadowErr error

	// 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
		}

		// Record any shadow errors for later
		if err := ctx.ShadowError(); err != nil {
			shadowErr = multierror.Append(shadowErr, multierror.Prefix(
				err, "input operation:"))
		}
	}
	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
		}

		// Record any shadow errors for later
		if err := ctx.ShadowError(); err != nil {
			shadowErr = multierror.Append(shadowErr, multierror.Prefix(
				err, "plan operation:"))
		}
	}

	// 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()

		// Record any shadow errors for later
		if err := ctx.ShadowError(); err != nil {
			shadowErr = multierror.Append(shadowErr, multierror.Prefix(
				err, "apply operation:"))
		}
	}()

	// 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))
		}
	}

	// If we have an error in the shadow graph, let the user know.
	c.outputShadowError(shadowErr, applyErr == nil)

	return 0
}