github.com/mikesimons/terraform@v0.6.13-0.20160304043642-f11448c69214/command/plan.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "log" 6 "os" 7 "strings" 8 9 "github.com/hashicorp/terraform/terraform" 10 ) 11 12 // PlanCommand is a Command implementation that compares a Terraform 13 // configuration to an actual infrastructure and shows the differences. 14 type PlanCommand struct { 15 Meta 16 } 17 18 func (c *PlanCommand) Run(args []string) int { 19 var destroy, refresh, detailed bool 20 var outPath string 21 var moduleDepth int 22 23 args = c.Meta.process(args, true) 24 25 cmdFlags := c.Meta.flagSet("plan") 26 cmdFlags.BoolVar(&destroy, "destroy", false, "destroy") 27 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 28 c.addModuleDepthFlag(cmdFlags, &moduleDepth) 29 cmdFlags.StringVar(&outPath, "out", "", "path") 30 cmdFlags.IntVar( 31 &c.Meta.parallelism, "parallelism", DefaultParallelism, "parallelism") 32 cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") 33 cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "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 ctx, _, err := c.Context(contextOpts{ 62 Destroy: destroy, 63 Path: path, 64 StatePath: c.Meta.statePath, 65 Parallelism: c.Meta.parallelism, 66 }) 67 if err != nil { 68 c.Ui.Error(err.Error()) 69 return 1 70 } 71 72 if err := ctx.Input(c.InputMode()); err != nil { 73 c.Ui.Error(fmt.Sprintf("Error configuring: %s", err)) 74 return 1 75 } 76 77 if !validateContext(ctx, c.Ui) { 78 return 1 79 } 80 81 if refresh { 82 c.Ui.Output("Refreshing Terraform state prior to plan...\n") 83 state, err := ctx.Refresh() 84 if err != nil { 85 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 86 return 1 87 } 88 c.Ui.Output("") 89 90 if state != nil { 91 log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath()) 92 if err := c.Meta.PersistState(state); err != nil { 93 c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) 94 return 1 95 } 96 } 97 } 98 99 plan, err := ctx.Plan() 100 if err != nil { 101 c.Ui.Error(fmt.Sprintf("Error running plan: %s", err)) 102 return 1 103 } 104 105 if outPath != "" { 106 log.Printf("[INFO] Writing plan output to: %s", outPath) 107 f, err := os.Create(outPath) 108 if err == nil { 109 defer f.Close() 110 err = terraform.WritePlan(plan, f) 111 } 112 if err != nil { 113 c.Ui.Error(fmt.Sprintf("Error writing plan file: %s", err)) 114 return 1 115 } 116 } 117 118 if plan.Diff.Empty() { 119 c.Ui.Output( 120 "No changes. Infrastructure is up-to-date. This means that Terraform\n" + 121 "could not detect any differences between your configuration and\n" + 122 "the real physical resources that exist. As a result, Terraform\n" + 123 "doesn't need to do anything.") 124 return 0 125 } 126 127 if outPath == "" { 128 c.Ui.Output(strings.TrimSpace(planHeaderNoOutput) + "\n") 129 } else { 130 c.Ui.Output(fmt.Sprintf( 131 strings.TrimSpace(planHeaderYesOutput)+"\n", 132 outPath)) 133 } 134 135 c.Ui.Output(FormatPlan(&FormatPlanOpts{ 136 Plan: plan, 137 Color: c.Colorize(), 138 ModuleDepth: moduleDepth, 139 })) 140 141 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 142 "[reset][bold]Plan:[reset] "+ 143 "%d to add, %d to change, %d to destroy.", 144 countHook.ToAdd+countHook.ToRemoveAndAdd, 145 countHook.ToChange, 146 countHook.ToRemove+countHook.ToRemoveAndAdd))) 147 148 if detailed { 149 return 2 150 } 151 return 0 152 } 153 154 func (c *PlanCommand) Help() string { 155 helpText := ` 156 Usage: terraform plan [options] [dir] 157 158 Generates an execution plan for Terraform. 159 160 This execution plan can be reviewed prior to running apply to get a 161 sense for what Terraform will do. Optionally, the plan can be saved to 162 a Terraform plan file, and apply can take this plan file to execute 163 this plan exactly. 164 165 Options: 166 167 -backup=path Path to backup the existing state file before 168 modifying. Defaults to the "-state-out" path with 169 ".backup" extension. Set to "-" to disable backup. 170 171 -destroy If set, a plan will be generated to destroy all resources 172 managed by the given configuration and state. 173 174 -detailed-exitcode Return detailed exit codes when the command exits. This 175 will change the meaning of exit codes to: 176 0 - Succeeded, diff is empty (no changes) 177 1 - Errored 178 2 - Succeeded, there is a diff 179 180 -input=true Ask for input for variables if not directly set. 181 182 -module-depth=n Specifies the depth of modules to show in the output. 183 This does not affect the plan itself, only the output 184 shown. By default, this is -1, which will expand all. 185 186 -no-color If specified, output won't contain any color. 187 188 -out=path Write a plan file to the given path. This can be used as 189 input to the "apply" command. 190 191 -parallelism=n Limit the number of concurrent operations. Defaults to 10. 192 193 -refresh=true Update state prior to checking for differences. 194 195 -state=statefile Path to a Terraform state file to use to look 196 up Terraform-managed resources. By default it will 197 use the state "terraform.tfstate" if it exists. 198 199 -target=resource Resource to target. Operation will be limited to this 200 resource and its dependencies. This flag can be used 201 multiple times. 202 203 -var 'foo=bar' Set a variable in the Terraform configuration. This 204 flag can be set multiple times. 205 206 -var-file=foo Set variables in the Terraform configuration from 207 a file. If "terraform.tfvars" is present, it will be 208 automatically loaded if this flag is not specified. 209 ` 210 return strings.TrimSpace(helpText) 211 } 212 213 func (c *PlanCommand) Synopsis() string { 214 return "Generate and show an execution plan" 215 } 216 217 const planHeaderNoOutput = ` 218 The Terraform execution plan has been generated and is shown below. 219 Resources are shown in alphabetical order for quick scanning. Green resources 220 will be created (or destroyed and then created if an existing resource 221 exists), yellow resources are being changed in-place, and red resources 222 will be destroyed. 223 224 Note: You didn't specify an "-out" parameter to save this plan, so when 225 "apply" is called, Terraform can't guarantee this is what will execute. 226 ` 227 228 const planHeaderYesOutput = ` 229 The Terraform execution plan has been generated and is shown below. 230 Resources are shown in alphabetical order for quick scanning. Green resources 231 will be created (or destroyed and then created if an existing resource 232 exists), yellow resources are being changed in-place, and red resources 233 will be destroyed. 234 235 Your plan was also saved to the path below. Call the "apply" subcommand 236 with this plan file and Terraform will exactly execute this execution 237 plan. 238 239 Path: %s 240 `