github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 log.Printf("[INFO] backend/local: writing plan output to: %s", path) 114 f, err := os.Create(path) 115 if err == nil { 116 err = terraform.WritePlan(plan, f) 117 } 118 f.Close() 119 if err != nil { 120 runningOp.Err = fmt.Errorf("Error writing plan file: %s", err) 121 return 122 } 123 } 124 125 // Perform some output tasks if we have a CLI to output to. 126 if b.CLI != nil { 127 if plan.Diff.Empty() { 128 b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges))) 129 return 130 } 131 132 if path := op.PlanOutPath; path == "" { 133 b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 134 } else { 135 b.CLI.Output(fmt.Sprintf( 136 strings.TrimSpace(planHeaderYesOutput)+"\n", 137 path)) 138 } 139 140 b.CLI.Output(format.Plan(&format.PlanOpts{ 141 Plan: plan, 142 Color: b.Colorize(), 143 ModuleDepth: -1, 144 })) 145 146 b.CLI.Output(b.Colorize().Color(fmt.Sprintf( 147 "[reset][bold]Plan:[reset] "+ 148 "%d to add, %d to change, %d to destroy.", 149 countHook.ToAdd+countHook.ToRemoveAndAdd, 150 countHook.ToChange, 151 countHook.ToRemove+countHook.ToRemoveAndAdd))) 152 } 153 } 154 155 const planErrNoConfig = ` 156 No configuration files found! 157 158 Plan requires configuration to be present. Planning without a configuration 159 would mark everything for destruction, which is normally not what is desired. 160 If you would like to destroy everything, please run plan with the "-destroy" 161 flag or create a single empty configuration file. Otherwise, please create 162 a Terraform configuration file in the path being executed and try again. 163 ` 164 165 const planHeaderNoOutput = ` 166 The Terraform execution plan has been generated and is shown below. 167 Resources are shown in alphabetical order for quick scanning. Green resources 168 will be created (or destroyed and then created if an existing resource 169 exists), yellow resources are being changed in-place, and red resources 170 will be destroyed. Cyan entries are data sources to be read. 171 172 Note: You didn't specify an "-out" parameter to save this plan, so when 173 "apply" is called, Terraform can't guarantee this is what will execute. 174 ` 175 176 const planHeaderYesOutput = ` 177 The Terraform execution plan has been generated and is shown below. 178 Resources are shown in alphabetical order for quick scanning. Green resources 179 will be created (or destroyed and then created if an existing resource 180 exists), yellow resources are being changed in-place, and red resources 181 will be destroyed. Cyan entries are data sources to be read. 182 183 Your plan was also saved to the path below. Call the "apply" subcommand 184 with this plan file and Terraform will exactly execute this execution 185 plan. 186 187 Path: %s 188 ` 189 190 const planNoChanges = ` 191 [reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green] 192 193 This means that Terraform did not detect any differences between your 194 configuration and real physical resources that exist. As a result, Terraform 195 doesn't need to do anything. 196 ` 197 198 const planRefreshing = ` 199 [reset][bold]Refreshing Terraform state in-memory prior to plan...[reset] 200 The refreshed state will be used to calculate this plan, but will not be 201 persisted to local or remote state storage. 202 `