github.com/ezbercih/terraform@v0.1.1-0.20140729011846-3c33865e0839/command/apply.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "os" 8 "sort" 9 "strings" 10 11 "github.com/hashicorp/terraform/terraform" 12 ) 13 14 // ApplyCommand is a Command implementation that applies a Terraform 15 // configuration and actually builds or changes infrastructure. 16 type ApplyCommand struct { 17 Meta 18 19 ShutdownCh <-chan struct{} 20 } 21 22 func (c *ApplyCommand) Run(args []string) int { 23 var refresh bool 24 var statePath, stateOutPath, backupPath string 25 26 args = c.Meta.process(args) 27 28 cmdFlags := c.Meta.flagSet("apply") 29 cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") 30 cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") 31 cmdFlags.StringVar(&stateOutPath, "state-out", "", "path") 32 cmdFlags.StringVar(&backupPath, "backup", "", "path") 33 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 34 if err := cmdFlags.Parse(args); err != nil { 35 return 1 36 } 37 38 var configPath string 39 args = cmdFlags.Args() 40 if len(args) > 1 { 41 c.Ui.Error("The apply command expacts at most one argument.") 42 cmdFlags.Usage() 43 return 1 44 } else if len(args) == 1 { 45 configPath = args[0] 46 } else { 47 var err error 48 configPath, err = os.Getwd() 49 if err != nil { 50 c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err)) 51 } 52 } 53 54 // Prepare the extra hooks to count resources 55 countHook := new(CountHook) 56 c.Meta.extraHooks = []terraform.Hook{countHook} 57 58 // If we don't specify an output path, default to out normal state 59 // path. 60 if stateOutPath == "" { 61 stateOutPath = statePath 62 } 63 64 // If we don't specify a backup path, default to state out with 65 // the extention 66 if backupPath == "" { 67 backupPath = stateOutPath + DefaultBackupExtention 68 } 69 70 // Build the context based on the arguments given 71 ctx, planned, err := c.Context(configPath, statePath) 72 if err != nil { 73 c.Ui.Error(err.Error()) 74 return 1 75 } 76 if !validateContext(ctx, c.Ui) { 77 return 1 78 } 79 80 // Create a backup of the state before updating 81 if backupPath != "-" && c.state != nil { 82 log.Printf("[INFO] Writing backup state to: %s", backupPath) 83 f, err := os.Create(backupPath) 84 if err == nil { 85 err = terraform.WriteState(c.state, f) 86 f.Close() 87 } 88 if err != nil { 89 c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) 90 return 1 91 } 92 } 93 94 // Plan if we haven't already 95 if !planned { 96 if refresh { 97 if _, err := ctx.Refresh(); err != nil { 98 c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) 99 return 1 100 } 101 } 102 103 if _, err := ctx.Plan(nil); err != nil { 104 c.Ui.Error(fmt.Sprintf( 105 "Error creating plan: %s", err)) 106 return 1 107 } 108 } 109 110 // Start the apply in a goroutine so that we can be interrupted. 111 var state *terraform.State 112 var applyErr error 113 doneCh := make(chan struct{}) 114 go func() { 115 defer close(doneCh) 116 state, applyErr = ctx.Apply() 117 }() 118 119 // Wait for the apply to finish or for us to be interrupted so 120 // we can handle it properly. 121 err = nil 122 select { 123 case <-c.ShutdownCh: 124 c.Ui.Output("Interrupt received. Gracefully shutting down...") 125 126 // Stop execution 127 ctx.Stop() 128 129 // Still get the result, since there is still one 130 select { 131 case <-c.ShutdownCh: 132 c.Ui.Error( 133 "Two interrupts received. Exiting immediately. Note that data\n" + 134 "loss may have occurred.") 135 return 1 136 case <-doneCh: 137 } 138 case <-doneCh: 139 } 140 141 if state != nil { 142 // Write state out to the file 143 f, err := os.Create(stateOutPath) 144 if err == nil { 145 err = terraform.WriteState(state, f) 146 f.Close() 147 } 148 if err != nil { 149 c.Ui.Error(fmt.Sprintf("Failed to save state: %s", err)) 150 return 1 151 } 152 } 153 154 if applyErr != nil { 155 c.Ui.Error(fmt.Sprintf( 156 "Error applying plan:\n\n"+ 157 "%s\n\n"+ 158 "Terraform does not automatically rollback in the face of errors.\n"+ 159 "Instead, your Terraform state file has been partially updated with\n"+ 160 "any resources that successfully completed. Please address the error\n"+ 161 "above and apply again to incrementally change your infrastructure.", 162 applyErr)) 163 return 1 164 } 165 166 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 167 "[reset][bold][green]\n"+ 168 "Apply complete! Resources: %d added, %d changed, %d destroyed.", 169 countHook.Added, 170 countHook.Changed, 171 countHook.Removed))) 172 173 if countHook.Added > 0 || countHook.Changed > 0 { 174 c.Ui.Output(c.Colorize().Color(fmt.Sprintf( 175 "[reset]\n"+ 176 "The state of your infrastructure has been saved to the path\n"+ 177 "below. This state is required to modify and destroy your\n"+ 178 "infrastructure, so keep it safe. To inspect the complete state\n"+ 179 "use the `terraform show` command.\n\n"+ 180 "State path: %s", 181 stateOutPath))) 182 } 183 184 // If we have outputs, then output those at the end. 185 if state != nil && len(state.Outputs) > 0 { 186 outputBuf := new(bytes.Buffer) 187 outputBuf.WriteString("[reset][bold][green]\nOutputs:\n\n") 188 189 // Output the outputs in alphabetical order 190 keyLen := 0 191 keys := make([]string, 0, len(state.Outputs)) 192 for key, _ := range state.Outputs { 193 keys = append(keys, key) 194 if len(key) > keyLen { 195 keyLen = len(key) 196 } 197 } 198 sort.Strings(keys) 199 200 for _, k := range keys { 201 v := state.Outputs[k] 202 203 outputBuf.WriteString(fmt.Sprintf( 204 " %s%s = %s\n", 205 k, 206 strings.Repeat(" ", keyLen-len(k)), 207 v)) 208 } 209 210 c.Ui.Output(c.Colorize().Color( 211 strings.TrimSpace(outputBuf.String()))) 212 } 213 214 return 0 215 } 216 217 func (c *ApplyCommand) Help() string { 218 helpText := ` 219 Usage: terraform apply [options] [dir] 220 221 Builds or changes infrastructure according to Terraform configuration 222 files . 223 224 Options: 225 226 -backup=path Path to backup the existing state file before 227 modifying. Defaults to the "-state-out" path with 228 ".backup" extention. Set to "-" to disable backup. 229 230 -no-color If specified, output won't contain any color. 231 232 -refresh=true Update state prior to checking for differences. This 233 has no effect if a plan file is given to apply. 234 235 -state=path Path to read and save state (unless state-out 236 is specified). Defaults to "terraform.tfstate". 237 238 -state-out=path Path to write state to that is different than 239 "-state". This can be used to preserve the old 240 state. 241 242 -var 'foo=bar' Set a variable in the Terraform configuration. This 243 flag can be set multiple times. 244 245 -var-file=foo Set variables in the Terraform configuration from 246 a file. If "terraform.tfvars" is present, it will be 247 automatically loaded if this flag is not specified. 248 249 250 ` 251 return strings.TrimSpace(helpText) 252 } 253 254 func (c *ApplyCommand) Synopsis() string { 255 return "Builds or changes infrastructure" 256 }