github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/cli/util/util.go (about)

     1  // Package cliutil contains methods used across all cli commands
     2  // @todo: get rid of os.Exits and use errors instread
     3  package util
     4  
     5  import (
     6  	"encoding/json"
     7  	"fmt"
     8  	"regexp"
     9  	"strings"
    10  
    11  	merrors "github.com/tickoalcantara12/micro/v3/service/errors"
    12  
    13  	"github.com/tickoalcantara12/micro/v3/util/config"
    14  	"github.com/urfave/cli/v2"
    15  )
    16  
    17  const (
    18  	// EnvLocal is a builtin environment, it represents your local `micro server`
    19  	EnvLocal = "local"
    20  	// EnvPlatform is a builtin highly available environment in the cloud,
    21  	EnvPlatform = "platform"
    22  )
    23  
    24  const (
    25  	// localProxyAddress is the default proxy address for environment server
    26  	localProxyAddress = "127.0.0.1:8081"
    27  	// platformProxyAddress is the default proxy address for environment platform
    28  	platformProxyAddress = "proxy.m3o.com"
    29  )
    30  
    31  var (
    32  	// list of services managed
    33  	// TODO: make use server/server list
    34  	services = []string{
    35  		// runtime services
    36  		"network",  // :8085 (peer), :8443 (proxy)
    37  		"runtime",  // :8088
    38  		"registry", // :8000
    39  		"config",   // :8001
    40  		"store",    // :8002
    41  		"broker",   // :8003
    42  		"router",   // :8084
    43  		"auth",     // :8010
    44  		"proxy",    // :8081
    45  		"api",      // :8080
    46  		"events",
    47  	}
    48  )
    49  
    50  var defaultEnvs = map[string]Env{
    51  	EnvLocal: {
    52  		Name:         EnvLocal,
    53  		ProxyAddress: localProxyAddress,
    54  		Description:  "Local running Micro Server",
    55  	},
    56  	EnvPlatform: {
    57  		Name:         EnvPlatform,
    58  		ProxyAddress: platformProxyAddress,
    59  		Description:  "Cloud hosted Micro Platform",
    60  	},
    61  }
    62  
    63  func IsBuiltInService(command string) bool {
    64  	for _, service := range services {
    65  		if command == service {
    66  			return true
    67  		}
    68  	}
    69  	return false
    70  }
    71  
    72  // CLIProxyAddress returns the proxy address which should be set for the client
    73  func CLIProxyAddress(ctx *cli.Context) (string, error) {
    74  	switch ctx.Args().First() {
    75  	case "new", "server", "help", "env":
    76  		return "", nil
    77  	}
    78  
    79  	// fix for "micro service [command]", e.g "micro service auth"
    80  	if ctx.Args().First() == "service" && IsBuiltInService(ctx.Args().Get(1)) {
    81  		return "", nil
    82  	}
    83  
    84  	// don't set the proxy address on the proxy
    85  	if ctx.Args().First() == "proxy" {
    86  		return "", nil
    87  	}
    88  
    89  	env, err := GetEnv(ctx)
    90  	if err != nil {
    91  		return "", err
    92  	}
    93  	addr := env.ProxyAddress
    94  	if !strings.Contains(addr, ":") {
    95  		return fmt.Sprintf("%v:443", addr), nil
    96  	}
    97  	return addr, nil
    98  }
    99  
   100  type Env struct {
   101  	Name         string
   102  	ProxyAddress string
   103  	Description  string
   104  }
   105  
   106  func AddEnv(env Env) error {
   107  	envs, err := getEnvs()
   108  	if err != nil {
   109  		return err
   110  	}
   111  	envs[env.Name] = env
   112  	return setEnvs(envs)
   113  }
   114  
   115  func getEnvs() (map[string]Env, error) {
   116  	envsJSON, err := config.Get("envs")
   117  	if err != nil {
   118  		return nil, fmt.Errorf("Error getting environment: %v", err)
   119  	}
   120  	envs := map[string]Env{}
   121  	if len(envsJSON) > 0 {
   122  		err := json.Unmarshal([]byte(envsJSON), &envs)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  	}
   127  	for k, v := range defaultEnvs {
   128  		envs[k] = v
   129  	}
   130  	return envs, nil
   131  }
   132  
   133  func setEnvs(envs map[string]Env) error {
   134  	envsJSON, err := json.Marshal(envs)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	return config.Set("envs", string(envsJSON))
   139  }
   140  
   141  // GetEnv returns the current selected environment
   142  // Does not take
   143  func GetEnv(ctx *cli.Context) (Env, error) {
   144  	var envName string
   145  	if len(ctx.String("env")) > 0 {
   146  		envName = ctx.String("env")
   147  	} else {
   148  		env, err := config.Get("env")
   149  		if err != nil {
   150  			return Env{}, err
   151  		}
   152  		if env == "" {
   153  			env = EnvLocal
   154  		}
   155  		envName = env
   156  	}
   157  
   158  	return GetEnvByName(envName)
   159  }
   160  
   161  func GetEnvByName(env string) (Env, error) {
   162  	envs, err := getEnvs()
   163  	if err != nil {
   164  		return Env{}, err
   165  	}
   166  	envir, ok := envs[env]
   167  	if !ok {
   168  		return Env{}, fmt.Errorf("Env \"%s\" not found. See `micro env` for available environments.", env)
   169  	}
   170  	return envir, nil
   171  }
   172  
   173  func GetEnvs() ([]Env, error) {
   174  	envs, err := getEnvs()
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	var ret []Env
   180  
   181  	// populate the default environments
   182  	for _, env := range defaultEnvs {
   183  		ret = append(ret, env)
   184  	}
   185  
   186  	var nonDefaults []Env
   187  
   188  	for _, env := range envs {
   189  		if _, isDefault := defaultEnvs[env.Name]; !isDefault {
   190  			nonDefaults = append(nonDefaults, env)
   191  		}
   192  	}
   193  
   194  	// @todo order nondefault envs alphabetically
   195  	ret = append(ret, nonDefaults...)
   196  
   197  	return ret, nil
   198  }
   199  
   200  // SetEnv selects an environment to be used.
   201  func SetEnv(envName string) error {
   202  	envs, err := getEnvs()
   203  	if err != nil {
   204  		return err
   205  	}
   206  	_, ok := envs[envName]
   207  	if !ok {
   208  		return fmt.Errorf("Environment '%v' does not exist", envName)
   209  	}
   210  	return config.Set("env", envName)
   211  }
   212  
   213  // DelEnv deletes an env from config
   214  func DelEnv(ctx *cli.Context, envName string) error {
   215  	env, err := GetEnv(ctx)
   216  	if err != nil {
   217  		return err
   218  	}
   219  	if env.Name == envName {
   220  		return fmt.Errorf("Environment '%v' is your current environment. Before deleting it, please change your current environment.", envName)
   221  	}
   222  	envs, err := getEnvs()
   223  	if err != nil {
   224  		return err
   225  	}
   226  	_, ok := envs[envName]
   227  	if !ok {
   228  		return fmt.Errorf("Environment '%v' does not exist", envName)
   229  	}
   230  	delete(envs, envName)
   231  	return setEnvs(envs)
   232  }
   233  
   234  func IsPlatform(ctx *cli.Context) bool {
   235  	env, err := GetEnv(ctx)
   236  	if err == nil && env.Name == EnvPlatform {
   237  		return true
   238  	}
   239  	return false
   240  }
   241  
   242  type Exec func(*cli.Context, []string) ([]byte, error)
   243  
   244  func Print(e Exec) func(*cli.Context) error {
   245  	return func(c *cli.Context) error {
   246  		rsp, err := e(c, c.Args().Slice())
   247  		if err != nil {
   248  			return CliError(err)
   249  		}
   250  		if len(rsp) > 0 {
   251  			fmt.Printf("%s\n", string(rsp))
   252  		}
   253  		return nil
   254  	}
   255  }
   256  
   257  // CliError returns a user friendly message from error. If we can't determine a good one returns an error with code 128
   258  func CliError(err error) cli.ExitCoder {
   259  	if err == nil {
   260  		return nil
   261  	}
   262  	// if it's already a cli.ExitCoder we use this
   263  	cerr, ok := err.(cli.ExitCoder)
   264  	if ok {
   265  		return cerr
   266  	}
   267  
   268  	// grpc errors
   269  	if mname := regexp.MustCompile(`malformed method name: \\?"(\w+)\\?"`).FindStringSubmatch(err.Error()); len(mname) > 0 {
   270  		return cli.Exit(fmt.Sprintf(`Method name "%s" invalid format. Expecting service.endpoint`, mname[1]), 3)
   271  	}
   272  	if service := regexp.MustCompile(`service ([\w\.]+): route not found`).FindStringSubmatch(err.Error()); len(service) > 0 {
   273  		return cli.Exit(fmt.Sprintf(`Service "%s" not found`, service[1]), 4)
   274  	}
   275  	if service := regexp.MustCompile(`unknown service ([\w\.]+)`).FindStringSubmatch(err.Error()); len(service) > 0 {
   276  		if strings.Contains(service[0], ".") {
   277  			return cli.Exit(fmt.Sprintf(`Service method "%s" not found`, service[1]), 5)
   278  		}
   279  		return cli.Exit(fmt.Sprintf(`Service "%s" not found`, service[1]), 5)
   280  	}
   281  	if address := regexp.MustCompile(`Error while dialing dial tcp.*?([\w]+\.[\w:\.]+): `).FindStringSubmatch(err.Error()); len(address) > 0 {
   282  		return cli.Exit(fmt.Sprintf(`Failed to connect to micro server at %s`, address[1]), 4)
   283  	}
   284  
   285  	merr, ok := err.(*merrors.Error)
   286  	if !ok {
   287  		return cli.Exit(err, 128)
   288  	}
   289  
   290  	switch merr.Code {
   291  	case 408:
   292  		return cli.Exit("Request timed out", 1)
   293  	case 401:
   294  		// TODO check if not signed in, prompt to sign in
   295  		return cli.Exit("Not authorized to perform this request", 2)
   296  	}
   297  
   298  	// fallback to using the detail from the merr
   299  	return cli.Exit(merr.Detail, 127)
   300  }