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 }