Complete project pivot (#55)

Complete project pivot
This commit is contained in:
2022-01-12 08:57:21 +01:00
committed by GitHub
parent 70aa5332a9
commit bd581db472
46 changed files with 2638 additions and 1396 deletions

View File

@@ -1,15 +0,0 @@
package cmd
import (
"speedrun/config"
"github.com/spf13/cobra"
)
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize speedrun",
RunE: func(cmd *cobra.Command, args []string) error {
return config.Create()
},
}

View File

@@ -1,181 +0,0 @@
package cmd
import (
"path/filepath"
"speedrun/cloud"
"speedrun/key"
"github.com/apex/log"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var keyCmd = &cobra.Command{
Use: "key",
Short: "Manage ssh keys",
TraverseChildren: true,
}
var newKeyCmd = &cobra.Command{
Use: "new",
Short: "Create a new ssh key",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: newKey,
}
var authorizeKeyCmd = &cobra.Command{
Use: "authorize",
Short: "Authorize key for ssh access",
Example: " speedrun key authorize",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: authorizeKey,
}
var revokeKeyCmd = &cobra.Command{
Use: "revoke",
Short: "Revoke ssh key",
Example: " speedrun key revoke",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: revokeKey,
}
var listKeysCmd = &cobra.Command{
Use: "list",
Short: "List OS Login keys",
Example: " speedrun key list",
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: listKeys,
}
func init() {
keyCmd.AddCommand(newKeyCmd)
keyCmd.AddCommand(authorizeKeyCmd)
keyCmd.AddCommand(revokeKeyCmd)
keyCmd.AddCommand(listKeysCmd)
authorizeKeyCmd.Flags().Bool("use-oslogin", false, "Authorize the key via OS Login rather than metadata")
viper.BindPFlag("gcp.use-oslogin", authorizeKeyCmd.Flags().Lookup("use-oslogin"))
}
func determineKeyFilePath() (string, error) {
log.Debug("Determining private key path")
home, err := homedir.Dir()
if err != nil {
return "", err
}
path := filepath.Join(home, ".speedrun/privatekey")
return path, nil
}
func newKey(cmd *cobra.Command, args []string) error {
k, err := key.New()
if err != nil {
return err
}
path, err := determineKeyFilePath()
if err != nil {
return err
}
err = k.Write(path)
if err != nil {
return err
}
log.Info("Private key created")
return nil
}
func authorizeKey(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
useOSlogin := viper.GetBool("gcp.use-oslogin")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
path, err := determineKeyFilePath()
if err != nil {
return err
}
k, err := key.Read(path)
if err != nil {
return err
}
if useOSlogin {
gcpClient.AddUserKey(k)
if err != nil {
return err
}
log.Info("Authorized key via OS Login")
} else {
gcpClient.AddKeyToMetadata(k)
if err != nil {
return err
}
log.Info("Authorized key in the project metadata")
}
return nil
}
func revokeKey(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
path, err := determineKeyFilePath()
if err != nil {
return err
}
k, err := key.Read(path)
if err != nil {
return err
}
log.Info("Revoking public key")
err = gcpClient.RemoveKeyFromMetadata(k)
if err != nil {
return err
}
err = gcpClient.RemoveUserKey(k)
if err != nil {
return err
}
return nil
}
func listKeys(cmd *cobra.Command, args []string) error {
project := viper.GetString("gcp.projectid")
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
log.Info("Fetching OS Login keys")
err = gcpClient.ListUserKeys()
if err != nil {
return err
}
return nil
}

59
cmd/portal/cli/init.go Normal file
View File

@@ -0,0 +1,59 @@
package cli
import (
"fmt"
"os"
"path/filepath"
"github.com/apex/log"
"github.com/pelletier/go-toml"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
initCmd.Flags().BoolP("print", "p", false, "Print default config to stdout")
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize portal",
RunE: func(cmd *cobra.Command, args []string) error {
print, err := cmd.Flags().GetBool("print")
if err != nil {
return err
}
if print {
c := viper.AllSettings()
bs, err := toml.Marshal(c)
if err != nil {
return fmt.Errorf("unable to marshal config: %v", err)
}
fmt.Println(string(bs))
return nil
}
dir := filepath.Dir(cfgFile)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.Mkdir(dir, 0755)
if err != nil {
return err
}
}
err = viper.SafeWriteConfigAs(cfgFile)
if err != nil {
if _, ok := err.(viper.ConfigFileAlreadyExistsError); !ok {
return fmt.Errorf("couldn't save config at \"%s\" (%s)", viper.ConfigFileUsed(), err)
}
} else {
log.Infof("Your config was saved at \"%s\"", viper.ConfigFileUsed())
return nil
}
log.Infof("Config already exists at \"%s\", no changes applied", viper.ConfigFileUsed())
return nil
},
}

