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