Skip to content

Commit

Permalink
When a reserved variable is set in a parameter, you need to determine…
Browse files Browse the repository at this point in the history
… whether it is also a query parameter required by the API.
  • Loading branch information
CodeSpaceiiii committed Feb 16, 2025
1 parent c88b08a commit 5b5cc1a
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 9 deletions.
23 changes: 16 additions & 7 deletions cli/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ func NewHelpFlag() *Flag {

// CLI Command Context
type Context struct {
help bool
flags *FlagSet
unknownFlags *FlagSet
command *Command
completion *Completion
stdout io.Writer
stderr io.Writer
help bool
flags *FlagSet
unknownFlags *FlagSet
command *Command
completion *Completion
stdout io.Writer
stderr io.Writer
inConfigureMode bool
}

func (ctx *Context) InConfigureMode() bool {
return ctx.inConfigureMode
}

func NewCommandContext(stdout io.Writer, stderr io.Writer) *Context {
Expand Down Expand Up @@ -147,3 +152,7 @@ func (ctx *Context) detectFlagByShorthand(ch rune) (*Flag, error) {
}
return nil, fmt.Errorf("unknown flag -%s", string(ch))
}

func (ctx *Context) SetInConfigureMode(mode bool) {
ctx.inConfigureMode = mode
}
5 changes: 4 additions & 1 deletion config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,10 @@ func LoadProfileWithContext(ctx *cli.Context) (profile Profile, err error) {
}

// Load from flags
profile.OverwriteWithFlags(ctx)
if ctx.InConfigureMode() {
// If not in configure mode, we will not overwrite the profile with flags
profile.OverwriteWithFlags(ctx)
}
err = profile.Validate()
return
}
Expand Down
1 change: 1 addition & 0 deletions config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ func TestLoadProfileWithContextWhenIGNORE_PROFILE(t *testing.T) {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
ctx := cli.NewCommandContext(stdout, stderr)
ctx.SetInConfigureMode(true)
AddFlags(ctx.Flags())
ctx.Flags().Get("access-key-id").SetAssigned(true)
ctx.Flags().Get("access-key-id").SetValue("test-ak-id")
Expand Down
1 change: 1 addition & 0 deletions main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func Main(args []string) {
ctx := cli.NewCommandContext(stdout, stderr)
ctx.EnterCommand(rootCmd)
ctx.SetCompletion(cli.ParseCompletionForShell())
ctx.SetInConfigureMode(openapi.DetectInConfigureMode(ctx.Flags()))

rootCmd.AddSubCommand(config.NewConfigureCommand())
rootCmd.AddSubCommand(lib.NewOssCommand())
Expand Down
36 changes: 36 additions & 0 deletions meta/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package meta

import (
"encoding/json"
"regexp"
"sort"
"strings"

Expand Down Expand Up @@ -74,6 +75,36 @@ func (a *Repository) GetApi(productCode string, version string, apiName string)
return result, true
}

func (a *Repository) GetApiByPath(productCode string, version string, method string, path string) (Api, bool) {
var result Api
product, ok := a.GetProduct(productCode)
if !ok {
return result, false
}
// list all apis
for _, apiName := range product.ApiNames {
err := ReadJsonFrom(strings.ToLower(product.Code)+"/"+apiName+".json", &result)
if err != nil {
return result, false
}
// method not equal
if result.Method != method {
continue
}
// replace all [something] to *
// example /permissions/users/[uid]/update to /permissions/users/*/update
var pattern = ReplacePathPattern(result.PathPattern)
// match path
re := regexp.MustCompile("^" + pattern + "$")
if re.MatchString(path) {
result.Product = &product
return result, true
}
}
result = Api{}
return result, false
}

//go:embed versions.json
var versions []byte

Expand Down Expand Up @@ -118,3 +149,8 @@ func (a *Repository) GetStyle(productCode, version string) (string, bool) {

return "", false
}

func ReplacePathPattern(pattern string) string {
re := regexp.MustCompile(`\[[^\]]+\]`)
return re.ReplaceAllString(pattern, "[0-9a-zA-Z_\\-\\.{}]+")
}
114 changes: 114 additions & 0 deletions meta/repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,117 @@ func TestGetStyle(t *testing.T) {
_, ok = repository.GetStyle("invalid_product", "2016-11-11")
assert.False(t, ok)
}

func TestReplacePathPattern(t *testing.T) {
type args struct {
pattern string
}
tests := []struct {
name string
args args
want string
}{
{
name: "test1",
args: args{
pattern: "/permissions/users/[uid]/update",
},
want: "/permissions/users/[0-9a-zA-Z_\\-\\.{}]+/update",
},
{
name: "test2",
args: args{
pattern: "/permissions/users/[uid]/update/[id]",
},
want: "/permissions/users/[0-9a-zA-Z_\\-\\.{}]+/update/[0-9a-zA-Z_\\-\\.{}]+",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, ReplacePathPattern(tt.args.pattern), "ReplacePathPattern(%v)", tt.args.pattern)
})
}
}

func TestRepository_GetApiByPath(t *testing.T) {
type fields struct {
Products []Product
Names []string
index map[string]Product
}
type args struct {
productCode string
version string
method string
path string
}
tests := []struct {
name string
fields fields
args args
want Api
want1 bool
}{
{
name: "test1",
fields: fields{
Products: []Product{
{
Code: "cs",
ApiNames: []string{"UpdateUserPermissions"},
},
},
Names: []string{"cs"},
index: map[string]Product{
"cs": {
Code: "cs",
ApiNames: []string{"UpdateUserPermissions"},
},
},
},
args: args{
productCode: "cs",
version: "2015-12-15",
method: "POST",
path: "/permissions/users/xx/update",
},
want1: true,
},
{
name: "test2",
fields: fields{
Products: []Product{
{
Code: "cs",
ApiNames: []string{"UpdateUserPermissions"},
},
},
Names: []string{"cs"},
index: map[string]Product{
"cs": {
Code: "cs",
ApiNames: []string{"UpdateUserPermissions"},
},
},
},
args: args{
productCode: "cs",
version: "2015-12-15",
method: "POST",
path: "/permissions/users/xx/update2",
},
want1: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := &Repository{
Products: tt.fields.Products,
Names: tt.fields.Names,
index: tt.fields.index,
}
_, got1 := a.GetApiByPath(tt.args.productCode, tt.args.version, tt.args.method, tt.args.path)
assert.Equalf(t, tt.want1, got1, "GetApiByPath(%v, %v, %v, %v)", tt.args.productCode, tt.args.version, tt.args.method, tt.args.path)
})
}
}
89 changes: 89 additions & 0 deletions openapi/commando.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,72 @@ func (c *Commando) InitWithCommand(cmd *cli.Command) {
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)
Expand Down Expand Up @@ -96,14 +155,27 @@ func (c *Commando) main(ctx *cli.Context, args []string) error {
}
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"),
Expand Down Expand Up @@ -418,3 +490,20 @@ func (c *Commando) printUsage(ctx *cli.Context) {
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)
}
}
}
Loading

0 comments on commit 5b5cc1a

Please sign in to comment.