128
cmd/portal/cli/root.go Normal file
View File

@@ -0,0 +1,128 @@
package cli
import (
"context"
"crypto/tls"
"fmt"
"os"
"path/filepath"
"github.com/apex/log"
jsonhandler "github.com/apex/log/handlers/json"
texthandler "github.com/apex/log/handlers/text"
"github.com/speedrunsh/speedrun/pkg/common/cryptoutil"
"github.com/speedrunsh/speedrun/pkg/portal"
portalpb "github.com/speedrunsh/speedrun/proto/portal"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"storj.io/drpc/drpcmux"
"storj.io/drpc/drpcserver"
)
var cfgFile string
var version string
var commit string
var date string
func Execute() {
var rootCmd = &cobra.Command{
Use: "portal",
Short: "Control your compute fleet at scale",
Version: fmt.Sprintf("%s, commit: %s, date: %s", version, commit, date),
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
insecure := viper.GetBool("tls.insecure")
caPath := viper.GetString("tls.ca")
certPath := viper.GetString("tls.cert")
keyPath := viper.GetString("tls.key")
m := drpcmux.New()
var err error
err = portalpb.DRPCRegisterPortal(m, &portal.Server{})
if err != nil {
return fmt.Errorf("could not register DRPC server: %v", err)
}
s := drpcserver.New(m)
var tlsConfig *tls.Config
if insecure {
log.Warn("Using insecure TLS configuration, this should be avoided in production environments")
tlsConfig, err = cryptoutil.InsecureTLSConfig()
} else {
tlsConfig, err = cryptoutil.ServerTLSConfig(caPath, certPath, keyPath)
}
if err != nil {
return fmt.Errorf("could not initialize TLS config: %v", err)
}
port := viper.GetInt("port")
ip := viper.GetString("address")
addr := fmt.Sprintf("%s:%d", ip, port)
lis, err := tls.Listen("tcp", addr, tlsConfig)
if err != nil {
return fmt.Errorf("could not create TCP socket: %v", err)
}
defer lis.Close()
ctx := context.Background()
log.Infof("Starting portal on %s", addr)
return s.Serve(ctx, lis)
},
}
dir := "/etc/portal"
configPath := filepath.Join(dir, "config.toml")
cobra.OnInitialize(initConfig)
rootCmd.AddCommand(initCmd)
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", configPath, "config file")
rootCmd.PersistentFlags().StringP("loglevel", "l", "info", "Log level")
rootCmd.PersistentFlags().BoolP("json", "j", false, "Output logs in JSON format")
rootCmd.Flags().IntP("port", "p", 1337, "Port to listen on for connections")
rootCmd.Flags().StringP("address", "a", "0.0.0.0", "Address to listen on for connections")
rootCmd.Flags().Bool("insecure", false, "Skip client certificate verification")
rootCmd.Flags().String("ca", "ca.crt", "Path to the CA cert")
rootCmd.Flags().String("cert", "portal.crt", "Path to the server cert")
rootCmd.Flags().String("key", "portal.key", "Path to the server key")
viper.BindPFlag("tls.insecure", rootCmd.Flags().Lookup("insecure"))
viper.BindPFlag("tls.ca", rootCmd.Flags().Lookup("ca"))
viper.BindPFlag("tls.cert", rootCmd.Flags().Lookup("cert"))
viper.BindPFlag("tls.key", rootCmd.Flags().Lookup("key"))
viper.BindPFlag("logging.loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
viper.BindPFlag("logging.json", rootCmd.PersistentFlags().Lookup("json"))
viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
viper.BindPFlag("address", rootCmd.Flags().Lookup("address"))
rootCmd.DisableSuggestions = false
if err := rootCmd.Execute(); err != nil {
log.Error(err.Error())
}
}
func initConfig() {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
json := viper.GetBool("logging.json")
if json {
handler := jsonhandler.New(os.Stdout)
log.SetHandler(handler)
} else {
handler := texthandler.New(os.Stdout)
log.SetHandler(handler)
}
if err := viper.ReadInConfig(); err != nil {
log.Warnf("Couldn't read config at \"%s\", starting with default settings", viper.ConfigFileUsed())
}
lvl, err := log.ParseLevel(viper.GetString("logging.loglevel"))
if err != nil {
log.Fatalf("couldn't parse log level: %s (%s)", err, lvl)
return
}
log.SetLevel(lvl)
}

