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  }