github.com/ojiry/terraform@v0.8.2-0.20161218223921-e50cec712c4a/command/plan.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "strings" 8 9 "github.com/hashicorp/go-multierror" 10 "github.com/hashicorp/terraform/terraform" 11 ) 12 13 // PlanCommand is a Command implementation that compares a Terraform 14 // configuration to an actual infrastructure and shows the differences. 15 type PlanCommand struct { 16 Meta 17 } 18 19 func (c *PlanCommand) Run(args []string) int { 20 var destroy, refresh, detailed bool 21 var outPath string 22 var moduleDepth int 23 24 args = c.Meta.process(args, true) 25 26 cmdFlags := c.Meta.flagSet("plan") 27 cmdFlags.BoolVar(&destroy, "destroy", false, "destroy") 28 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 29 c.addModuleDepthFlag(cmdFlags, &moduleDepth) 30 cmdFlags.StringVar(&outPath, "out", "", "path") 31 cmdFlags.IntVar( 32 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 33 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 34 cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode") 35 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 36 if err := cmdFlags.Parse(args); err != nil { 37 return 1 38 } 39 40 var path string 41 args = cmdFlags.Args() 42 if len(args) > 1 { 43 c.Ui.Error( 44 "The plan command expects at most one argument with the path\n" + 45 "to a Terraform configuration.\n") 46 cmdFlags.Usage() 47 return 1 48 } else if len(args) == 1 { 49 path = args[0] 50 } else { 51 var err error 52 path, err = os.Getwd() 53 if err != nil { 54 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 55 } 56 } 57 58 countHook := new(CountHook) 59 c.Meta.extraHooks = []terraform.Hook{countHook} 60 61 // This is going to keep track of shadow errors 62 var shadowErr error 63 64 ctx, planned, err := c.Context(contextOpts{ 65 Destroy: destroy, 66 Path: path, 67 StatePath: c.Meta.statePath, 68 Parallelism: c.Meta.parallelism, 69 }) 70 if err != nil { 71 c.Ui.Error(err.Error()) 72 return 1 73 } 74 if planned { 75 c.Ui.Output(c.Colorize().Color( 76 "[reset][bold][yellow]" + 77 "The plan command received a saved plan file as input. This command\n" + 78 "will output the saved plan. This will not modify the already-existing\n" + 79 "plan. If you wish to generate a new plan, please pass in a configuration\n" + 80 "directory as an argument.\n\n")) 81 82 // Disable refreshing no matter what since we only want to show the plan 83 refresh = false 84 } 85 86 err = terraform.SetDebugInfo(DefaultDataDir) 87 if err != nil { 88 c.Ui.Error(err.Error()) 89 return 1 90 } 91 92 if err := ctx.Input(c.InputMode()); err != nil { 93 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 94 return 1 95 } 96 97 // Record any shadow errors for later 98 if err := ctx.ShadowError(); err != nil { 99 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 100 err, "input operation:")) 101 } 102 103 if !validateContext(ctx, c.Ui) { 104 return 1 105 } 106 107 if refresh { 108 c.Ui.Output("Refreshing Terraform state in-memory prior to plan...") 109 c.Ui.Output("The refreshed state will be used to calculate this plan, but") 110 c.Ui.Output("will not be persisted to local or remote state storage.\n") 111 _, err := ctx.Refresh() 112 if err != nil { 113 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 114 return 1 115 } 116 c.Ui.Output("") 117 } 118 119 plan, err := ctx.Plan() 120 if err != nil { 121 c.Ui.Error(fmt.Sprintf("Error running plan: %s", err)) 122 return 1 123 } 124 125 if outPath != "" { 126 log.Printf("[INFO] Writing plan output to: %s", outPath) 127 f, err := os.Create(outPath) 128 if err == nil { 129 defer f.Close() 130 err = terraform.WritePlan(plan, f) 131 } 132 if err != nil { 133 c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err)) 134 return 1 135 } 136 } 137 138 if plan.Diff.Empty() { 139 c.Ui.Output( 140 "No changes. Infrastructure is up-to-date. This means that Terraform\n" + 141 "could not detect any differences between your configuration and\n" + 142 "the real physical resources that exist. As a result, Terraform\n" + 143 "doesn't need to do anything.") 144 return 0 145 } 146 147 if outPath == "" { 148 c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 149 } else { 150 c.Ui.Output(fmt.Sprintf( 151 strings.TrimSpace(planHeaderYesOutput)+"\n", 152 outPath)) 153 } 154 155 c.Ui.Output(FormatPlan(&FormatPlanOpts{ 156 Plan: plan, 157 Color: c.Colorize(), 158 ModuleDepth: moduleDepth, 159 })) 160 161 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 162 "[reset][bold]Plan:[reset] "+ 163 "%d to add, %d to change, %d to destroy.", 164 countHook.ToAdd+countHook.ToRemoveAndAdd, 165 countHook.ToChange, 166 countHook.ToRemove+countHook.ToRemoveAndAdd))) 167 168 // Record any shadow errors for later 169 if err := ctx.ShadowError(); err != nil { 170 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 171 err, "plan operation:")) 172 } 173 174 // If we have an error in the shadow graph, let the user know. 175 c.outputShadowError(shadowErr, true) 176 177 if detailed { 178 return 2 179 } 180 return 0 181 } 182 183 func (c *PlanCommand) Help() string { 184 helpText := ` 185 Usage: terraform plan [options] [DIR-OR-PLAN] 186 187 Generates an execution plan for Terraform. 188 189 This execution plan can be reviewed prior to running apply to get a 190 sense for what Terraform will do. Optionally, the plan can be saved to 191 a Terraform plan file, and apply can take this plan file to execute 192 this plan exactly. 193 194 If a saved plan is passed as an argument, this command will output 195 the saved plan contents. It will not modify the given plan. 196 197 Options: 198 199 -destroy If set, a plan will be generated to destroy all resources 200 managed by the given configuration and state. 201 202 -detailed-exitcode Return detailed exit codes when the command exits. This 203 will change the meaning of exit codes to: 204 0 - Succeeded, diff is empty (no changes) 205 1 - Errored 206 2 - Succeeded, there is a diff 207 208 -input=true Ask for input for variables if not directly set. 209 210 -module-depth=n Specifies the depth of modules to show in the output. 211 This does not affect the plan itself, only the output 212 shown. By default, this is -1, which will expand all. 213 214 -no-color If specified, output won't contain any color. 215 216 -out=path Write a plan file to the given path. This can be used as 217 input to the "apply" command. 218 219 -parallelism=n Limit the number of concurrent operations. Defaults to 10. 220 221 -refresh=true Update state prior to checking for differences. 222 223 -state=statefile Path to a Terraform state file to use to look 224 up Terraform-managed resources. By default it will 225 use the state "terraform.tfstate" if it exists. 226 227 -target=resource Resource to target. Operation will be limited to this 228 resource and its dependencies. This flag can be used 229 multiple times. 230 231 -var 'foo=bar' Set a variable in the Terraform configuration. This 232 flag can be set multiple times. 233 234 -var-file=foo Set variables in the Terraform configuration from 235 a file. If "terraform.tfvars" is present, it will be 236 automatically loaded if this flag is not specified. 237 ` 238 return strings.TrimSpace(helpText) 239 } 240 241 func (c *PlanCommand) Synopsis() string { 242 return "Generate and show an execution plan" 243 } 244 245 const planHeaderNoOutput = ` 246 The Terraform execution plan has been generated and is shown below. 247 Resources are shown in alphabetical order for quick scanning. Green resources 248 will be created (or destroyed and then created if an existing resource 249 exists), yellow resources are being changed in-place, and red resources 250 will be destroyed. Cyan entries are data sources to be read. 251 252 Note: You didn't specify an "-out" parameter to save this plan, so when 253 "apply" is called, Terraform can't guarantee this is what will execute. 254 ` 255 256 const planHeaderYesOutput = ` 257 The Terraform execution plan has been generated and is shown below. 258 Resources are shown in alphabetical order for quick scanning. Green resources 259 will be created (or destroyed and then created if an existing resource 260 exists), yellow resources are being changed in-place, and red resources 261 will be destroyed. Cyan entries are data sources to be read. 262 263 Your plan was also saved to the path below. Call the "apply" subcommand 264 with this plan file and Terraform will exactly execute this execution 265 plan. 266 267 Path: %s 268 `