9
cmd/portal/main.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import (
"github.com/speedrunsh/speedrun/cmd/portal/cli"
)
func main() {
cli.Execute()
}

View File

@@ -1,72 +0,0 @@
package cmd
import (
"fmt"
"path/filepath"
"github.com/apex/log"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var version string
var commit string
var date string
//Execute runs the root command
func Execute() {
// cobra.OnInitialize(initConfig)
var rootCmd = &cobra.Command{
Use: "speedrun",
Short: "Cloud first command execution",
Version: fmt.Sprintf("%s, commit: %s, date: %s", version, commit, date),
SilenceUsage: true,
SilenceErrors: true,
}
rootCmd.AddCommand(initCmd)
rootCmd.AddCommand(keyCmd)
rootCmd.AddCommand(runCmd)
home, err := homedir.Dir()
if err != nil {
log.Fatal(err.Error())
}
dir := filepath.Join(home, ".speedrun")
path := filepath.Join(dir, "config.toml")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", path, "config file")
rootCmd.PersistentFlags().StringP("loglevel", "l", "info", "Log level")
viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
if err := rootCmd.Execute(); err != nil {
log.Fatal(err.Error())
}
}
func initConfig() {
dir, file := filepath.Split(cfgFile)
viper.SetConfigName(file)
viper.SetConfigType("toml")
viper.AddConfigPath(dir)
if err := viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
if err != nil {
log.Error(err.Error())
log.Fatal("Run `speedrun init` first")
}
} else {
log.Fatal(err.Error())
}
}
lvl, err := log.ParseLevel(viper.GetString("loglevel"))
if err != nil {
log.Fatalf("Couldn't parse log level: %s", err)
return
}
log.SetLevel(lvl)
}

View File

@@ -1,101 +0,0 @@
package cmd
import (
"speedrun/cloud"
"speedrun/key"
"speedrun/marathon"
"strings"
"time"
"github.com/apex/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var runCmd = &cobra.Command{
Use: "run <command to run>",
Short: "Run command on remote servers",
Example: " speedrun run whoami\n speedrun run whoami --only-failures --target \"labels.foo = bar AND labels.environment = staging\"",
Args: cobra.MinimumNArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initConfig()
},
RunE: run,
}
func init() {
runCmd.Flags().StringP("target", "t", "", "Fetch instances that match the target selection criteria")
runCmd.Flags().String("projectid", "", "Override GCP project id")
runCmd.Flags().Bool("only-failures", false, "Print only failures and errors")
runCmd.Flags().Bool("ignore-fingerprint", false, "Ignore host's fingerprint mismatch")
runCmd.Flags().Duration("timeout", time.Duration(10*time.Second), "SSH connection timeout")
runCmd.Flags().Int("concurrency", 100, "Number of maximum concurrent SSH workers")
runCmd.Flags().Bool("use-private-ip", false, "Connect to private IPs instead of public ones")
runCmd.Flags().Bool("use-oslogin", false, "Authenticate via OS Login")
viper.BindPFlag("gcp.projectid", runCmd.Flags().Lookup("projectid"))
viper.BindPFlag("gcp.use-oslogin", runCmd.Flags().Lookup("use-oslogin"))
viper.BindPFlag("ssh.timeout", runCmd.Flags().Lookup("timeout"))
viper.BindPFlag("ssh.ignore-fingerprint", runCmd.Flags().Lookup("ignore-fingerprint"))
viper.BindPFlag("ssh.only-failures", runCmd.Flags().Lookup("only-failures"))
viper.BindPFlag("ssh.concurrency", runCmd.Flags().Lookup("concurrency"))
viper.BindPFlag("ssh.use-private-ip", runCmd.Flags().Lookup("use-private-ip"))
}
func run(cmd *cobra.Command, args []string) error {
command := strings.Join(args, " ")
project := viper.GetString("gcp.projectid")
timeout := viper.GetDuration("ssh.timeout")
ignoreFingerprint := viper.GetBool("ssh.ignore-fingerprint")
onlyFailures := viper.GetBool("ssh.only-failures")
concurrency := viper.GetInt("ssh.concurrency")
usePrivateIP := viper.GetBool("ssh.use-private-ip")
useOSlogin := viper.GetBool("gcp.use-oslogin")
target, err := cmd.Flags().GetString("target")
if err != nil {
return err
}
gcpClient, err := cloud.NewGCPClient(project)
if err != nil {
return err
}
path, err := determineKeyFilePath()
if err != nil {
return err
}
k, err := key.Read(path)
if err != nil {
return err
}
log.Info("Fetching instance list")
instances, err := gcpClient.GetInstances(target, usePrivateIP)
if err != nil {
return err
}
if len(instances) == 0 {
log.Warn("No instances found")
return nil
}
if useOSlogin {
user, err := gcpClient.GetSAUsername()
if err != nil {
return err
}
k.User = user
}
m := marathon.New(command, timeout, concurrency)
err = m.Run(instances, k, ignoreFingerprint)
if err != nil {
return err
}
m.PrintResult(onlyFailures)
return nil
}

