github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/backend/local/backend_plan.go (about) 1 package local 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "os" 8 "strings" 9 10 "github.com/hashicorp/errwrap" 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/command/format" 14 clistate "github.com/hashicorp/terraform/command/state" 15 "github.com/hashicorp/terraform/config/module" 16 "github.com/hashicorp/terraform/terraform" 17 ) 18 19 func (b *Local) opPlan( 20 ctx context.Context, 21 op *backend.Operation, 22 runningOp *backend.RunningOperation) { 23 log.Printf("[INFO] backend/local: starting Plan operation") 24 25 if b.CLI != nil && op.Plan != nil { 26 b.CLI.Output(b.Colorize().Color( 27 "[reset][bold][yellow]" + 28 "The plan command received a saved plan file as input. This command\n" + 29 "will output the saved plan. This will not modify the already-existing\n" + 30 "plan. If you wish to generate a new plan, please pass in a configuration\n" + 31 "directory as an argument.\n\n")) 32 } 33 34 // A local plan requires either a plan or a module 35 if op.Plan == nil && op.Module == nil && !op.Destroy { 36 runningOp.Err = fmt.Errorf(strings.TrimSpace(planErrNoConfig)) 37 return 38 } 39 40 // If we have a nil module at this point, then set it to an empty tree 41 // to avoid any potential crashes. 42 if op.Module == nil { 43 op.Module = module.NewEmptyTree() 44 } 45 46 // Setup our count hook that keeps track of resource changes 47 countHook := new(CountHook) 48 if b.ContextOpts == nil { 49 b.ContextOpts = new(terraform.ContextOpts) 50 } 51 old := b.ContextOpts.Hooks 52 defer func() { b.ContextOpts.Hooks = old }() 53 b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook) 54 55 // Get our context 56 tfCtx, opState, err := b.context(op) 57 if err != nil { 58 runningOp.Err = err 59 return 60 } 61 62 // If we're locking state, unlock when we're done 63 if op.LockState { 64 defer func() { 65 if err := clistate.Unlock(opState, b.CLI, b.Colorize()); err != nil { 66 runningOp.Err = multierror.Append(runningOp.Err, err) 67 } 68 }() 69 } 70 71 // Setup the state 72 runningOp.State = tfCtx.State() 73 74 // If we're refreshing before plan, perform that 75 if op.PlanRefresh { 76 log.Printf("[INFO] backend/local: plan calling Refresh") 77 78 if b.CLI != nil { 79 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n")) 80 } 81 82 _, err := tfCtx.Refresh() 83 if err != nil { 84 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 85 return 86 } 87 } 88 89 // Perform the plan 90 log.Printf("[INFO] backend/local: plan calling Plan") 91 plan, err := tfCtx.Plan() 92 if err != nil { 93 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) 94 return 95 } 96 97 // Record state 98 runningOp.PlanEmpty = plan.Diff.Empty() 99 100 // Save the plan to disk 101 if path := op.PlanOutPath; path != "" { 102 // Write the backend if we have one 103 plan.Backend = op.PlanOutBackend 104 105 log.Printf("[INFO] backend/local: writing plan output to: %s", path) 106 f, err := os.Create(path) 107 if err == nil { 108 err = terraform.WritePlan(plan, f) 109 } 110 f.Close() 111 if err != nil { 112 runningOp.Err = fmt.Errorf("Error writing plan file: %s", err) 113 return 114 } 115 } 116 117 // Perform some output tasks if we have a CLI to output to. 118 if b.CLI != nil { 119 if plan.Diff.Empty() { 120 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges))) 121 return 122 } 123 124 if path := op.PlanOutPath; path == "" { 125 b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 126 } else { 127 b.CLI.Output(fmt.Sprintf( 128 strings.TrimSpace(planHeaderYesOutput)+"\n", 129 path)) 130 } 131 132 b.CLI.Output(format.Plan(&format.PlanOpts{ 133 Plan: plan, 134 Color: b.Colorize(), 135 ModuleDepth: -1, 136 })) 137 138 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 139 "[reset][bold]Plan:[reset] "+ 140 "%d to add, %d to change, %d to destroy.", 141 countHook.ToAdd+countHook.ToRemoveAndAdd, 142 countHook.ToChange, 143 countHook.ToRemove+countHook.ToRemoveAndAdd))) 144 } 145 } 146 147 const planErrNoConfig = ` 148 No configuration files found! 149 150 Plan requires configuration to be present. Planning without a configuration 151 would mark everything for destruction, which is normally not what is desired. 152 If you would like to destroy everything, please run plan with the "-destroy" 153 flag or create a single empty configuration file. Otherwise, please create 154 a Terraform configuration file in the path being executed and try again. 155 ` 156 157 const planHeaderNoOutput = ` 158 The Terraform execution plan has been generated and is shown below. 159 Resources are shown in alphabetical order for quick scanning. Green resources 160 will be created (or destroyed and then created if an existing resource 161 exists), yellow resources are being changed in-place, and red resources 162 will be destroyed. Cyan entries are data sources to be read. 163 164 Note: You didn't specify an "-out" parameter to save this plan, so when 165 "apply" is called, Terraform can't guarantee this is what will execute. 166 ` 167 168 const planHeaderYesOutput = ` 169 The Terraform execution plan has been generated and is shown below. 170 Resources are shown in alphabetical order for quick scanning. Green resources 171 will be created (or destroyed and then created if an existing resource 172 exists), yellow resources are being changed in-place, and red resources 173 will be destroyed. Cyan entries are data sources to be read. 174 175 Your plan was also saved to the path below. Call the "apply" subcommand 176 with this plan file and Terraform will exactly execute this execution 177 plan. 178 179 Path: %s 180 ` 181 182 const planNoChanges = ` 183 [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green] 184 185 This means that Terraform did not detect any differences between your 186 configuration and real physical resources that exist. As a result, Terraform 187 doesn't need to do anything. 188 ` 189 190 const planRefreshing = ` 191 [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset] 192 The refreshed state will be used to calculate this plan, but will not be 193 persisted to local or remote state storage. 194 `