github.com/mkuzmin/terraform@v0.3.7-0.20161118171027-ec4c00ff92a9/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, _, 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 75 err = terraform.SetDebugInfo(DefaultDataDir) 76 if err != nil { 77 c.Ui.Error(err.Error()) 78 return 1 79 } 80 81 if err := ctx.Input(c.InputMode()); err != nil { 82 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 83 return 1 84 } 85 86 // Record any shadow errors for later 87 if err := ctx.ShadowError(); err != nil { 88 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 89 err, "input operation:")) 90 } 91 92 if !validateContext(ctx, c.Ui) { 93 return 1 94 } 95 96 if refresh { 97 c.Ui.Output("Refreshing Terraform state in-memory prior to plan...") 98 c.Ui.Output("The refreshed state will be used to calculate this plan, but") 99 c.Ui.Output("will not be persisted to local or remote state storage.\n") 100 _, err := ctx.Refresh() 101 if err != nil { 102 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 103 return 1 104 } 105 c.Ui.Output("") 106 } 107 108 plan, err := ctx.Plan() 109 if err != nil { 110 c.Ui.Error(fmt.Sprintf("Error running plan: %s", err)) 111 return 1 112 } 113 114 if outPath != "" { 115 log.Printf("[INFO] Writing plan output to: %s", outPath) 116 f, err := os.Create(outPath) 117 if err == nil { 118 defer f.Close() 119 err = terraform.WritePlan(plan, f) 120 } 121 if err != nil { 122 c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err)) 123 return 1 124 } 125 } 126 127 if plan.Diff.Empty() { 128 c.Ui.Output( 129 "No changes. Infrastructure is up-to-date. This means that Terraform\n" + 130 "could not detect any differences between your configuration and\n" + 131 "the real physical resources that exist. As a result, Terraform\n" + 132 "doesn't need to do anything.") 133 return 0 134 } 135 136 if outPath == "" { 137 c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 138 } else { 139 c.Ui.Output(fmt.Sprintf( 140 strings.TrimSpace(planHeaderYesOutput)+"\n", 141 outPath)) 142 } 143 144 c.Ui.Output(FormatPlan(&FormatPlanOpts{ 145 Plan: plan, 146 Color: c.Colorize(), 147 ModuleDepth: moduleDepth, 148 })) 149 150 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 151 "[reset][bold]Plan:[reset] "+ 152 "%d to add, %d to change, %d to destroy.", 153 countHook.ToAdd+countHook.ToRemoveAndAdd, 154 countHook.ToChange, 155 countHook.ToRemove+countHook.ToRemoveAndAdd))) 156 157 // Record any shadow errors for later 158 if err := ctx.ShadowError(); err != nil { 159 shadowErr = multierror.Append(shadowErr, multierror.Prefix( 160 err, "plan operation:")) 161 } 162 163 // If we have an error in the shadow graph, let the user know. 164 c.outputShadowError(shadowErr, true) 165 166 if detailed { 167 return 2 168 } 169 return 0 170 } 171 172 func (c *PlanCommand) Help() string { 173 helpText := ` 174 Usage: terraform plan [options] [dir] 175 176 Generates an execution plan for Terraform. 177 178 This execution plan can be reviewed prior to running apply to get a 179 sense for what Terraform will do. Optionally, the plan can be saved to 180 a Terraform plan file, and apply can take this plan file to execute 181 this plan exactly. 182 183 Options: 184 185 -destroy If set, a plan will be generated to destroy all resources 186 managed by the given configuration and state. 187 188 -detailed-exitcode Return detailed exit codes when the command exits. This 189 will change the meaning of exit codes to: 190 0 - Succeeded, diff is empty (no changes) 191 1 - Errored 192 2 - Succeeded, there is a diff 193 194 -input=true Ask for input for variables if not directly set. 195 196 -module-depth=n Specifies the depth of modules to show in the output. 197 This does not affect the plan itself, only the output 198 shown. By default, this is -1, which will expand all. 199 200 -no-color If specified, output won't contain any color. 201 202 -out=path Write a plan file to the given path. This can be used as 203 input to the "apply" command. 204 205 -parallelism=n Limit the number of concurrent operations. Defaults to 10. 206 207 -refresh=true Update state prior to checking for differences. 208 209 -state=statefile Path to a Terraform state file to use to look 210 up Terraform-managed resources. By default it will 211 use the state "terraform.tfstate" if it exists. 212 213 -target=resource Resource to target. Operation will be limited to this 214 resource and its dependencies. This flag can be used 215 multiple times. 216 217 -var 'foo=bar' Set a variable in the Terraform configuration. This 218 flag can be set multiple times. 219 220 -var-file=foo Set variables in the Terraform configuration from 221 a file. If "terraform.tfvars" is present, it will be 222 automatically loaded if this flag is not specified. 223 ` 224 return strings.TrimSpace(helpText) 225 } 226 227 func (c *PlanCommand) Synopsis() string { 228 return "Generate and show an execution plan" 229 } 230 231 const planHeaderNoOutput = ` 232 The Terraform execution plan has been generated and is shown below. 233 Resources are shown in alphabetical order for quick scanning. Green resources 234 will be created (or destroyed and then created if an existing resource 235 exists), yellow resources are being changed in-place, and red resources 236 will be destroyed. Cyan entries are data sources to be read. 237 238 Note: You didn't specify an "-out" parameter to save this plan, so when 239 "apply" is called, Terraform can't guarantee this is what will execute. 240 ` 241 242 const planHeaderYesOutput = ` 243 The Terraform execution plan has been generated and is shown below. 244 Resources are shown in alphabetical order for quick scanning. Green resources 245 will be created (or destroyed and then created if an existing resource 246 exists), yellow resources are being changed in-place, and red resources 247 will be destroyed. Cyan entries are data sources to be read. 248 249 Your plan was also saved to the path below. Call the "apply" subcommand 250 with this plan file and Terraform will exactly execute this execution 251 plan. 252 253 Path: %s 254 `