61
cmd/speedrun/cli/init.go Normal file
View File

@@ -0,0 +1,61 @@
package cli
import (
"fmt"
"os"
"path/filepath"
"github.com/apex/log"
"github.com/pelletier/go-toml"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
initCmd.SetUsageTemplate(usage)
initCmd.Flags().BoolP("print", "p", false, "Print default config to stdout")
}
var initCmd = &cobra.Command{
Use: "init",
Short: "Initialize speedrun",
RunE: func(cmd *cobra.Command, args []string) error {
viper.SetDefault("gcp.projectid", "")
print, err := cmd.Flags().GetBool("print")
if err != nil {
return err
}
if print {
c := viper.AllSettings()
bs, err := toml.Marshal(c)
if err != nil {
return fmt.Errorf("unable to marshal config: %v", err)
}
fmt.Println(string(bs))
return nil
}
dir := filepath.Dir(cfgFile)
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.Mkdir(dir, 0755)
if err != nil {
return err
}
}
err = viper.SafeWriteConfigAs(cfgFile)
if err != nil {
if _, ok := err.(viper.ConfigFileAlreadyExistsError); !ok {
return fmt.Errorf("couldn't save config at \"%s\" (%s)", viper.ConfigFileUsed(), err)
}
} else {
log.Infof("Your config was saved at \"%s\"", viper.ConfigFileUsed())
return nil
}
log.Infof("Config already exists at \"%s\", no changes applied", viper.ConfigFileUsed())
return nil
},
}

97
cmd/speedrun/cli/root.go Normal file
View File

