github.com/dougneal/terraform@v0.6.15-0.20170330092735-b6a3840768a4/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/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 lockInfo := state.NewLockInfo() 65 lockInfo.Operation = op.Type.String() 66 lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize()) 67 if err != nil { 68 runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) 69 return 70 } 71 72 defer func() { 73 if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil { 74 runningOp.Err = multierror.Append(runningOp.Err, err) 75 } 76 }() 77 } 78 79 // Setup the state 80 runningOp.State = tfCtx.State() 81 82 // If we're refreshing before plan, perform that 83 if op.PlanRefresh { 84 log.Printf("[INFO] backend/local: plan calling Refresh") 85 86 if b.CLI != nil { 87 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planRefreshing) + "\n")) 88 } 89 90 _, err := tfCtx.Refresh() 91 if err != nil { 92 runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err) 93 return 94 } 95 } 96 97 // Perform the plan 98 log.Printf("[INFO] backend/local: plan calling Plan") 99 plan, err := tfCtx.Plan() 100 if err != nil { 101 runningOp.Err = errwrap.Wrapf("Error running plan: {{err}}", err) 102 return 103 } 104 105 // Record state 106 runningOp.PlanEmpty = plan.Diff.Empty() 107 108 // Save the plan to disk 109 if path := op.PlanOutPath; path != "" { 110 // Write the backend if we have one 111 plan.Backend = op.PlanOutBackend 112 113 // This works around a bug (#12871) which is no longer possible to 114 // trigger but will exist for already corrupted upgrades. 115 if plan.Backend != nil && plan.State != nil { 116 plan.State.Remote = nil 117 } 118 119 log.Printf("[INFO] backend/local: writing plan output to: %s", path) 120 f, err := os.Create(path) 121 if err == nil { 122 err = terraform.WritePlan(plan, f) 123 } 124 f.Close() 125 if err != nil { 126 runningOp.Err = fmt.Errorf("Error writing plan file: %s", err) 127 return 128 } 129 } 130 131 // Perform some output tasks if we have a CLI to output to. 132 if b.CLI != nil { 133 if plan.Diff.Empty() { 134 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges))) 135 return 136 } 137 138 if path := op.PlanOutPath; path == "" { 139 b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 140 } else { 141 b.CLI.Output(fmt.Sprintf( 142 strings.TrimSpace(planHeaderYesOutput)+"\n", 143 path)) 144 } 145 146 b.CLI.Output(format.Plan(&format.PlanOpts{ 147 Plan: plan, 148 Color: b.Colorize(), 149 ModuleDepth: -1, 150 })) 151 152 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 153 "[reset][bold]Plan:[reset] "+ 154 "%d to add, %d to change, %d to destroy.", 155 countHook.ToAdd+countHook.ToRemoveAndAdd, 156 countHook.ToChange, 157 countHook.ToRemove+countHook.ToRemoveAndAdd))) 158 } 159 } 160 161 const planErrNoConfig = ` 162 No configuration files found! 163 164 Plan requires configuration to be present. Planning without a configuration 165 would mark everything for destruction, which is normally not what is desired. 166 If you would like to destroy everything, please run plan with the "-destroy" 167 flag or create a single empty configuration file. Otherwise, please create 168 a Terraform configuration file in the path being executed and try again. 169 ` 170 171 const planHeaderNoOutput = ` 172 The Terraform execution plan has been generated and is shown below. 173 Resources are shown in alphabetical order for quick scanning. Green resources 174 will be created (or destroyed and then created if an existing resource 175 exists), yellow resources are being changed in-place, and red resources 176 will be destroyed. Cyan entries are data sources to be read. 177 178 Note: You didn't specify an "-out" parameter to save this plan, so when 179 "apply" is called, Terraform can't guarantee this is what will execute. 180 ` 181 182 const planHeaderYesOutput = ` 183 The Terraform execution plan has been generated and is shown below. 184 Resources are shown in alphabetical order for quick scanning. Green resources 185 will be created (or destroyed and then created if an existing resource 186 exists), yellow resources are being changed in-place, and red resources 187 will be destroyed. Cyan entries are data sources to be read. 188 189 Your plan was also saved to the path below. Call the "apply" subcommand 190 with this plan file and Terraform will exactly execute this execution 191 plan. 192 193 Path: %s 194 ` 195 196 const planNoChanges = ` 197 [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green] 198 199 This means that Terraform did not detect any differences between your 200 configuration and real physical resources that exist. As a result, Terraform 201 doesn't need to do anything. 202 ` 203 204 const planRefreshing = ` 205 [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset] 206 The refreshed state will be used to calculate this plan, but will not be 207 persisted to local or remote state storage. 208 `