openapi/commando.go (395 lines of code) (raw):

// Copyright (c) 2009-present, Alibaba Cloud All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package openapi import ( "bytes" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses" "github.com/aliyun/aliyun-cli/v3/cli" "github.com/aliyun/aliyun-cli/v3/config" "github.com/aliyun/aliyun-cli/v3/i18n" "github.com/aliyun/aliyun-cli/v3/meta" "encoding/json" "fmt" "io" "strings" ) // main entrance of aliyun cli type Commando struct { profile config.Profile library *Library } var hookdo = func(fn func() (*responses.CommonResponse, error)) func() (*responses.CommonResponse, error) { return fn } func NewCommando(w io.Writer, profile config.Profile) *Commando { r := &Commando{ profile: profile, } r.library = NewLibrary(w, profile.Language) //TODO: load from local repository return r } func (c *Commando) InitWithCommand(cmd *cli.Command) { cmd.Run = c.main cmd.Help = c.help cmd.AutoComplete = c.complete } func DetectInConfigureMode(flags *cli.FlagSet) bool { _, modeExist := flags.GetValue(config.ModeFlagName) if !modeExist { return true } // if mode exist, check if other flags exist _, akExist := flags.GetValue(config.AccessKeyIdFlagName) if akExist { return true } _, skExist := flags.GetValue(config.AccessKeySecretFlagName) if skExist { return true } _, stsExist := flags.GetValue(config.StsTokenFlagName) if stsExist { return true } // RamRoleNameFlagName _, ramRoleNameExist := flags.GetValue(config.RamRoleNameFlagName) if ramRoleNameExist { return true } // RamRoleArnFlagName _, ramRoleArnExist := flags.GetValue(config.RamRoleArnFlagName) if ramRoleArnExist { return true } // RoleSessionNameFlagName _, roleSessionNameExist := flags.GetValue(config.RoleSessionNameFlagName) if roleSessionNameExist { return true } // PrivateKeyFlagName _, privateKeyExist := flags.GetValue(config.PrivateKeyFlagName) if privateKeyExist { return true } // KeyPairNameFlagName _, keyPairNameExist := flags.GetValue(config.KeyPairNameFlagName) if keyPairNameExist { return true } // OIDCProviderARNFlagName _, oidcProviderArnExist := flags.GetValue(config.OIDCProviderARNFlagName) if oidcProviderArnExist { return true } // OIDCTokenFileFlagName _, oidcTokenFileExist := flags.GetValue(config.OIDCTokenFileFlagName) if oidcTokenFileExist { return true } return false } func (c *Commando) main(ctx *cli.Context, args []string) error { // aliyun if len(args) == 0 { c.printUsage(ctx) return nil } // detect if in configure mode ctx.SetInConfigureMode(DetectInConfigureMode(ctx.Flags())) // update current `Profile` with flags var err error c.profile, err = config.LoadProfileWithContext(ctx) if err != nil { return cli.NewErrorWithTip(err, "Configuration failed, use `aliyun configure` to configure it") } err = c.profile.Validate() if err != nil { return cli.NewErrorWithTip(err, "Configuration failed, use `aliyun configure` to configure it.") } i18n.SetLanguage(c.profile.Language) // process following commands: // aliyun <productCode> // aliyun <productCode> <method> --param1 value1 // aliyun <productCode> GET <path> productName := args[0] if len(args) == 1 { // aliyun <productCode> // TODO: aliyun pluginName ... return c.library.PrintProductUsage(productName, true) } else if len(args) == 2 { // rpc or restful call // aliyun <productCode> <method> --param1 value1 product, _ := c.library.GetProduct(args[0]) if product.Code != "" { if version, _ := ctx.Flags().Get("version").GetValue(); version != "" { if style, ok := c.library.GetStyle(productName, version); ok { product.ApiStyle = style } else { return cli.NewErrorWithTip(fmt.Errorf("unchecked version %s", version), "Please contact the customer support to get more info about API version") } } } if product.ApiStyle == "restful" { api, _ := c.library.GetApi(product.Code, product.Version, args[1]) c.CheckApiParamWithBuildInArgs(ctx, api) ctx.Command().Name = args[1] return c.processInvoke(ctx, productName, api.Method, api.PathPattern) } else { // RPC need check API parameters too api, _ := c.library.GetApi(product.Code, product.Version, args[1]) c.CheckApiParamWithBuildInArgs(ctx, api) } return c.processInvoke(ctx, productName, args[1], "") } else if len(args) == 3 { // restful call // aliyun <productCode> {GET|PUT|POST|DELETE} <path> -- product, _ := c.library.GetProduct(productName) api, find := c.library.GetApiByPath(product.Code, product.Version, args[1], args[2]) if !find { // throw error, can not find api by path return cli.NewErrorWithTip(fmt.Errorf("can not find api by path %s", args[2]), "Please confirm if the API path exists") } c.CheckApiParamWithBuildInArgs(ctx, api) return c.processInvoke(ctx, productName, args[1], args[2]) } else { return cli.NewErrorWithTip(fmt.Errorf("too many arguments"), "Use `aliyun --help` to show usage") } } func (c *Commando) processInvoke(ctx *cli.Context, productCode string, apiOrMethod string, path string) error { // create specific invoker invoker, err := c.createInvoker(ctx, productCode, apiOrMethod, path) if err != nil { return err } err = invoker.Prepare(ctx) if err != nil { return err } // process --dryrun if DryRunFlag(ctx.Flags()).IsAssigned() { invoker.getRequest().TransToAcsRequest() invoker.getClient().BuildRequestWithSigner(invoker.getRequest(), nil) cli.Printf(ctx.Stdout(), "Skip invoke in dry-run mode, request is:\n------------------------------------\n%s\n", invoker.getRequest().String()) return nil } // if invoke with helper out, err, ok := c.invokeWithHelper(invoker) // cli.Printf("invoker %v %v \n", invoker, reflect.TypeOf(invoker)) if ok { if err != nil { // call with helper failed return err } } else { resp, err := hookdo(invoker.Call)() if err != nil { // if unmarshal failed, if !strings.Contains(strings.ToLower(err.Error()), "unmarshal") { return err } } out = resp.GetHttpContentString() } // if `--quiet` assigned. do not print anything if QuietFlag(ctx.Flags()).IsAssigned() { return nil } // process `--output ...` if filter := GetOutputFilter(ctx); filter != nil { out, err = filter.FilterOutput(out) if err != nil { return err } } out = sortJSON(out) cli.Println(ctx.Stdout(), out) return nil } func sortJSON(content string) string { var v interface{} dec := json.NewDecoder(bytes.NewReader([]byte(content))) dec.UseNumber() err := dec.Decode(&v) if err != nil { return content } buf := new(bytes.Buffer) encoder := json.NewEncoder(buf) encoder.SetEscapeHTML(false) encoder.SetIndent("", "\t") err = encoder.Encode(v) if err != nil { return content } return strings.TrimSuffix(buf.String(), "\n") } // invoke with helper func (c *Commando) invokeWithHelper(invoker Invoker) (resp string, err error, ok bool) { if pager := GetPager(); pager != nil { // cli.Printf("call with pager") resp, err = pager.CallWith(invoker) ok = true return } if waiter := GetWaiter(); waiter != nil { // cli.Printf("call with waiter") resp, err = waiter.CallWith(invoker) ok = true return } ok = false return } // create invoker for specific case // rpc: RpcInvoker, ForceRpcInvoker // restful: RestfulInvoker func (c *Commando) createInvoker(ctx *cli.Context, productCode string, apiOrMethod string, path string) (Invoker, error) { force := ForceFlag(ctx.Flags()).IsAssigned() basicInvoker := NewBasicInvoker(&c.profile) // // get product info if product, ok := c.library.GetProduct(productCode); ok { err := basicInvoker.Init(ctx, &product) if err != nil { return nil, err } if force { if version, _ := ctx.Flags().Get("version").GetValue(); version != "" { if style, ok := c.library.GetStyle(productCode, version); ok { product.ApiStyle = style } else { // 没有在 versions.json 中配置的版本可以通过 --style 自行指定 style, _ := ctx.Flags().Get("style").GetValue() if style == "" { return nil, cli.NewErrorWithTip(fmt.Errorf("uncheked version %s", version), "Please use --style to specify API style, rpc or restful.") } product.ApiStyle = style } } } if strings.ToLower(product.ApiStyle) == "rpc" { // // Rpc call if path != "" { return nil, cli.NewErrorWithTip(fmt.Errorf("invalid argument %s", path), "Use `aliyun help %s` see more information.", product.GetLowerCode()) } if force { return &ForceRpcInvoker{ basicInvoker, apiOrMethod, }, nil } if api, ok := c.library.GetApi(product.Code, product.Version, apiOrMethod); ok { return &RpcInvoker{ basicInvoker, &api, }, nil } return nil, &InvalidApiError{apiOrMethod, &product} } // // Restful Call // aliyun cs GET /clusters // aliyun cs /clusters --roa GET ok, method, path, err := checkRestfulMethod(ctx, apiOrMethod, path) if err != nil { return nil, err } if !ok { return nil, cli.NewErrorWithTip(fmt.Errorf("product '%s' need restful call", product.GetLowerCode()), "Use `aliyun %s {GET|PUT|POST|DELETE} <path> ...`", product.GetLowerCode()) } if api, ok := c.library.GetApi(product.Code, product.Version, ctx.Command().Name); ok { return &RestfulInvoker{ basicInvoker, method, path, force, &api, }, nil } return &RestfulInvoker{ basicInvoker, method, path, force, nil, }, nil } else { if !force { return nil, &InvalidProductError{Code: productCode, library: c.library} } // // force call on unknown product, use temporary product info product = meta.Product{ Code: productCode, } err := basicInvoker.Init(ctx, &product) if err != nil { return nil, err } // // Restful Call // aliyun cs GET /clusters // aliyun cs /clusters --roa GET ok, method, path, err := checkRestfulMethod(ctx, apiOrMethod, path) if err != nil { return nil, err } if ok { return &RestfulInvoker{ basicInvoker, method, path, force, nil, }, nil } return &ForceRpcInvoker{ basicInvoker, apiOrMethod, }, nil } } func (c *Commando) help(ctx *cli.Context, args []string) error { cmd := ctx.Command() if len(args) == 0 { cmd.PrintHead(ctx) cmd.PrintUsage(ctx) cmd.PrintFlags(ctx) cmd.PrintSample(ctx) c.library.PrintProducts() cmd.PrintTail(ctx) return nil } else if len(args) == 1 { cmd.PrintHead(ctx) return c.library.PrintProductUsage(args[0], true) } else if len(args) == 2 { cmd.PrintHead(ctx) return c.library.PrintApiUsage(args[0], args[1]) } else { return fmt.Errorf("too many arguments: %d", len(args)) } } func (c *Commando) complete(ctx *cli.Context, args []string) []string { w := ctx.Stdout() r := make([]string, 0) // // aliyun if len(args) == 0 { // Case insensitive strings.ToLower() ctx.Command().ExecuteComplete(ctx, args) for _, p := range c.library.GetProducts() { if !strings.HasPrefix(p.GetLowerCode(), strings.ToLower(ctx.Completion().Current)) { continue } cli.PrintfWithColor(w, "", "%s\n", p.GetLowerCode()) } return r } product, ok := c.library.GetProduct(args[0]) if !ok { return r } if product.ApiStyle == "rpc" { if len(args) == 1 { for _, name := range product.ApiNames { if !strings.HasPrefix(strings.ToLower(name), strings.ToLower(ctx.Completion().Current)) { continue } cli.PrintfWithColor(w, "", "%s\n", name) } return r } api, ok := c.library.GetApi(product.Code, product.Version, args[1]) if !ok { return r } api.ForeachParameters(func(s string, p meta.Parameter) { if strings.HasPrefix("--"+strings.ToLower(s), strings.ToLower(ctx.Completion().Current)) && !p.Hidden { cli.Printf(ctx.Stdout(), "--%s\n", s) } }) } else if product.ApiStyle == "restful" { if len(args) == 1 { cli.PrintfWithColor(w, "", "GET\n") cli.PrintfWithColor(w, "", "POST\n") cli.PrintfWithColor(w, "", "DELETE\n") cli.PrintfWithColor(w, "", "PUT\n") return r } } return r } func (c *Commando) printUsage(ctx *cli.Context) { cmd := ctx.Command() cmd.PrintHead(ctx) cmd.PrintUsage(ctx) cmd.PrintSubCommands(ctx) cmd.PrintFlags(ctx) cmd.PrintSample(ctx) cmd.PrintTail(ctx) } func (c *Commando) CheckApiParamWithBuildInArgs(ctx *cli.Context, api meta.Api) { for _, p := range api.Parameters { // 如果参数中包含了known参数,且 known参数已经被赋值,则将 known 参数拷贝给 unknown 参数 if ep, ok := ctx.Flags().GetValue(p.Name); ok { if p.Position != "Query" { continue } var flagNew = &cli.Flag{ Name: p.Name, } flagNew.SetValue(ep) flagNew.SetAssigned(true) ctx.UnknownFlags().Add(flagNew) } } }