@@ -0,0 +1,97 @@
package cli
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"github.com/apex/log"
jsonhandler "github.com/apex/log/handlers/json"
texthandler "github.com/apex/log/handlers/text"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var version string
var commit string
var date string
//go:embed templates/root.tmpl
var rootUsage string
//go:embed templates/usage.tmpl
var usage string
func Execute() {
var rootCmd = &cobra.Command{
Use: "speedrun",
Short: "Control your compute fleet at scale",
Version: fmt.Sprintf("%s, commit: %s, date: %s", version, commit, date),
SilenceUsage: true,
SilenceErrors: true,
}
cobra.OnInitialize(initConfig)
rootCmd.SetUsageTemplate(rootUsage)
rootCmd.AddCommand(initCmd, runCmd, serviceCmd)
home, err := homedir.Dir()
if err != nil {
log.Fatal(err.Error())
}
dir := filepath.Join(home, ".speedrun")
configPath := filepath.Join(dir, "config.toml")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", configPath, "config file")
rootCmd.PersistentFlags().StringP("loglevel", "l", "info", "Log level")
rootCmd.PersistentFlags().BoolP("json", "j", false, "Output logs in JSON format")
rootCmd.PersistentFlags().StringP("target", "t", "", "Fetch instances that match the target selection criteria")
rootCmd.PersistentFlags().Bool("insecure", false, "Skip server certificate verification")
rootCmd.PersistentFlags().String("ca", "ca.crt", "Path to the CA cert")
rootCmd.PersistentFlags().String("cert", "cert.crt", "Path to the client cert")
rootCmd.PersistentFlags().String("key", "key.key", "Path to the client key")
rootCmd.PersistentFlags().Bool("use-private-ip", false, "Connect to private IPs instead of public ones")
viper.BindPFlag("logging.loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
viper.BindPFlag("logging.json", rootCmd.PersistentFlags().Lookup("json"))
viper.BindPFlag("tls.insecure", rootCmd.PersistentFlags().Lookup("insecure"))
viper.BindPFlag("tls.ca", rootCmd.PersistentFlags().Lookup("ca"))
viper.BindPFlag("tls.cert", rootCmd.PersistentFlags().Lookup("cert"))
viper.BindPFlag("tls.key", rootCmd.PersistentFlags().Lookup("key"))
viper.BindPFlag("portal.use-private-ip", rootCmd.PersistentFlags().Lookup("use-private-ip"))
rootCmd.DisableSuggestions = false
if err := rootCmd.Execute(); err != nil {
log.Error(err.Error())
}
}
func initConfig() {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
json := viper.GetBool("logging.json")
if json {
handler := jsonhandler.New(os.Stdout)
log.SetHandler(handler)
} else {
handler := texthandler.New(os.Stdout)
log.SetHandler(handler)
}
if err := viper.ReadInConfig(); err != nil {
log.Warnf("Couldn't read config at \"%s\", starting with default settings", viper.ConfigFileUsed())
}
lvl, err := log.ParseLevel(viper.GetString("logging.loglevel"))
if err != nil {
log.Fatalf("couldn't parse log level: %s (%s)", err, lvl)
return
}
log.SetLevel(lvl)
}

86
cmd/speedrun/cli/run.go Normal file
View File

@@ -0,0 +1,86 @@
package cli
import (
"context"
"crypto/tls"
"fmt"
"strings"
"time"
"github.com/alitto/pond"
"github.com/speedrunsh/speedrun/pkg/speedrun/cloud"
portalpb "github.com/speedrunsh/speedrun/proto/portal"
"storj.io/drpc/drpcconn"
"github.com/apex/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var runCmd = &cobra.Command{
Use: "run <command to run>",
Short: "Run a shell command on remote servers",
Example: " speedrun run whoami\n speedrun run whoami --target \"labels.foo = bar AND labels.environment = staging\"",
Args: cobra.MinimumNArgs(1),
RunE: run,
}
func init() {
runCmd.SetUsageTemplate(usage)
}
func run(cmd *cobra.Command, args []string) error {
command := strings.Join(args, " ")
s := strings.Split(command, " ")
usePrivateIP := viper.GetBool("portal.use-private-ip")
tlsConfig, err := cloud.SetupTLS()
if err != nil {
return err
}
target, err := cmd.Flags().GetString("target")
if err != nil {
return err
}
instances, err := cloud.GetInstances(target)
if err != nil {
return err
}
pool := pond.New(1000, 10000)
for _, p := range instances {
instance := p
pool.Submit(func() {
fields := log.Fields{
"host": instance.Name,
"address": instance.GetAddress(usePrivateIP),
}
log := log.WithFields(fields)
addr := fmt.Sprintf("%s:%d", instance.GetAddress(usePrivateIP), 1337)
rawconn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
log.Error(err.Error())
return
}
conn := drpcconn.New(rawconn)
defer conn.Close()
c := portalpb.NewDRPCPortalClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
r, err := c.RunCommand(ctx, &portalpb.CommandRequest{Name: s[0], Args: s[1:]})
if err != nil {
log.Error(err.Error())
return
}
log.WithField("state", r.GetState()).Info(r.GetMessage())
})
}
pool.StopAndWait()
return nil
}

142
cmd/speedrun/cli/service.go Normal file
View File

