github.com/greenboxal/deis@v1.12.1/deisctl/cmd/cmd.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strconv" 11 "strings" 12 "sync" 13 14 "github.com/deis/deis/deisctl/backend" 15 "github.com/deis/deis/deisctl/config" 16 "github.com/deis/deis/deisctl/units" 17 "github.com/deis/deis/deisctl/utils" 18 "github.com/deis/deis/deisctl/utils/net" 19 ) 20 21 const ( 22 // PlatformCommand is shorthand for "all the Deis components." 23 PlatformCommand string = "platform" 24 // StatelessPlatformCommand is shorthand for the components except store-* and database. 25 StatelessPlatformCommand string = "stateless-platform" 26 swarm string = "swarm" 27 mesos string = "mesos" 28 // DefaultRouterMeshSize defines the default number of routers to be loaded when installing the platform. 29 DefaultRouterMeshSize uint8 = 3 30 k8s string = "k8s" 31 ) 32 33 // ListUnits prints a list of installed units. 34 func ListUnits(b backend.Backend) error { 35 return b.ListUnits() 36 } 37 38 // ListMachines prints a list of current hosts. 39 func ListMachines(b backend.Backend) error { 40 return b.ListMachines() 41 } 42 43 // ListUnitFiles prints the contents of all defined unit files. 44 func ListUnitFiles(b backend.Backend) error { 45 return b.ListUnitFiles() 46 } 47 48 // Location to write standard output. By default, this is the os.Stdout. 49 var Stdout io.Writer = os.Stdout 50 51 // Location to write standard error information. By default, this is the os.Stderr. 52 var Stderr io.Writer = os.Stderr 53 54 // Number of routers to be installed. By default, it's DefaultRouterMeshSize. 55 var RouterMeshSize = DefaultRouterMeshSize 56 57 // Scale grows or shrinks the number of running components. 58 // Currently "router", "registry" and "store-gateway" are the only types that can be scaled. 59 func Scale(targets []string, b backend.Backend) error { 60 var wg sync.WaitGroup 61 62 for _, target := range targets { 63 component, num, err := splitScaleTarget(target) 64 if err != nil { 65 return err 66 } 67 // the router, registry, and store-gateway are the only component that can scale at the moment 68 if !strings.Contains(component, "router") && !strings.Contains(component, "registry") && !strings.Contains(component, "store-gateway") { 69 return fmt.Errorf("cannot scale %s component", component) 70 } 71 b.Scale(component, num, &wg, Stdout, Stderr) 72 wg.Wait() 73 } 74 return nil 75 } 76 77 // Start activates the specified components. 78 func Start(targets []string, b backend.Backend) error { 79 80 // if target is platform, install all services 81 if len(targets) == 1 { 82 switch targets[0] { 83 case PlatformCommand: 84 return StartPlatform(b, false) 85 case StatelessPlatformCommand: 86 return StartPlatform(b, true) 87 case mesos: 88 return StartMesos(b) 89 case swarm: 90 return StartSwarm(b) 91 case k8s: 92 return StartK8s(b) 93 } 94 } 95 var wg sync.WaitGroup 96 97 b.Start(targets, &wg, Stdout, Stderr) 98 wg.Wait() 99 100 return nil 101 } 102 103 // RollingRestart restart instance unit in a rolling manner 104 func RollingRestart(target string, b backend.Backend) error { 105 var wg sync.WaitGroup 106 107 b.RollingRestart(target, &wg, Stdout, Stderr) 108 wg.Wait() 109 110 return nil 111 } 112 113 // CheckRequiredKeys exist in config backend 114 func CheckRequiredKeys(cb config.Backend) error { 115 if err := config.CheckConfig("/deis/platform/", "domain", cb); err != nil { 116 return fmt.Errorf(`Missing platform domain, use: 117 deisctl config platform set domain=<your-domain>`) 118 } 119 120 if err := config.CheckConfig("/deis/platform/", "sshPrivateKey", cb); err != nil { 121 fmt.Printf(`Warning: Missing sshPrivateKey, "deis run" will be unavailable. Use: 122 deisctl config platform set sshPrivateKey=<path-to-key> 123 `) 124 } 125 return nil 126 } 127 128 func startDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) { 129 130 // Wait for groups to come up. 131 // If we're running in stateless mode, we start only a subset of services. 132 if !stateless { 133 fmt.Fprintln(out, "Storage subsystem...") 134 b.Start([]string{"store-monitor"}, wg, out, err) 135 wg.Wait() 136 b.Start([]string{"store-daemon"}, wg, out, err) 137 wg.Wait() 138 b.Start([]string{"store-metadata"}, wg, out, err) 139 wg.Wait() 140 141 // we start gateway first to give metadata time to come up for volume 142 b.Start([]string{"store-gateway@*"}, wg, out, err) 143 wg.Wait() 144 b.Start([]string{"store-volume"}, wg, out, err) 145 wg.Wait() 146 } 147 148 // start logging subsystem first to collect logs from other components 149 fmt.Fprintln(out, "Logging subsystem...") 150 b.Start([]string{"logger"}, wg, out, err) 151 wg.Wait() 152 b.Start([]string{"logspout"}, wg, out, err) 153 wg.Wait() 154 155 // Start these in parallel. This section can probably be removed now. 156 var bgwg sync.WaitGroup 157 var trash bytes.Buffer 158 batch := []string{ 159 "database", "registry@*", "controller", "builder", 160 "publisher", "router@*", 161 } 162 if stateless { 163 batch = []string{"registry@*", "controller", "builder", "publisher", "router@*"} 164 } 165 b.Start(batch, &bgwg, &trash, &trash) 166 // End background stuff. 167 168 fmt.Fprintln(Stdout, "Control plane...") 169 batch = []string{"database", "registry@*", "controller"} 170 if stateless { 171 batch = []string{"registry@*", "controller"} 172 } 173 b.Start(batch, wg, out, err) 174 wg.Wait() 175 176 b.Start([]string{"builder"}, wg, out, err) 177 wg.Wait() 178 179 fmt.Fprintln(out, "Data plane...") 180 b.Start([]string{"publisher"}, wg, out, err) 181 wg.Wait() 182 183 fmt.Fprintln(out, "Router mesh...") 184 b.Start([]string{"router@*"}, wg, out, err) 185 wg.Wait() 186 } 187 188 // Stop deactivates the specified components. 189 func Stop(targets []string, b backend.Backend) error { 190 191 // if target is platform, stop all services 192 if len(targets) == 1 { 193 switch targets[0] { 194 case PlatformCommand: 195 return StopPlatform(b, false) 196 case StatelessPlatformCommand: 197 return StopPlatform(b, true) 198 case mesos: 199 return StopMesos(b) 200 case swarm: 201 return StopSwarm(b) 202 case k8s: 203 return StopK8s(b) 204 } 205 } 206 207 var wg sync.WaitGroup 208 209 b.Stop(targets, &wg, Stdout, Stderr) 210 wg.Wait() 211 212 return nil 213 } 214 215 func stopDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) { 216 217 fmt.Fprintln(out, "Router mesh...") 218 b.Stop([]string{"router@*"}, wg, out, err) 219 wg.Wait() 220 221 fmt.Fprintln(out, "Data plane...") 222 b.Stop([]string{"publisher"}, wg, out, err) 223 wg.Wait() 224 225 fmt.Fprintln(out, "Control plane...") 226 if stateless { 227 b.Stop([]string{"controller", "builder", "registry@*"}, wg, out, err) 228 } else { 229 b.Stop([]string{"controller", "builder", "database", "registry@*"}, wg, out, err) 230 } 231 wg.Wait() 232 233 fmt.Fprintln(out, "Logging subsystem...") 234 if stateless { 235 b.Stop([]string{"logspout"}, wg, out, err) 236 } else { 237 b.Stop([]string{"logger", "logspout"}, wg, out, err) 238 } 239 wg.Wait() 240 241 if !stateless { 242 fmt.Fprintln(out, "Storage subsystem...") 243 b.Stop([]string{"store-volume", "store-gateway@*"}, wg, out, err) 244 wg.Wait() 245 b.Stop([]string{"store-metadata"}, wg, out, err) 246 wg.Wait() 247 b.Stop([]string{"store-daemon"}, wg, out, err) 248 wg.Wait() 249 b.Stop([]string{"store-monitor"}, wg, out, err) 250 wg.Wait() 251 } 252 253 } 254 255 // Restart stops and then starts the specified components. 256 func Restart(targets []string, b backend.Backend) error { 257 258 // act as if the user called "stop" and then "start" 259 if err := Stop(targets, b); err != nil { 260 return err 261 } 262 263 return Start(targets, b) 264 } 265 266 // Status prints the current status of components. 267 func Status(targets []string, b backend.Backend) error { 268 269 for _, target := range targets { 270 if err := b.Status(target); err != nil { 271 return err 272 } 273 } 274 return nil 275 } 276 277 // Journal prints log output for the specified components. 278 func Journal(targets []string, b backend.Backend) error { 279 280 for _, target := range targets { 281 if err := b.Journal(target); err != nil { 282 return err 283 } 284 } 285 return nil 286 } 287 288 // Install loads the definitions of components from local unit files. 289 // After Install, the components will be available to Start. 290 func Install(targets []string, b backend.Backend, cb config.Backend, checkKeys func(config.Backend) error) error { 291 292 // if target is platform, install all services 293 if len(targets) == 1 { 294 switch targets[0] { 295 case PlatformCommand: 296 return InstallPlatform(b, cb, checkKeys, false) 297 case StatelessPlatformCommand: 298 return InstallPlatform(b, cb, checkKeys, true) 299 case mesos: 300 return InstallMesos(b) 301 case swarm: 302 return InstallSwarm(b) 303 case k8s: 304 return InstallK8s(b) 305 } 306 } 307 var wg sync.WaitGroup 308 309 // otherwise create the specific targets 310 b.Create(targets, &wg, Stdout, Stderr) 311 wg.Wait() 312 313 return nil 314 } 315 316 func installDefaultServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) { 317 318 if !stateless { 319 fmt.Fprintln(out, "Storage subsystem...") 320 b.Create([]string{"store-daemon", "store-monitor", "store-metadata", "store-volume", "store-gateway@1"}, wg, out, err) 321 wg.Wait() 322 } 323 324 fmt.Fprintln(out, "Logging subsystem...") 325 b.Create([]string{"logger", "logspout"}, wg, out, err) 326 wg.Wait() 327 328 fmt.Fprintln(out, "Control plane...") 329 if stateless { 330 b.Create([]string{"registry@1", "controller", "builder"}, wg, out, err) 331 } else { 332 b.Create([]string{"database", "registry@1", "controller", "builder"}, wg, out, err) 333 } 334 wg.Wait() 335 336 fmt.Fprintln(out, "Data plane...") 337 b.Create([]string{"publisher"}, wg, out, err) 338 wg.Wait() 339 340 fmt.Fprintln(out, "Router mesh...") 341 b.Create(getRouters(), wg, out, err) 342 wg.Wait() 343 344 } 345 346 func getRouters() []string { 347 routers := make([]string, RouterMeshSize) 348 for i := uint8(0); i < RouterMeshSize; i++ { 349 routers[i] = fmt.Sprintf("router@%d", i+1) 350 } 351 return routers 352 } 353 354 // Uninstall unloads the definitions of the specified components. 355 // After Uninstall, the components will be unavailable until Install is called. 356 func Uninstall(targets []string, b backend.Backend) error { 357 if len(targets) == 1 { 358 switch targets[0] { 359 case PlatformCommand: 360 return UninstallPlatform(b, false) 361 case StatelessPlatformCommand: 362 return UninstallPlatform(b, true) 363 case mesos: 364 return UninstallMesos(b) 365 case swarm: 366 return UnInstallSwarm(b) 367 case k8s: 368 return UnInstallK8s(b) 369 } 370 } 371 372 var wg sync.WaitGroup 373 374 // uninstall the specific target 375 b.Destroy(targets, &wg, Stdout, Stderr) 376 wg.Wait() 377 378 return nil 379 } 380 381 func uninstallAllServices(b backend.Backend, stateless bool, wg *sync.WaitGroup, out, err io.Writer) error { 382 383 fmt.Fprintln(out, "Router mesh...") 384 b.Destroy([]string{"router@*"}, wg, out, err) 385 wg.Wait() 386 387 fmt.Fprintln(out, "Data plane...") 388 b.Destroy([]string{"publisher"}, wg, out, err) 389 wg.Wait() 390 391 fmt.Fprintln(out, "Control plane...") 392 if stateless { 393 b.Destroy([]string{"controller", "builder", "registry@*"}, wg, out, err) 394 } else { 395 b.Destroy([]string{"controller", "builder", "database", "registry@*"}, wg, out, err) 396 } 397 wg.Wait() 398 399 fmt.Fprintln(out, "Logging subsystem...") 400 if stateless { 401 b.Destroy([]string{"logspout"}, wg, out, err) 402 } else { 403 b.Destroy([]string{"logger", "logspout"}, wg, out, err) 404 } 405 wg.Wait() 406 407 if !stateless { 408 fmt.Fprintln(out, "Storage subsystem...") 409 b.Destroy([]string{"store-volume", "store-gateway@*"}, wg, out, err) 410 wg.Wait() 411 b.Destroy([]string{"store-metadata"}, wg, out, err) 412 wg.Wait() 413 b.Destroy([]string{"store-daemon"}, wg, out, err) 414 wg.Wait() 415 b.Destroy([]string{"store-monitor"}, wg, out, err) 416 wg.Wait() 417 } 418 419 return nil 420 } 421 422 func splitScaleTarget(target string) (c string, num int, err error) { 423 r := regexp.MustCompile(`([a-z-]+)=([\d]+)`) 424 match := r.FindStringSubmatch(target) 425 if len(match) == 0 { 426 err = fmt.Errorf("Could not parse: %v", target) 427 return 428 } 429 c = match[1] 430 num, err = strconv.Atoi(match[2]) 431 if err != nil { 432 return 433 } 434 return 435 } 436 437 // Config gets or sets a configuration value from the cluster. 438 // 439 // A configuration value is stored and retrieved from a key/value store 440 // at /deis/<component>/<config>. Configuration values are typically used for component-level 441 // configuration, such as enabling TLS for the routers. 442 func Config(target string, action string, key []string, cb config.Backend) error { 443 if err := config.Config(target, action, key, cb); err != nil { 444 return err 445 } 446 return nil 447 } 448 449 // RefreshUnits overwrites local unit files with those requested. 450 // Downloading from the Deis project GitHub URL by tag or SHA is the only mechanism 451 // currently supported. 452 func RefreshUnits(unitDir, tag, rootURL string) error { 453 unitDir = utils.ResolvePath(unitDir) 454 decoratorDir := filepath.Join(unitDir, "decorators") 455 // create the target dir if necessary 456 if err := os.MkdirAll(decoratorDir, 0755); err != nil { 457 return err 458 } 459 // download and save the unit files to the specified path 460 for _, unit := range units.Names { 461 unitSrc := rootURL + tag + "/deisctl/units/" + unit + ".service" 462 unitDest := filepath.Join(unitDir, unit+".service") 463 if err := net.Download(unitSrc, unitDest); err != nil { 464 return err 465 } 466 fmt.Printf("Refreshed %s unit from %s\n", unit, tag) 467 decoratorSrc := rootURL + tag + "/deisctl/units/decorators/" + unit + ".service.decorator" 468 decoratorDest := filepath.Join(decoratorDir, unit+".service.decorator") 469 if err := net.Download(decoratorSrc, decoratorDest); err != nil { 470 if err.Error() == "404 Not Found" { 471 fmt.Printf("Decorator for %s not found in %s\n", unit, tag) 472 } else { 473 return err 474 } 475 } else { 476 fmt.Printf("Refreshed %s decorator from %s\n", unit, tag) 477 } 478 } 479 return nil 480 } 481 482 // SSH opens an interactive shell on a machine in the cluster 483 func SSH(target string, cmd []string, b backend.Backend) error { 484 485 if len(cmd) > 0 { 486 return b.SSHExec(target, strings.Join(cmd, " ")) 487 } 488 489 return b.SSH(target) 490 } 491 492 // Dock connects to the appropriate host and runs 'docker exec -it'. 493 func Dock(target string, cmd []string, b backend.Backend) error { 494 return b.Dock(target, cmd) 495 }