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