github.com/dahs81/otto@v0.2.1-0.20160126165905-6400716cf085/helper/vagrant/dev.go (about) 1 package vagrant 2 3 import ( 4 "flag" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/hashicorp/otto/app" 12 "github.com/hashicorp/otto/directory" 13 "github.com/hashicorp/otto/helper/router" 14 ) 15 16 // DevOptions is the configuration struct used for Dev. 17 type DevOptions struct { 18 // Dir is the path to the directory with the Vagrantfile. This 19 // will default to `#{ctx.Dir}/dev` if empty. 20 Dir string 21 22 // DataDir is the path to the directory where Vagrant should store its data. 23 // Defaults to `#{ctx.LocalDir/vagrant}` if empty. 24 DataDir string 25 26 // Layer, if non-nil, will be the set of layers that this environment 27 // builds on top of. If this is set, then the layers will be managed 28 // automatically by this. 29 // 30 // If this is nil, then layers won't be used. 31 Layer *Layered 32 33 // Instructions are help text that is shown after creating the 34 // development environment. 35 Instructions string 36 } 37 38 // Dev can be used as an implementation of app.App.Dev to automatically 39 // handle creating a development environment and forwarding commands down 40 // to Vagrant. 41 func Dev(opts *DevOptions) *router.Router { 42 return &router.Router{ 43 Actions: map[string]router.Action{ 44 "": &router.SimpleAction{ 45 ExecuteFunc: opts.actionUp, 46 SynopsisText: actionUpSyn, 47 HelpText: strings.TrimSpace(actionUpHelp), 48 }, 49 50 "address": &router.SimpleAction{ 51 ExecuteFunc: opts.actionAddress, 52 SynopsisText: actionAddressSyn, 53 HelpText: strings.TrimSpace(actionAddressHelp), 54 }, 55 56 "destroy": &router.SimpleAction{ 57 ExecuteFunc: opts.actionDestroy, 58 SynopsisText: actionDestroySyn, 59 HelpText: strings.TrimSpace(actionDestroyHelp), 60 }, 61 62 "halt": &router.SimpleAction{ 63 ExecuteFunc: opts.actionHalt, 64 SynopsisText: actionHaltSyn, 65 HelpText: strings.TrimSpace(actionHaltHelp), 66 }, 67 68 "layers": &router.SimpleAction{ 69 ExecuteFunc: opts.actionLayers, 70 SynopsisText: actionLayersSyn, 71 HelpText: strings.TrimSpace(actionLayersHelp), 72 }, 73 74 "ssh": &router.SimpleAction{ 75 ExecuteFunc: opts.actionSSH, 76 SynopsisText: actionSSHSyn, 77 HelpText: strings.TrimSpace(actionSSHHelp), 78 }, 79 80 "vagrant": &router.SimpleAction{ 81 ExecuteFunc: opts.actionRaw, 82 SynopsisText: actionVagrantSyn, 83 HelpText: strings.TrimSpace(actionVagrantHelp), 84 }, 85 }, 86 } 87 } 88 89 func (opts *DevOptions) Vagrant(ctx *app.Context) *Vagrant { 90 dir := opts.Dir 91 if dir == "" { 92 dir = filepath.Join(ctx.Dir, "dev") 93 } 94 dataDir := opts.DataDir 95 if dataDir == "" { 96 dataDir = filepath.Join(ctx.LocalDir, "vagrant") 97 } 98 result := &Vagrant{ 99 Dir: dir, 100 DataDir: dataDir, 101 Ui: ctx.Ui, 102 } 103 104 // If we have a layered environment we want to configure every environment 105 // with the layer information so that we can call arbitrary commands. 106 if opts.Layer != nil { 107 if err := opts.Layer.ConfigureEnv(result); err != nil { 108 // This shouldn't fail 109 panic(err) 110 } 111 } 112 113 return result 114 } 115 116 func (opts *DevOptions) actionAddress(rctx router.Context) error { 117 ctx := rctx.(*app.Context) 118 ctx.Ui.Raw(ctx.DevIPAddress + "\n") 119 return nil 120 } 121 122 func (opts *DevOptions) actionDestroy(rctx router.Context) error { 123 ctx := rctx.(*app.Context) 124 project := Project(&ctx.Shared) 125 if err := project.InstallIfNeeded(); err != nil { 126 return err 127 } 128 129 ctx.Ui.Header("Destroying the local development environment...") 130 vagrant := opts.Vagrant(ctx) 131 132 // If the Vagrant directory doesn't exist, then we're already deleted. 133 // So we just verify here that it exists and then call destroy only 134 // if it does. 135 log.Printf("[DEBUG] vagrant: verifying data dir exists: %s", vagrant.DataDir) 136 _, err := os.Stat(vagrant.DataDir) 137 if err != nil && !os.IsNotExist(err) { 138 log.Printf("[ERROR] vagrant: err: %s", err) 139 return err 140 } 141 if err == nil { 142 if err := vagrant.Execute("destroy", "-f"); err != nil { 143 return err 144 } 145 ctx.Ui.Raw("\n") 146 } 147 148 // Store the dev status into the directory. We just do this before 149 // since there are a lot of cases where Vagrant fails but still imported. 150 // We just override any prior dev. 151 ctx.Ui.Header("Deleting development environment metadata...") 152 if opts.Layer != nil { 153 if err := opts.Layer.RemoveEnv(vagrant); err != nil { 154 return fmt.Errorf( 155 "Error preparing dev environment: %s", err) 156 } 157 } 158 159 if err := ctx.Directory.DeleteDev(opts.devLookup(ctx)); err != nil { 160 return fmt.Errorf( 161 "Error deleting dev environment metadata: %s", err) 162 } 163 164 if err := opts.sshCache(ctx).Delete(); err != nil { 165 return fmt.Errorf( 166 "Error cleaning SSH cache: %s", err) 167 } 168 169 ctx.Ui.Header("[green]Development environment has been destroyed!") 170 return nil 171 } 172 173 func (opts *DevOptions) actionHalt(rctx router.Context) error { 174 ctx := rctx.(*app.Context) 175 project := Project(&ctx.Shared) 176 if err := project.InstallIfNeeded(); err != nil { 177 return err 178 } 179 180 ctx.Ui.Header("Halting the the local development environment...") 181 182 if err := opts.Vagrant(ctx).Execute("halt"); err != nil { 183 return err 184 } 185 186 ctx.Ui.Header("[green]Development environment halted!") 187 188 return nil 189 } 190 191 func (opts *DevOptions) actionRaw(rctx router.Context) error { 192 ctx := rctx.(*app.Context) 193 project := Project(&ctx.Shared) 194 if err := project.InstallIfNeeded(); err != nil { 195 return err 196 } 197 198 ctx.Ui.Header(fmt.Sprintf( 199 "Executing: 'vagrant %s'", strings.Join(ctx.ActionArgs, " "))) 200 201 if err := opts.Vagrant(ctx).Execute(ctx.ActionArgs...); err != nil { 202 return err 203 } 204 205 return nil 206 } 207 208 func (opts *DevOptions) actionSSH(rctx router.Context) error { 209 ctx := rctx.(*app.Context) 210 211 dev, err := ctx.Directory.GetDev(opts.devLookup(ctx)) 212 if err != nil { 213 return err 214 } 215 if dev == nil { 216 return fmt.Errorf( 217 "The development environment hasn't been created yet! Please\n" + 218 "create the development environmet by running `otto dev` before\n" + 219 "attempting to SSH.") 220 } 221 222 project := Project(&ctx.Shared) 223 if err := project.InstallIfNeeded(); err != nil { 224 return err 225 } 226 227 ctx.Ui.Header("Executing SSH. This may take a few seconds...") 228 return opts.sshCache(ctx).Exec(true) 229 } 230 231 func (opts *DevOptions) actionUp(rctx router.Context) error { 232 ctx := rctx.(*app.Context) 233 project := Project(&ctx.Shared) 234 if err := project.InstallIfNeeded(); err != nil { 235 return err 236 } 237 238 // If we are layered, then let the user know we're going to use 239 // a layer development environment... 240 if opts.Layer != nil { 241 pending, err := opts.Layer.Pending() 242 if err != nil { 243 return fmt.Errorf("Error checking dev layer status: %s", err) 244 } 245 246 if len(pending) > 0 { 247 ctx.Ui.Header("Creating development environment layers...") 248 ctx.Ui.Message( 249 "Otto uses layers to speed up building development environments.\n" + 250 "Each layer only needs to be built once. We've detected that the\n" + 251 "layers below aren't created yet. These will be built this time.\n" + 252 "Future development envirionments will use the cached versions\n" + 253 "to be much, much faster.") 254 } 255 256 if err := opts.Layer.Build(&ctx.Shared); err != nil { 257 return fmt.Errorf( 258 "Error building dev environment layers: %s", err) 259 } 260 } 261 262 // Output some info the user prior to running 263 ctx.Ui.Header( 264 "Creating local development environment with Vagrant if it doesn't exist...") 265 266 // Store the dev status into the directory. We just do this before 267 // since there are a lot of cases where Vagrant fails but still imported. 268 // We just override any prior dev. 269 dev := &directory.Dev{Lookup: directory.Lookup{AppID: ctx.Appfile.ID}} 270 dev.MarkReady() 271 if err := ctx.Directory.PutDev(dev); err != nil { 272 return fmt.Errorf( 273 "Error saving dev environment metadata: %s", err) 274 } 275 276 // Run it! 277 vagrant := opts.Vagrant(ctx) 278 if opts.Layer != nil { 279 if err := opts.Layer.ConfigureEnv(vagrant); err != nil { 280 return fmt.Errorf( 281 "Error preparing dev environment: %s", err) 282 } 283 284 // Configure the environment as ready 285 if err := opts.Layer.SetEnv(vagrant, envStateReady); err != nil { 286 return fmt.Errorf( 287 "Error preparing dev environment: %s", err) 288 } 289 } 290 if err := vagrant.Execute("up"); err != nil { 291 return err 292 } 293 294 // Cache the SSH info 295 ctx.Ui.Header("Caching SSH credentials from Vagrant...") 296 if err := opts.sshCache(ctx).Cache(); err != nil { 297 return err 298 } 299 300 // Success, let the user know whats up 301 ctx.Ui.Header("[green]Development environment successfully created!") 302 ctx.Ui.Message(fmt.Sprintf("IP address: %s", ctx.DevIPAddress)) 303 if opts.Instructions != "" { 304 ctx.Ui.Message("\n" + opts.Instructions) 305 } 306 307 return nil 308 } 309 310 func (opts *DevOptions) actionLayers(rctx router.Context) error { 311 if opts.Layer == nil { 312 return fmt.Errorf( 313 "This development environment does not use layers.\n" + 314 "This command can only be used to manage development\n" + 315 "environments with layers.") 316 } 317 318 ctx := rctx.(*app.Context) 319 fs := flag.NewFlagSet("otto", flag.ContinueOnError) 320 graph := fs.Bool("graph", false, "show graph") 321 prune := fs.Bool("prune", false, "prune unused layers") 322 if err := fs.Parse(rctx.RouteArgs()); err != nil { 323 return err 324 } 325 326 // Graph? 327 if *graph { 328 graph, err := opts.Layer.Graph() 329 if err != nil { 330 return err 331 } 332 333 ctx.Ui.Raw(graph.String() + "\n") 334 return nil 335 } 336 337 // Prune? 338 if *prune { 339 ctx.Ui.Header("Pruning any outdated or unused layers...") 340 count, err := opts.Layer.Prune(&ctx.Shared) 341 if err != nil { 342 return err 343 } 344 if count == 0 { 345 ctx.Ui.Message("No outdated or unused layers were found!") 346 } else { 347 ctx.Ui.Message(fmt.Sprintf( 348 "[green]Pruned %d outdated or unused layers!", count)) 349 } 350 351 return nil 352 } 353 354 // We're just listing the layers. Eventually we probably should 355 // output status or something more useful here. 356 for _, l := range opts.Layer.Layers { 357 ctx.Ui.Raw(l.ID + "\n") 358 } 359 360 return nil 361 } 362 363 func (opts *DevOptions) devLookup(ctx *app.Context) *directory.Dev { 364 return &directory.Dev{Lookup: directory.Lookup{AppID: ctx.Appfile.ID}} 365 } 366 367 func (opts *DevOptions) sshCache(ctx *app.Context) *SSHCache { 368 return &SSHCache{ 369 Path: filepath.Join(ctx.CacheDir, "dev_ssh_cache"), 370 Vagrant: opts.Vagrant(ctx), 371 } 372 } 373 374 // Synopsis text for actions 375 const ( 376 actionAddressSyn = "Shows the address to reach the development environment" 377 actionUpSyn = "Starts the development environment" 378 actionDestroySyn = "Destroy the development environment" 379 actionHaltSyn = "Halts the development environment" 380 actionLayersSyn = "Manage the layers of this development environment" 381 actionSSHSyn = "SSH into the development environment" 382 actionVagrantSyn = "Run arbitrary Vagrant commands" 383 ) 384 385 // Help text for actions 386 const actionUpHelp = ` 387 Usage: otto dev 388 389 Builds and starts the development environment. 390 391 The development environment runs locally via Vagrant. Otto manages 392 Vagrant for you. All upstream dependencies will automatically be started 393 and running within the development environment. 394 395 At the end of running this command, help text will be shown that tell 396 you how to interact with the build environment. 397 ` 398 399 const actionDestroyHelp = ` 400 Usage: otto dev destroy 401 402 Destroys the development environment. 403 404 This command will stop and delete the development environment. 405 Any data that was put onto the development environment will be deleted, 406 except for your own project's code (the directory and any subdirectories 407 where the Appfile exists). 408 409 ` 410 411 const actionHaltHelp = ` 412 Usage: otto dev halt 413 414 Halts the development environment. 415 416 This command will stop the development environment. The environment can then 417 be started again with 'otto dev'. 418 419 ` 420 421 const actionLayersHelp = ` 422 Usage: otto dev layers [options] 423 424 Manage the development environment layers. 425 426 WARNING: This is an advanced, low level command. You shouldn't need this 427 command. It is meant to give you the ability to get out of a bad situation 428 if Otto mis-manages your layers. If you run into a scenario where you need 429 to use this, please report a bug to Otto so we can think of others ways 430 around it. 431 432 This command will manage the layers of the development environment. 433 Otto uses layers as a mechanism for caching parts of the development 434 environment that aren't often updated. This makes "otto dev" faster 435 after the first call. 436 437 If no options are given, the layers will be listed that this development 438 environment uses. If multiple conflicting options are given, the first 439 in alphabetical order is processed. For example, if both "-graph" and 440 "-prune" are specified, the graph will be shown. 441 442 Options: 443 444 -graph Show the full layer graph for Otto 445 -prune Delete all unused or outdated layers 446 447 ` 448 449 const actionSSHHelp = ` 450 Usage: otto dev ssh 451 452 Connect to the development environment via SSH. 453 454 The development environment typically is headless, meaning that the 455 preferred way to access it is SSH. This command will automatically SSH 456 you into the development environment. 457 458 ` 459 460 const actionAddressHelp = ` 461 Usage: otto dev address 462 463 Output the address to connect to the development environment. 464 465 The development environment is configured with a static IP address. 466 This command outputs that address so you can reach it. If you want to 467 SSH into the development environment, use 'otto dev ssh'. This address 468 is meant for reaching running services such as in a web browser. 469 470 ` 471 472 const actionVagrantHelp = ` 473 Usage: otto dev vagrant [command...] 474 475 Run arbitrary Vagrant commands against the development environment. 476 477 This is for advanced users who know and are comfortable with Vagrant. 478 In average day to day usage, this command isn't needed. 479 480 Because the development environment is backed by Vagrant, this command 481 lets you access it directly via Vagrant. For example, if you want to 482 run "vagrant ssh-config" against the environment, you can use 483 "otto dev vagrant ssh-config" 484 485 `