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  }