@@ -0,0 +1,142 @@
package cli
import (
"context"
"crypto/tls"
"fmt"
"strings"
"time"
"github.com/alitto/pond"
"github.com/apex/log"
"github.com/speedrunsh/speedrun/pkg/speedrun/cloud"
portalpb "github.com/speedrunsh/speedrun/proto/portal"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"storj.io/drpc/drpcconn"
)
var serviceCmd = &cobra.Command{
Use: "service",
Short: "Manage services",
TraverseChildren: true,
}
var restartCmd = &cobra.Command{
Use: "restart <servicename>",
Short: "Restart a service",
Example: " speedrun service restart nginx",
Args: cobra.MinimumNArgs(1),
RunE: action,
}
var startCmd = &cobra.Command{
Use: "start <servicename>",
Short: "Start a service",
Example: " speedrun service start nginx",
Args: cobra.MinimumNArgs(1),
RunE: action,
}
var stopCmd = &cobra.Command{
Use: "stop <servicename>",
Short: "Stop a service",
Example: " speedrun service stop nginx",
Args: cobra.MinimumNArgs(1),
RunE: action,
}
var statusCmd = &cobra.Command{
Use: "status <servicename>",
Short: "Return the status of the service",
Example: " speedrun service status nginx",
Args: cobra.MinimumNArgs(1),
RunE: action,
}
func init() {
serviceCmd.SetUsageTemplate(usage)
serviceCmd.AddCommand(restartCmd)
serviceCmd.AddCommand(startCmd)
serviceCmd.AddCommand(stopCmd)
serviceCmd.AddCommand(statusCmd)
}
func action(cmd *cobra.Command, args []string) error {
usePrivateIP := viper.GetBool("portal.use-private-ip")
tlsConfig, err := cloud.SetupTLS()
if err != nil {
return err
}
target, err := cmd.Flags().GetString("target")
if err != nil {
return err
}
portals, err := cloud.GetInstances(target)
if err != nil {
return err
}
pool := pond.New(1000, 10000)
for _, p := range portals {
portal := p
pool.Submit(func() {
fields := log.Fields{
"host": portal.Name,
"address": portal.GetAddress(usePrivateIP),
}
log := log.WithFields(fields)
addr := fmt.Sprintf("%s:%d", portal.GetAddress(usePrivateIP), 1337)
rawconn, err := tls.Dial("tcp", addr, tlsConfig)
if err != nil {
log.Error(err.Error())
return
}
conn := drpcconn.New(rawconn)
defer conn.Close()
c := portalpb.NewDRPCPortalClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
switch cmd.Name() {
case "restart":
r, err := c.ServiceRestart(ctx, &portalpb.ServiceRequest{Name: strings.Join(args, " ")})
if err != nil {
log.Error(err.Error())
return
}
log.WithField("state", r.GetState()).Info(r.GetMessage())
case "start":
r, err := c.ServiceStart(ctx, &portalpb.ServiceRequest{Name: strings.Join(args, " ")})
if err != nil {
log.Error(err.Error())
return
}
log.WithField("state", r.GetState()).Info(r.GetMessage())
case "stop":
r, err := c.ServiceStop(ctx, &portalpb.ServiceRequest{Name: strings.Join(args, " ")})
if err != nil {
log.Error(err.Error())
return
}
log.WithField("state", r.GetState()).Info(r.GetMessage())
case "status":
r, err := c.ServiceStatus(ctx, &portalpb.ServiceRequest{Name: strings.Join(args, " ")})
if err != nil {
log.Error(err.Error())
return
}
log.WithField("state", r.GetState()).Infof("Loadstate: \"%s\", Activestate: \"%s\", Substate: \"%s\"", r.GetLoadstate(), r.GetActivestate(), r.GetSubstate())
}
})
}
pool.StopAndWait()
return nil
}

View File

@@ -0,0 +1,18 @@
Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}
Core Commands:{{range .Commands}}{{if (or (eq .Name "help") (eq .Name "init") (eq .Name "key") (eq .Name "portal") (eq .Name "completion"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
Action Commands:{{range .Commands}}{{if (or (eq .Name "run") (eq .Name "exec") (eq .Name "service"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}
{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}

View File

@@ -0,0 +1,23 @@
Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}

9
cmd/speedrun/main.go Normal file
View File

@@ -0,0 +1,9 @@
package main
import (
"github.com/speedrunsh/speedrun/cmd/speedrun/cli"
)
func main() {
cli.Execute()
}