Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When a reserved variable is set in a parameter, you need to determine whether it is also a query parameter required by the API. #1185

Merged
merged 1 commit into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}
12 changes: 12 additions & 0 deletions cli/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,15 @@ func TestDetectFlagByShorthand(t *testing.T) {
assert.Nil(t, f)
assert.EqualError(t, err, "unknown flag -c")
}

func TestSetInConfigureMode(t *testing.T) {
w := new(bytes.Buffer)
stderr := new(bytes.Buffer)
ctx := NewCommandContext(w, stderr)

ctx.SetInConfigureMode(true)
assert.True(t, ctx.InConfigureMode())

ctx.SetInConfigureMode(false)
assert.False(t, ctx.InConfigureMode())
}
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
Loading