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

     1  package cli
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"sort"
    11  	"strings"
    12  	"text/tabwriter"
    13  	"time"
    14  
    15  	"github.com/tickoalcantara12/micro/v3/client/cli/namespace"
    16  	"github.com/tickoalcantara12/micro/v3/client/cli/util"
    17  	"github.com/tickoalcantara12/micro/v3/service/client"
    18  	"github.com/tickoalcantara12/micro/v3/service/context/metadata"
    19  	"github.com/tickoalcantara12/micro/v3/service/registry"
    20  	cbytes "github.com/tickoalcantara12/micro/v3/util/codec/bytes"
    21  	"github.com/serenize/snaker"
    22  	"github.com/urfave/cli/v2"
    23  )
    24  
    25  func quit(c *cli.Context, args []string) ([]byte, error) {
    26  	os.Exit(0)
    27  	return nil, nil
    28  }
    29  
    30  func help(c *cli.Context, args []string) ([]byte, error) {
    31  	w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0)
    32  
    33  	fmt.Fprintln(os.Stdout, "Commands:")
    34  
    35  	var keys []string
    36  	for k := range commands {
    37  		keys = append(keys, k)
    38  	}
    39  	sort.Strings(keys)
    40  
    41  	for _, k := range keys {
    42  		cmd := commands[k]
    43  		fmt.Fprintln(w, "\t", cmd.name, "\t\t", cmd.usage)
    44  	}
    45  
    46  	w.Flush()
    47  	return nil, nil
    48  }
    49  
    50  func formatEndpoint(v *registry.Value, r int) string {
    51  	// default format is tabbed plus the value plus new line
    52  	fparts := []string{"", "%s %s", "\n"}
    53  	for i := 0; i < r+1; i++ {
    54  		fparts[0] += "\t"
    55  	}
    56  	// its just a primitive of sorts so return
    57  	if len(v.Values) == 0 {
    58  		return fmt.Sprintf(strings.Join(fparts, ""), snaker.CamelToSnake(v.Name), v.Type)
    59  	}
    60  
    61  	// this thing has more things, it's complex
    62  	fparts[1] += " {"
    63  
    64  	vals := []interface{}{snaker.CamelToSnake(v.Name), v.Type}
    65  
    66  	for _, val := range v.Values {
    67  		fparts = append(fparts, "%s")
    68  		vals = append(vals, formatEndpoint(val, r+1))
    69  	}
    70  
    71  	// at the end
    72  	l := len(fparts) - 1
    73  	for i := 0; i < r+1; i++ {
    74  		fparts[l] += "\t"
    75  	}
    76  	fparts = append(fparts, "}\n")
    77  
    78  	return fmt.Sprintf(strings.Join(fparts, ""), vals...)
    79  }
    80  
    81  func callContext(c *cli.Context) context.Context {
    82  	callMD := make(map[string]string)
    83  
    84  	for _, md := range c.StringSlice("metadata") {
    85  		parts := strings.Split(md, "=")
    86  		if len(parts) < 2 {
    87  			continue
    88  		}
    89  
    90  		key := parts[0]
    91  		val := strings.Join(parts[1:], "=")
    92  
    93  		// set the key/val
    94  		callMD[key] = val
    95  	}
    96  
    97  	return metadata.NewContext(context.Background(), callMD)
    98  }
    99  
   100  func GetService(c *cli.Context, args []string) ([]byte, error) {
   101  	if len(args) == 0 {
   102  		return nil, cli.ShowSubcommandHelp(c)
   103  	}
   104  
   105  	env, err := util.GetEnv(c)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	ns, err := namespace.Get(env.Name)
   110  	if err != nil {
   111  		return nil, err
   112  	}
   113  
   114  	var output []string
   115  	var srv []*registry.Service
   116  
   117  	srv, err = registry.DefaultRegistry.GetService(args[0], registry.GetDomain(ns))
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	if len(srv) == 0 {
   122  		return nil, errors.New("Service not found")
   123  	}
   124  
   125  	output = append(output, "service  "+srv[0].Name)
   126  
   127  	for _, serv := range srv {
   128  		if len(serv.Version) > 0 {
   129  			output = append(output, "\nversion "+serv.Version)
   130  		}
   131  
   132  		output = append(output, "\nID\tAddress\tMetadata")
   133  		for _, node := range serv.Nodes {
   134  			var meta []string
   135  			for k, v := range node.Metadata {
   136  				meta = append(meta, k+"="+v)
   137  			}
   138  			output = append(output, fmt.Sprintf("%s\t%s\t%s", node.Id, node.Address, strings.Join(meta, ",")))
   139  		}
   140  	}
   141  
   142  	for _, e := range srv[0].Endpoints {
   143  		var request, response string
   144  		var meta []string
   145  		for k, v := range e.Metadata {
   146  			meta = append(meta, k+"="+v)
   147  		}
   148  		if e.Request != nil && len(e.Request.Values) > 0 {
   149  			request = "{\n"
   150  			for _, v := range e.Request.Values {
   151  				request += formatEndpoint(v, 0)
   152  			}
   153  			request += "}"
   154  		} else {
   155  			request = "{}"
   156  		}
   157  		if e.Response != nil && len(e.Response.Values) > 0 {
   158  			response = "{\n"
   159  			for _, v := range e.Response.Values {
   160  				response += formatEndpoint(v, 0)
   161  			}
   162  			response += "}"
   163  		} else {
   164  			response = "{}"
   165  		}
   166  
   167  		output = append(output, fmt.Sprintf("\nEndpoint: %s\n", e.Name))
   168  
   169  		// set metadata if exists
   170  		if len(meta) > 0 {
   171  			output = append(output, fmt.Sprintf("Metadata: %s\n", strings.Join(meta, ",")))
   172  		}
   173  
   174  		output = append(output, fmt.Sprintf("Request: %s\n\nResponse: %s\n", request, response))
   175  	}
   176  
   177  	return []byte(strings.Join(output, "\n")), nil
   178  }
   179  
   180  func ListServices(c *cli.Context, args []string) ([]byte, error) {
   181  	var rsp []*registry.Service
   182  	var err error
   183  
   184  	env, err := util.GetEnv(c)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	ns, err := namespace.Get(env.Name)
   189  	if err != nil {
   190  		return nil, err
   191  	}
   192  
   193  	rsp, err = registry.DefaultRegistry.ListServices(registry.ListDomain(ns))
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	var services []string
   199  	for _, service := range rsp {
   200  		services = append(services, service.Name)
   201  	}
   202  
   203  	sort.Strings(services)
   204  
   205  	return []byte(strings.Join(services, "\n")), nil
   206  }
   207  
   208  func Publish(c *cli.Context, args []string) error {
   209  	if len(args) < 2 {
   210  		return cli.ShowSubcommandHelp(c)
   211  	}
   212  	defer func() {
   213  		time.Sleep(time.Millisecond * 100)
   214  	}()
   215  	topic := args[0]
   216  	message := args[1]
   217  
   218  	ct := func(o *client.MessageOptions) {
   219  		o.ContentType = "application/json"
   220  	}
   221  
   222  	d := json.NewDecoder(strings.NewReader(message))
   223  	d.UseNumber()
   224  
   225  	var msg map[string]interface{}
   226  	if err := d.Decode(&msg); err != nil {
   227  		return cli.Exit(fmt.Sprintf("Error creating request %s", err), 1)
   228  	}
   229  
   230  	ctx := callContext(c)
   231  	m := client.DefaultClient.NewMessage(topic, msg, ct)
   232  	return client.Publish(ctx, m)
   233  }
   234  
   235  func CallService(c *cli.Context, args []string) ([]byte, error) {
   236  	if len(args) < 2 {
   237  		return nil, cli.ShowSubcommandHelp(c)
   238  	}
   239  
   240  	var req, service, endpoint string
   241  	service = args[0]
   242  	endpoint = args[1]
   243  
   244  	if len(args) > 2 {
   245  		req = strings.Join(args[2:], " ")
   246  	}
   247  
   248  	// empty request
   249  	if len(req) == 0 {
   250  		req = `{}`
   251  	}
   252  
   253  	var request map[string]interface{}
   254  	var response []byte
   255  
   256  	d := json.NewDecoder(strings.NewReader(req))
   257  	d.UseNumber()
   258  
   259  	if err := d.Decode(&request); err != nil {
   260  		return nil, cli.Exit(fmt.Sprintf("Error creating request %s", err), 1)
   261  	}
   262  
   263  	ctx := callContext(c)
   264  
   265  	creq := client.DefaultClient.NewRequest(service, endpoint, request, client.WithContentType("application/json"))
   266  
   267  	opts := []client.CallOption{client.WithAuthToken()}
   268  	if timeout := c.String("request_timeout"); timeout != "" {
   269  		duration, err := time.ParseDuration(timeout)
   270  		if err != nil {
   271  			return nil, cli.Exit("Invalid format for request_timeout duration. Try 500ms or 5s", 2)
   272  		}
   273  		opts = append(opts, client.WithRequestTimeout(duration))
   274  	}
   275  
   276  	if addr := c.String("address"); len(addr) > 0 {
   277  		opts = append(opts, client.WithAddress(addr))
   278  	}
   279  
   280  	var err error
   281  	if output := c.String("output"); output == "raw" {
   282  		rsp := cbytes.Frame{}
   283  		err = client.DefaultClient.Call(ctx, creq, &rsp, opts...)
   284  		// set the raw output
   285  		response = rsp.Data
   286  	} else {
   287  		var rsp json.RawMessage
   288  		err = client.DefaultClient.Call(ctx, creq, &rsp, opts...)
   289  		// set the response
   290  		if err == nil {
   291  			var out bytes.Buffer
   292  			defer out.Reset()
   293  			if err := json.Indent(&out, rsp, "", "\t"); err != nil {
   294  				return nil, cli.Exit("Error while trying to format the response", 3)
   295  			}
   296  			response = out.Bytes()
   297  		}
   298  	}
   299  
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	return response, nil
   305  }
   306  
   307  func getEnv(c *cli.Context, args []string) ([]byte, error) {
   308  	env, err := util.GetEnv(c)
   309  	if err != nil {
   310  		return nil, err
   311  	}
   312  	return []byte(env.Name), nil
   313  }
   314  
   315  func setEnv(c *cli.Context, args []string) ([]byte, error) {
   316  	if len(args) == 0 {
   317  		return nil, cli.ShowSubcommandHelp(c)
   318  	}
   319  	return nil, util.SetEnv(args[0])
   320  }
   321  
   322  func listEnvs(c *cli.Context, args []string) ([]byte, error) {
   323  	envs, err := util.GetEnvs()
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	sort.Slice(envs, func(i, j int) bool { return envs[i].Name < envs[j].Name })
   328  	current, err := util.GetEnv(c)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  
   333  	byt := bytes.NewBuffer([]byte{})
   334  
   335  	w := tabwriter.NewWriter(byt, 0, 0, 1, ' ', 0)
   336  	for i, env := range envs {
   337  		if i > 0 {
   338  			fmt.Fprintf(w, "\n")
   339  		}
   340  		prefix := " "
   341  		if env.Name == current.Name {
   342  			prefix = "*"
   343  		}
   344  		if env.ProxyAddress == "" {
   345  			env.ProxyAddress = "none"
   346  		}
   347  		fmt.Fprintf(w, "%v %v \t %v \t\t %v", prefix, env.Name, env.ProxyAddress, env.Description)
   348  	}
   349  	w.Flush()
   350  	return byt.Bytes(), nil
   351  }
   352  
   353  func addEnv(c *cli.Context, args []string) ([]byte, error) {
   354  	if len(args) == 0 {
   355  		return nil, cli.ShowSubcommandHelp(c)
   356  	}
   357  	if len(args) == 1 {
   358  		args = append(args, "") // default to no proxy address
   359  	}
   360  
   361  	return nil, util.AddEnv(util.Env{
   362  		Name:         args[0],
   363  		ProxyAddress: args[1],
   364  	})
   365  }
   366  
   367  func delEnv(c *cli.Context, args []string) ([]byte, error) {
   368  	if len(args) == 0 {
   369  		return nil, cli.ShowSubcommandHelp(c)
   370  	}
   371  	return nil, util.DelEnv(c, args[0])
   372  }
   373  
   374  // TODO: stream via HTTP
   375  func streamService(c *cli.Context, args []string) ([]byte, error) {
   376  	if len(args) < 2 {
   377  		return nil, cli.ShowSubcommandHelp(c)
   378  	}
   379  	service := args[0]
   380  	endpoint := args[1]
   381  	var request map[string]interface{}
   382  
   383  	// ignore error
   384  	json.Unmarshal([]byte(strings.Join(args[2:], " ")), &request)
   385  
   386  	ctx := callContext(c)
   387  	opts := []client.CallOption{client.WithAuthToken()}
   388  
   389  	req := client.DefaultClient.NewRequest(service, endpoint, request, client.WithContentType("application/json"))
   390  	stream, err := client.DefaultClient.Stream(ctx, req, opts...)
   391  	if err != nil {
   392  		if cerr := util.CliError(err); cerr.ExitCode() != 128 {
   393  			return nil, cerr
   394  		}
   395  		return nil, fmt.Errorf("error calling %s.%s: %v", service, endpoint, err)
   396  	}
   397  
   398  	if err := stream.Send(request); err != nil {
   399  		if cerr := util.CliError(err); cerr.ExitCode() != 128 {
   400  			return nil, cerr
   401  		}
   402  		return nil, fmt.Errorf("error sending to %s.%s: %v", service, endpoint, err)
   403  	}
   404  
   405  	output := c.String("output")
   406  
   407  	for {
   408  		if output == "raw" {
   409  			rsp := cbytes.Frame{}
   410  			if err := stream.Recv(&rsp); err != nil && err.Error() == "EOF" {
   411  				return nil, nil
   412  			} else if err != nil {
   413  				if cerr := util.CliError(err); cerr.ExitCode() != 128 {
   414  					return nil, cerr
   415  				}
   416  				return nil, fmt.Errorf("error receiving from %s.%s: %v", service, endpoint, err)
   417  			}
   418  			fmt.Print(string(rsp.Data))
   419  		} else {
   420  			var response map[string]interface{}
   421  			if err := stream.Recv(&response); err != nil && err.Error() == "EOF" {
   422  				return nil, nil
   423  			} else if err != nil {
   424  				if cerr := util.CliError(err); cerr.ExitCode() != 128 {
   425  					return nil, cerr
   426  				}
   427  				return nil, fmt.Errorf("error receiving from %s.%s: %v", service, endpoint, err)
   428  			}
   429  			b, _ := json.MarshalIndent(response, "", "\t")
   430  			fmt.Print(string(b))
   431  		}
   432  	}
   433  }
   434  
   435  func publish(c *cli.Context, args []string) ([]byte, error) {
   436  	if err := Publish(c, args); err != nil {
   437  		return nil, err
   438  	}
   439  	return []byte(`ok`), nil
   440  }