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