github.com/misfo/deis@v1.0.1-0.20141111224634-e0eee0392b8a/deisctl/cmd/cmd.go (about) 1 package cmd 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "os" 9 "path" 10 "path/filepath" 11 "regexp" 12 "strconv" 13 "strings" 14 "sync" 15 "time" 16 17 "github.com/deis/deis/deisctl/backend" 18 "github.com/deis/deis/deisctl/config" 19 "github.com/deis/deis/deisctl/utils" 20 21 docopt "github.com/docopt/docopt-go" 22 ) 23 24 const ( 25 // PlatformCommand is shorthand for "all the Deis components." 26 PlatformCommand string = "platform" 27 ) 28 29 // ListUnits prints a list of installed units. 30 func ListUnits(argv []string, b backend.Backend) error { 31 usage := `Prints a list of installed units. 32 33 Usage: 34 deisctl list [options] 35 ` 36 // parse command-line arguments 37 if _, err := docopt.Parse(usage, argv, true, "", false); err != nil { 38 return err 39 } 40 return b.ListUnits() 41 } 42 43 // ListUnitFiles prints the contents of all defined unit files. 44 func ListUnitFiles(argv []string, b backend.Backend) error { 45 err := b.ListUnitFiles() 46 return err 47 } 48 49 // Scale grows or shrinks the number of running components. 50 // Currently "router" is the only type that can be scaled. 51 func Scale(argv []string, b backend.Backend) error { 52 usage := `Grows or shrinks the number of running components. 53 54 Currently "router" is the only type that can be scaled. 55 56 Usage: 57 deisctl scale [<target>...] [options] 58 ` 59 // parse command-line arguments 60 args, err := docopt.Parse(usage, argv, true, "", false) 61 if err != nil { 62 return err 63 } 64 targets := args["<target>"].([]string) 65 66 outchan := make(chan string) 67 errchan := make(chan error) 68 var wg sync.WaitGroup 69 70 go printState(outchan, errchan, 500*time.Millisecond) 71 72 for _, target := range targets { 73 component, num, err := splitScaleTarget(target) 74 if err != nil { 75 return err 76 } 77 // the router is the only component that can scale at the moment 78 if !strings.Contains(component, "router") { 79 return fmt.Errorf("cannot scale %s components", component) 80 } 81 b.Scale(component, num, &wg, outchan, errchan) 82 wg.Wait() 83 } 84 close(outchan) 85 return nil 86 } 87 88 // Start activates the specified components. 89 func Start(argv []string, b backend.Backend) error { 90 usage := `Activates the specified components. 91 92 Usage: 93 deisctl start [<target>...] [options] 94 ` 95 // parse command-line arguments 96 args, err := docopt.Parse(usage, argv, true, "", false) 97 if err != nil { 98 return err 99 } 100 101 // if target is platform, install all services 102 targets := args["<target>"].([]string) 103 104 if len(targets) == 1 && targets[0] == PlatformCommand { 105 return StartPlatform(b) 106 } 107 108 outchan := make(chan string) 109 errchan := make(chan error) 110 var wg sync.WaitGroup 111 112 go printState(outchan, errchan, 500*time.Millisecond) 113 114 b.Start(targets, &wg, outchan, errchan) 115 wg.Wait() 116 close(outchan) 117 118 return nil 119 } 120 121 // checkRequiredKeys exist in etcd 122 func checkRequiredKeys() error { 123 if err := config.CheckConfig("/deis/platform/", "domain"); err != nil { 124 return fmt.Errorf(`Missing platform domain, use: 125 deisctl config platform set domain=<your-domain>`) 126 } 127 128 if err := config.CheckConfig("/deis/platform/", "sshPrivateKey"); err != nil { 129 fmt.Printf(`Warning: Missing sshPrivateKey, "deis run" will be unavailable. Use: 130 deisctl config platform set sshPrivateKey=<path-to-key> 131 `) 132 } 133 return nil 134 } 135 136 // StartPlatform activates all components. 137 func StartPlatform(b backend.Backend) error { 138 139 outchan := make(chan string) 140 errchan := make(chan error) 141 var wg sync.WaitGroup 142 143 go printState(outchan, errchan, 500*time.Millisecond) 144 145 outchan <- utils.DeisIfy("Starting Deis...") 146 147 startDefaultServices(b, &wg, outchan, errchan) 148 149 wg.Wait() 150 close(outchan) 151 152 fmt.Println("Done.") 153 fmt.Println() 154 fmt.Println("Please use `deis register` to setup an administrator account.") 155 return nil 156 } 157 158 func startDefaultServices(b backend.Backend, wg *sync.WaitGroup, outchan chan string, errchan chan error) { 159 160 // create separate channels for background tasks 161 _outchan := make(chan string) 162 _errchan := make(chan error) 163 var _wg sync.WaitGroup 164 165 // wait for groups to come up 166 outchan <- fmt.Sprintf("Storage subsystem...") 167 b.Start([]string{"store-monitor"}, wg, outchan, errchan) 168 wg.Wait() 169 b.Start([]string{"store-daemon"}, wg, outchan, errchan) 170 wg.Wait() 171 b.Start([]string{"store-metadata"}, wg, outchan, errchan) 172 wg.Wait() 173 174 // we start gateway first to give metadata time to come up for volume 175 b.Start([]string{"store-gateway"}, wg, outchan, errchan) 176 wg.Wait() 177 b.Start([]string{"store-volume"}, wg, outchan, errchan) 178 wg.Wait() 179 180 // start logging subsystem first to collect logs from other components 181 outchan <- fmt.Sprintf("Logging subsystem...") 182 b.Start([]string{"logger"}, wg, outchan, errchan) 183 wg.Wait() 184 b.Start([]string{"logspout"}, wg, outchan, errchan) 185 wg.Wait() 186 187 // optimization: start all remaining services in the background 188 b.Start([]string{ 189 "cache", "database", "registry", "controller", "builder", 190 "publisher", "router@1", "router@2", "router@3"}, 191 &_wg, _outchan, _errchan) 192 193 outchan <- fmt.Sprintf("Control plane...") 194 b.Start([]string{"cache", "database", "registry", "controller"}, wg, outchan, errchan) 195 wg.Wait() 196 b.Start([]string{"builder"}, wg, outchan, errchan) 197 wg.Wait() 198 199 outchan <- fmt.Sprintf("Data plane...") 200 b.Start([]string{"publisher"}, wg, outchan, errchan) 201 wg.Wait() 202 203 outchan <- fmt.Sprintf("Routing mesh...") 204 b.Start([]string{"router@1", "router@2", "router@3"}, wg, outchan, errchan) 205 wg.Wait() 206 } 207 208 // Stop deactivates the specified components. 209 func Stop(argv []string, b backend.Backend) error { 210 usage := `Deactivates the specified components. 211 212 Usage: 213 deisctl stop [<target>...] [options] 214 ` 215 // parse command-line arguments 216 args, err := docopt.Parse(usage, argv, true, "", false) 217 if err != nil { 218 return err 219 } 220 targets := args["<target>"].([]string) 221 222 // if target is platform, stop all services 223 if len(targets) == 1 && targets[0] == PlatformCommand { 224 return StopPlatform(b) 225 } 226 227 outchan := make(chan string) 228 errchan := make(chan error) 229 var wg sync.WaitGroup 230 231 go printState(outchan, errchan, 500*time.Millisecond) 232 233 b.Stop(targets, &wg, outchan, errchan) 234 wg.Wait() 235 close(outchan) 236 237 return nil 238 } 239 240 // StopPlatform deactivates all components. 241 func StopPlatform(b backend.Backend) error { 242 243 outchan := make(chan string) 244 errchan := make(chan error) 245 var wg sync.WaitGroup 246 247 go printState(outchan, errchan, 500*time.Millisecond) 248 249 outchan <- utils.DeisIfy("Stopping Deis...") 250 251 stopDefaultServices(b, &wg, outchan, errchan) 252 253 wg.Wait() 254 close(outchan) 255 256 fmt.Println("Done.") 257 fmt.Println() 258 fmt.Println("Please run `deisctl start platform` to restart Deis.") 259 return nil 260 } 261 262 func stopDefaultServices(b backend.Backend, wg *sync.WaitGroup, outchan chan string, errchan chan error) { 263 264 outchan <- fmt.Sprintf("Routing mesh...") 265 b.Stop([]string{"router@1", "router@2", "router@3"}, wg, outchan, errchan) 266 wg.Wait() 267 268 outchan <- fmt.Sprintf("Data plane...") 269 b.Stop([]string{"publisher"}, wg, outchan, errchan) 270 wg.Wait() 271 272 outchan <- fmt.Sprintf("Control plane...") 273 b.Stop([]string{"controller", "builder", "cache", "database", "registry"}, wg, outchan, errchan) 274 wg.Wait() 275 276 outchan <- fmt.Sprintf("Logging subsystem...") 277 b.Stop([]string{"logger", "logspout"}, wg, outchan, errchan) 278 wg.Wait() 279 280 outchan <- fmt.Sprintf("Storage subsystem...") 281 b.Stop([]string{"store-volume", "store-gateway"}, wg, outchan, errchan) 282 wg.Wait() 283 b.Stop([]string{"store-metadata"}, wg, outchan, errchan) 284 wg.Wait() 285 b.Stop([]string{"store-daemon"}, wg, outchan, errchan) 286 wg.Wait() 287 b.Stop([]string{"store-monitor"}, wg, outchan, errchan) 288 wg.Wait() 289 } 290 291 // Restart stops and then starts the specified components. 292 func Restart(argv []string, b backend.Backend) error { 293 usage := `Stops and then starts the specified components. 294 295 Usage: 296 deisctl restart [<target>...] [options] 297 ` 298 // parse command-line arguments 299 if _, err := docopt.Parse(usage, argv, true, "", false); err != nil { 300 return err 301 } 302 303 // act as if the user called "stop" and then "start" 304 argv[0] = "stop" 305 if err := Stop(argv, b); err != nil { 306 return err 307 } 308 argv[0] = "start" 309 return Start(argv, b) 310 } 311 312 // Status prints the current status of components. 313 func Status(argv []string, b backend.Backend) error { 314 usage := `Prints the current status of components. 315 316 Usage: 317 deisctl status [<target>...] [options] 318 ` 319 // parse command-line arguments 320 args, err := docopt.Parse(usage, argv, true, "", false) 321 if err != nil { 322 return err 323 } 324 325 targets := args["<target>"].([]string) 326 for _, target := range targets { 327 if err := b.Status(target); err != nil { 328 return err 329 } 330 } 331 return nil 332 } 333 334 // Journal prints log output for the specified components. 335 func Journal(argv []string, b backend.Backend) error { 336 usage := `Prints log output for the specified components. 337 338 Usage: 339 deisctl journal [<target>...] [options] 340 ` 341 // parse command-line arguments 342 args, err := docopt.Parse(usage, argv, true, "", false) 343 if err != nil { 344 return err 345 } 346 347 targets := args["<target>"].([]string) 348 for _, target := range targets { 349 if err := b.Journal(target); err != nil { 350 return err 351 } 352 } 353 return nil 354 } 355 356 // Install loads the definitions of components from local unit files. 357 // After Install, the components will be available to Start. 358 func Install(argv []string, b backend.Backend) error { 359 usage := `Loads the definitions of components from local unit files. 360 361 After install, the components will be available to start. 362 363 "deisctl install" looks for unit files in these directories, in this order: 364 - the $DEISCTL_UNITS environment variable, if set 365 - $HOME/.deis/units 366 - /var/lib/deis/units 367 368 Usage: 369 deisctl install [<target>...] [options] 370 ` 371 // parse command-line arguments 372 args, err := docopt.Parse(usage, argv, true, "", false) 373 if err != nil { 374 return err 375 } 376 377 // if target is platform, install all services 378 targets := args["<target>"].([]string) 379 if len(targets) == 1 && targets[0] == PlatformCommand { 380 return InstallPlatform(b) 381 } 382 383 outchan := make(chan string) 384 errchan := make(chan error) 385 var wg sync.WaitGroup 386 387 go printState(outchan, errchan, 500*time.Millisecond) 388 389 // otherwise create the specific targets 390 b.Create(targets, &wg, outchan, errchan) 391 wg.Wait() 392 393 close(outchan) 394 return nil 395 } 396 397 // InstallPlatform loads all components' definitions from local unit files. 398 // After InstallPlatform, all components will be available for StartPlatform. 399 func InstallPlatform(b backend.Backend) error { 400 401 if err := checkRequiredKeys(); err != nil { 402 return err 403 } 404 405 outchan := make(chan string) 406 errchan := make(chan error) 407 var wg sync.WaitGroup 408 409 go printState(outchan, errchan, 500*time.Millisecond) 410 411 outchan <- utils.DeisIfy("Installing Deis...") 412 413 installDefaultServices(b, &wg, outchan, errchan) 414 415 wg.Wait() 416 close(outchan) 417 418 fmt.Println("Done.") 419 fmt.Println() 420 fmt.Println("Please run `deisctl start platform` to boot up Deis.") 421 return nil 422 } 423 424 func installDefaultServices(b backend.Backend, wg *sync.WaitGroup, outchan chan string, errchan chan error) { 425 426 outchan <- fmt.Sprintf("Storage subsystem...") 427 b.Create([]string{"store-daemon", "store-monitor", "store-metadata", "store-volume", "store-gateway"}, wg, outchan, errchan) 428 wg.Wait() 429 430 outchan <- fmt.Sprintf("Logging subsystem...") 431 b.Create([]string{"logger", "logspout"}, wg, outchan, errchan) 432 wg.Wait() 433 434 outchan <- fmt.Sprintf("Control plane...") 435 b.Create([]string{"cache", "database", "registry", "controller", "builder"}, wg, outchan, errchan) 436 wg.Wait() 437 438 outchan <- fmt.Sprintf("Data plane...") 439 b.Create([]string{"publisher"}, wg, outchan, errchan) 440 wg.Wait() 441 442 outchan <- fmt.Sprintf("Routing mesh...") 443 b.Create([]string{"router@1", "router@2", "router@3"}, wg, outchan, errchan) 444 wg.Wait() 445 } 446 447 // Uninstall unloads the definitions of the specified components. 448 // After Uninstall, the components will be unavailable until Install is called. 449 func Uninstall(argv []string, b backend.Backend) error { 450 usage := `Unloads the definitions of the specified components. 451 452 After uninstall, the components will be unavailable until install is called. 453 454 Usage: 455 deisctl uninstall [<target>...] [options] 456 ` 457 // parse command-line arguments 458 args, err := docopt.Parse(usage, argv, true, "", false) 459 if err != nil { 460 return err 461 } 462 463 // if target is platform, uninstall all services 464 targets := args["<target>"].([]string) 465 if len(targets) == 1 && targets[0] == PlatformCommand { 466 return UninstallPlatform(b) 467 } 468 469 outchan := make(chan string) 470 errchan := make(chan error) 471 var wg sync.WaitGroup 472 473 go printState(outchan, errchan, 500*time.Millisecond) 474 475 // uninstall the specific target 476 b.Destroy(targets, &wg, outchan, errchan) 477 wg.Wait() 478 close(outchan) 479 480 return nil 481 } 482 483 // UninstallPlatform unloads all components' definitions. 484 // After UninstallPlatform, all components will be unavailable. 485 func UninstallPlatform(b backend.Backend) error { 486 487 outchan := make(chan string) 488 errchan := make(chan error) 489 var wg sync.WaitGroup 490 491 go printState(outchan, errchan, 500*time.Millisecond) 492 493 outchan <- utils.DeisIfy("Uninstalling Deis...") 494 495 uninstallAllServices(b, &wg, outchan, errchan) 496 497 wg.Wait() 498 close(outchan) 499 500 fmt.Println("Done.") 501 return nil 502 } 503 504 func uninstallAllServices(b backend.Backend, wg *sync.WaitGroup, outchan chan string, errchan chan error) error { 505 506 outchan <- fmt.Sprintf("Routing mesh...") 507 b.Destroy([]string{"router@1", "router@2", "router@3"}, wg, outchan, errchan) 508 wg.Wait() 509 510 outchan <- fmt.Sprintf("Data plane...") 511 b.Destroy([]string{"publisher"}, wg, outchan, errchan) 512 wg.Wait() 513 514 outchan <- fmt.Sprintf("Control plane...") 515 b.Destroy([]string{"controller", "builder", "cache", "database", "registry"}, wg, outchan, errchan) 516 wg.Wait() 517 518 outchan <- fmt.Sprintf("Logging subsystem...") 519 b.Destroy([]string{"logger", "logspout"}, wg, outchan, errchan) 520 wg.Wait() 521 522 outchan <- fmt.Sprintf("Storage subsystem...") 523 b.Destroy([]string{"store-volume", "store-gateway"}, wg, outchan, errchan) 524 wg.Wait() 525 b.Destroy([]string{"store-metadata"}, wg, outchan, errchan) 526 wg.Wait() 527 b.Destroy([]string{"store-daemon"}, wg, outchan, errchan) 528 wg.Wait() 529 b.Destroy([]string{"store-monitor"}, wg, outchan, errchan) 530 wg.Wait() 531 532 return nil 533 } 534 535 func printState(outchan chan string, errchan chan error, interval time.Duration) error { 536 for { 537 select { 538 case out := <-outchan: 539 // done on closed channel 540 if out == "" { 541 return nil 542 } 543 fmt.Println(out) 544 case err := <-errchan: 545 if err != nil { 546 fmt.Println(err.Error()) 547 return err 548 } 549 } 550 time.Sleep(interval) 551 } 552 } 553 554 func splitScaleTarget(target string) (c string, num int, err error) { 555 r := regexp.MustCompile(`([a-z-]+)=([\d]+)`) 556 match := r.FindStringSubmatch(target) 557 if len(match) == 0 { 558 err = fmt.Errorf("Could not parse: %v", target) 559 return 560 } 561 c = match[1] 562 num, err = strconv.Atoi(match[2]) 563 if err != nil { 564 return 565 } 566 return 567 } 568 569 // Config gets or sets a configuration value from the cluster. 570 // 571 // A configuration value is stored and retrieved from a key/value store (in this case, etcd) 572 // at /deis/<component>/<config>. Configuration values are typically used for component-level 573 // configuration, such as enabling TLS for the routers. 574 func Config(argv []string) error { 575 usage := `Gets or sets a configuration value from the cluster. 576 577 A configuration value is stored and retrieved from a key/value store 578 (in this case, etcd) at /deis/<component>/<config>. Configuration 579 values are typically used for component-level configuration, such as 580 enabling TLS for the routers. 581 582 Usage: 583 deisctl config <target> get [<key>...] [options] 584 deisctl config <target> set <key=val>... [options] 585 586 Options: 587 --verbose print out the request bodies [default: false] 588 ` 589 // parse command-line arguments 590 args, err := docopt.Parse(usage, argv, true, "", false) 591 if err != nil { 592 return err 593 } 594 if err := config.Config(args); err != nil { 595 return err 596 } 597 return nil 598 } 599 600 // RefreshUnits overwrites local unit files with those requested. 601 // Downloading from the Deis project GitHub URL by tag or SHA is the only mechanism 602 // currently supported. 603 func RefreshUnits(argv []string) error { 604 usage := `Overwrites local unit files with those requested. 605 606 Downloading from the Deis project GitHub URL by tag or SHA is the only mechanism 607 currently supported. 608 609 "deisctl install" looks for unit files in these directories, in this order: 610 - the $DEISCTL_UNITS environment variable, if set 611 - $HOME/.deis/units 612 - /var/lib/deis/units 613 614 Usage: 615 deisctl refresh-units [-p <target>] [-t <tag>] 616 617 Options: 618 -p --path=<target> where to save unit files [default: $HOME/.deis/units] 619 -t --tag=<tag> git tag, branch, or SHA to use when downloading unit files 620 [default: master] 621 ` 622 // parse command-line arguments 623 args, err := docopt.Parse(usage, argv, true, "", false) 624 if err != nil { 625 fmt.Printf("Error: %v\n", err) 626 os.Exit(2) 627 } 628 dir := args["--path"].(string) 629 if dir == "$HOME/.deis/units" || dir == "~/.deis/units" { 630 dir = path.Join(os.Getenv("HOME"), ".deis", "units") 631 } 632 // create the target dir if necessary 633 if err := os.MkdirAll(dir, 0755); err != nil { 634 return err 635 } 636 // download and save the unit files to the specified path 637 rootURL := "https://raw.githubusercontent.com/deis/deis/" 638 tag := args["--tag"].(string) 639 units := []string{ 640 "deis-builder.service", 641 "deis-cache.service", 642 "deis-controller.service", 643 "deis-database.service", 644 "deis-logger.service", 645 "deis-logspout.service", 646 "deis-publisher.service", 647 "deis-registry.service", 648 "deis-router.service", 649 "deis-store-daemon.service", 650 "deis-store-gateway.service", 651 "deis-store-metadata.service", 652 "deis-store-monitor.service", 653 "deis-store-volume.service", 654 } 655 for _, unit := range units { 656 src := rootURL + tag + "/deisctl/units/" + unit 657 dest := filepath.Join(dir, unit) 658 res, err := http.Get(src) 659 if err != nil { 660 return err 661 } 662 if res.StatusCode != 200 { 663 return errors.New(res.Status) 664 } 665 defer res.Body.Close() 666 data, err := ioutil.ReadAll(res.Body) 667 if err != nil { 668 return err 669 } 670 if err = ioutil.WriteFile(dest, data, 0644); err != nil { 671 return err 672 } 673 fmt.Printf("Refreshed %s from %s\n", unit, tag) 674 } 675 return nil 676 }