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