github.com/sathish1597/hashicorp-terraform@v0.11.12-beta1/backend/local/backend_plan.go (about) 1 package local 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "log" 8 "os" 9 "strings" 10 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/terraform/backend" 13 "github.com/hashicorp/terraform/command/format" 14 "github.com/hashicorp/terraform/config/module" 15 "github.com/hashicorp/terraform/terraform" 16 ) 17 18 func (b *Local) opPlan( 19 stopCtx context.Context, 20 cancelCtx 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 // Setup the state 63 runningOp.State = tfCtx.State() 64 65 // If we're refreshing before plan, perform that 66 if op.PlanRefresh { 67 log.Printf("[INFO] backend/local: plan calling Refresh") 68 69 if b.CLI != nil { 70 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n")) 71 } 72 73 _, err := tfCtx.Refresh() 74 if err != nil { 75 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 76 return 77 } 78 if b.CLI != nil { 79 b.CLI.Output("\n------------------------------------------------------------------------") 80 } 81 } 82 83 // Perform the plan in a goroutine so we can be interrupted 84 var plan *terraform.Plan 85 var planErr error 86 doneCh := make(chan struct{}) 87 go func() { 88 defer close(doneCh) 89 log.Printf("[INFO] backend/local: plan calling Plan") 90 plan, planErr = tfCtx.Plan() 91 }() 92 93 if b.opWait(doneCh, stopCtx, cancelCtx, tfCtx, opState) { 94 return 95 } 96 97 if planErr != nil { 98 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", planErr) 99 return 100 } 101 // Record state 102 runningOp.PlanEmpty = plan.Diff.Empty() 103 104 // Save the plan to disk 105 if path := op.PlanOutPath; path != "" { 106 // Write the backend if we have one 107 plan.Backend = op.PlanOutBackend 108 109 // This works around a bug (#12871) which is no longer possible to 110 // trigger but will exist for already corrupted upgrades. 111 if plan.Backend != nil && plan.State != nil { 112 plan.State.Remote = nil 113 } 114 115 log.Printf("[INFO] backend/local: writing plan output to: %s", path) 116 f, err := os.Create(path) 117 if err == nil { 118 err = terraform.WritePlan(plan, f) 119 } 120 f.Close() 121 if err != nil { 122 runningOp.Err = fmt.Errorf("Error writing plan file: %s", err) 123 return 124 } 125 } 126 127 // Perform some output tasks if we have a CLI to output to. 128 if b.CLI != nil { 129 dispPlan := format.NewPlan(plan) 130 if dispPlan.Empty() { 131 b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges))) 132 return 133 } 134 135 b.renderPlan(dispPlan) 136 137 // Give the user some next-steps, unless we're running in an automation 138 // tool which is presumed to provide its own UI for further actions. 139 if !b.RunningInAutomation { 140 141 b.CLI.Output("\n------------------------------------------------------------------------") 142 143 if path := op.PlanOutPath; path == "" { 144 b.CLI.Output(fmt.Sprintf( 145 "\n" + strings.TrimSpace(planHeaderNoOutput) + "\n", 146 )) 147 } else { 148 b.CLI.Output(fmt.Sprintf( 149 "\n"+strings.TrimSpace(planHeaderYesOutput)+"\n", 150 path, path, 151 )) 152 } 153 154 } 155 } 156 } 157 158 func (b *Local) renderPlan(dispPlan *format.Plan) { 159 160 headerBuf := &bytes.Buffer{} 161 fmt.Fprintf(headerBuf, "\n%s\n", strings.TrimSpace(planHeaderIntro)) 162 counts := dispPlan.ActionCounts() 163 if counts[terraform.DiffCreate] > 0 { 164 fmt.Fprintf(headerBuf, "%s create\n", format.DiffActionSymbol(terraform.DiffCreate)) 165 } 166 if counts[terraform.DiffUpdate] > 0 { 167 fmt.Fprintf(headerBuf, "%s update in-place\n", format.DiffActionSymbol(terraform.DiffUpdate)) 168 } 169 if counts[terraform.DiffDestroy] > 0 { 170 fmt.Fprintf(headerBuf, "%s destroy\n", format.DiffActionSymbol(terraform.DiffDestroy)) 171 } 172 if counts[terraform.DiffDestroyCreate] > 0 { 173 fmt.Fprintf(headerBuf, "%s destroy and then create replacement\n", format.DiffActionSymbol(terraform.DiffDestroyCreate)) 174 } 175 if counts[terraform.DiffRefresh] > 0 { 176 fmt.Fprintf(headerBuf, "%s read (data resources)\n", format.DiffActionSymbol(terraform.DiffRefresh)) 177 } 178 179 b.CLI.Output(b.Colorize().Color(headerBuf.String())) 180 181 b.CLI.Output("Terraform will perform the following actions:\n") 182 183 b.CLI.Output(dispPlan.Format(b.Colorize())) 184 185 stats := dispPlan.Stats() 186 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 187 "[reset][bold]Plan:[reset] "+ 188 "%d to add, %d to change, %d to destroy.", 189 stats.ToAdd, stats.ToChange, stats.ToDestroy, 190 ))) 191 } 192 193 const planErrNoConfig = ` 194 No configuration files found! 195 196 Plan requires configuration to be present. Planning without a configuration 197 would mark everything for destruction, which is normally not what is desired. 198 If you would like to destroy everything, please run plan with the "-destroy" 199 flag or create a single empty configuration file. Otherwise, please create 200 a Terraform configuration file in the path being executed and try again. 201 ` 202 203 const planHeaderIntro = ` 204 An execution plan has been generated and is shown below. 205 Resource actions are indicated with the following symbols: 206 ` 207 208 const planHeaderNoOutput = ` 209 Note: You didn't specify an "-out" parameter to save this plan, so Terraform 210 can't guarantee that exactly these actions will be performed if 211 "terraform apply" is subsequently run. 212 ` 213 214 const planHeaderYesOutput = ` 215 This plan was saved to: %s 216 217 To perform exactly these actions, run the following command to apply: 218 terraform apply %q 219 ` 220 221 const planNoChanges = ` 222 [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green] 223 224 This means that Terraform did not detect any differences between your 225 configuration and real physical resources that exist. As a result, no 226 actions need to be performed. 227 ` 228 229 const planRefreshing = ` 230 [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset] 231 The refreshed state will be used to calculate this plan, but will not be 232 persisted to local or remote state storage. 233 `