From d1f0dcffd300687db3778f126ff95c58b79843c7 Mon Sep 17 00:00:00 2001 From: Xinyi Wang Date: Wed, 4 Oct 2023 08:42:19 -0700 Subject: [PATCH 1/5] gvk partial inference --- command/apiresources/apiresources.go | 59 ++++++++++++++++++++++++++++ command/registry.go | 2 + command/resource/helper.go | 56 ++++++++++++++++++++------ 3 files changed, 104 insertions(+), 13 deletions(-) create mode 100644 command/apiresources/apiresources.go diff --git a/command/apiresources/apiresources.go b/command/apiresources/apiresources.go new file mode 100644 index 00000000000..d3fa4566ca3 --- /dev/null +++ b/command/apiresources/apiresources.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package apiresources + +import ( + "encoding/json" + "flag" + + "github.com/hashicorp/consul/command/cli" + + "github.com/hashicorp/consul/command/flags" + "github.com/hashicorp/consul/command/resource" +) + +func New(ui cli.Ui) *cmd { + c := &cmd{UI: ui} + c.init() + return c +} + +type cmd struct { + UI cli.Ui + flags *flag.FlagSet + help string +} + +func (c *cmd) init() { + c.flags = flag.NewFlagSet("", flag.ContinueOnError) + c.help = flags.Usage(help, c.flags) +} + +func (c *cmd) Run(args []string) int { + kindToGVKMap := resource.BuildKindToGVKMap() + b, err := json.MarshalIndent(kindToGVKMap, "", " ") + if err != nil { + c.UI.Error("Failed to encode output data") + return 1 + } + + c.UI.Info(string(b)) + return 0 +} + +func (c *cmd) Synopsis() string { + return synopsis +} + +func (c *cmd) Help() string { + return flags.Usage(c.help, nil) +} + +const synopsis = "Reads resource map keyed with the kind and valued with the GVK" +const help = ` +Usage: consul api-resources + +Lists all the resources map whose keys are resource kind and values are GVK format. +User could use the kind as abbreviation to refer to the resource GVK. +` diff --git a/command/registry.go b/command/registry.go index 559189b6f98..aa8637c2053 100644 --- a/command/registry.go +++ b/command/registry.go @@ -48,6 +48,7 @@ import ( acltread "github.com/hashicorp/consul/command/acl/token/read" acltupdate "github.com/hashicorp/consul/command/acl/token/update" "github.com/hashicorp/consul/command/agent" + "github.com/hashicorp/consul/command/apiresources" "github.com/hashicorp/consul/command/catalog" catlistdc "github.com/hashicorp/consul/command/catalog/list/dc" catlistnodes "github.com/hashicorp/consul/command/catalog/list/nodes" @@ -187,6 +188,7 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory { entry{"acl templated-policy read", func(ui cli.Ui) (cli.Command, error) { return acltpread.New(ui), nil }}, entry{"acl templated-policy preview", func(ui cli.Ui) (cli.Command, error) { return acltppreview.New(ui), nil }}, entry{"agent", func(ui cli.Ui) (cli.Command, error) { return agent.New(ui), nil }}, + entry{"api-resources", func(ui cli.Ui) (cli.Command, error) { return apiresources.New(ui), nil }}, entry{"catalog", func(cli.Ui) (cli.Command, error) { return catalog.New(), nil }}, entry{"catalog datacenters", func(ui cli.Ui) (cli.Command, error) { return catlistdc.New(ui), nil }}, entry{"catalog nodes", func(ui cli.Ui) (cli.Command, error) { return catlistnodes.New(ui), nil }}, diff --git a/command/resource/helper.go b/command/resource/helper.go index 0b79ea8ec1a..58ea218ee37 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -8,11 +8,12 @@ import ( "errors" "flag" "fmt" - "google.golang.org/protobuf/encoding/protojson" - "google.golang.org/protobuf/types/known/anypb" "net/http" "strings" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/types/known/anypb" + "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/command/helpers" "github.com/hashicorp/consul/command/resource/client" @@ -120,19 +121,10 @@ func GetTypeAndResourceName(args []string) (gvk *GVK, resourceName string, e err if strings.HasPrefix(args[1], "-") { return nil, "", fmt.Errorf("Must provide resource name right after type") } + resourceName = args[1] - s := strings.Split(args[0], ".") - if len(s) != 3 { - return nil, "", fmt.Errorf("Must include resource type argument in group.verion.kind format") - } - - gvk = &GVK{ - Group: s[0], - Version: s[1], - Kind: s[2], - } + gvk, e = inferGVKFromResourceType(args[0]) - resourceName = args[1] return } @@ -233,3 +225,41 @@ func (resource *Resource) List(gvk *GVK, q *client.QueryOptions) (*ListResponse, return out, nil } + +func inferGVKFromResourceType(resourceType string) (*GVK, error) { + s := strings.Split(resourceType, ".") + if len(s) == 1 { + kindToGVKMap := BuildKindToGVKMap() + // infer gvk from resource kind + if len(kindToGVKMap[s[0]]) != 0 { + return &GVK{ + Group: kindToGVKMap[s[0]][0], + Version: kindToGVKMap[s[0]][1], + Kind: kindToGVKMap[s[0]][2], + }, nil + } else { + return nil, fmt.Errorf("The shorthand name does not map to any existing resource type, please check `consul api-resources`") + } + } + + if len(s) != 3 { + return nil, fmt.Errorf("Must provide resource type argument with either in group.verion.kind format or its shorthand name") + } + + return &GVK{ + Group: s[0], + Version: s[1], + Kind: s[2], + }, nil +} + +func BuildKindToGVKMap() map[string][]string { + // this will generate the map everytime when we execute the CLI + // do we need to build this beforehand and save it somewhere? + typeRegistry := consul.NewTypeRegistry() + kindToGVKMap := map[string][]string{} + for _, r := range typeRegistry.Types() { + kindToGVKMap[r.Type.Kind] = []string{r.Type.GroupVersion, r.Type.GroupVersion, r.Type.Kind} + } + return kindToGVKMap +} From 6543573959103a877e0278d97fef0e590200102f Mon Sep 17 00:00:00 2001 From: Xinyi Wang Date: Wed, 4 Oct 2023 16:11:19 -0700 Subject: [PATCH 2/5] could infer from kind --- command/resource/helper.go | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/command/resource/helper.go b/command/resource/helper.go index 58ea218ee37..7c4c94a6c0a 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -227,18 +227,21 @@ func (resource *Resource) List(gvk *GVK, q *client.QueryOptions) (*ListResponse, } func inferGVKFromResourceType(resourceType string) (*GVK, error) { - s := strings.Split(resourceType, ".") + s := strings.Split(strings.ToLower(resourceType), ".") if len(s) == 1 { kindToGVKMap := BuildKindToGVKMap() - // infer gvk from resource kind - if len(kindToGVKMap[s[0]]) != 0 { + if len(kindToGVKMap[s[0]]) == 0 { + return nil, fmt.Errorf("The shorthand name does not map to any existing resource type, please check `consul api-resources`") + } else if len(kindToGVKMap[s[0]]) == 1 { + // infer gvk from resource kind + gvkSplit := strings.Split(kindToGVKMap[s[0]][0], ".") return &GVK{ - Group: kindToGVKMap[s[0]][0], - Version: kindToGVKMap[s[0]][1], - Kind: kindToGVKMap[s[0]][2], + Group: gvkSplit[0], + Version: gvkSplit[1], + Kind: gvkSplit[2], }, nil } else { - return nil, fmt.Errorf("The shorthand name does not map to any existing resource type, please check `consul api-resources`") + return nil, fmt.Errorf("The shorthand name has conflicts %v, please use the full name", kindToGVKMap[s[0]]) } } @@ -259,7 +262,13 @@ func BuildKindToGVKMap() map[string][]string { typeRegistry := consul.NewTypeRegistry() kindToGVKMap := map[string][]string{} for _, r := range typeRegistry.Types() { - kindToGVKMap[r.Type.Kind] = []string{r.Type.GroupVersion, r.Type.GroupVersion, r.Type.Kind} + gvkString := fmt.Sprintf("%s.%s.%s", r.Type.Group, r.Type.GroupVersion, r.Type.Kind) + kindKey := strings.ToLower(r.Type.Kind) + if len(kindToGVKMap[kindKey]) == 0 { + kindToGVKMap[kindKey] = []string{gvkString} + } else { + kindToGVKMap[kindKey] = append(kindToGVKMap[kindKey], gvkString) + } } return kindToGVKMap } From e6ed18155154e8674881d0a6a867977f3649f434 Mon Sep 17 00:00:00 2001 From: Xinyi Wang Date: Wed, 4 Oct 2023 16:26:58 -0700 Subject: [PATCH 3/5] fix tests --- command/resource/delete/delete_test.go | 2 +- command/resource/helper.go | 9 +++++---- command/resource/read/read_test.go | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/command/resource/delete/delete_test.go b/command/resource/delete/delete_test.go index f888bb3c8fd..7454455c941 100644 --- a/command/resource/delete/delete_test.go +++ b/command/resource/delete/delete_test.go @@ -67,7 +67,7 @@ func TestResourceDeleteInvalidArgs(t *testing.T) { "invalid resource type format": { args: []string{"a.", "name", "-namespace", "default"}, expectedCode: 1, - expectedErr: errors.New("Incorrect argument format: Must include resource type argument in group.verion.kind format"), + expectedErr: errors.New("Must provide resource type argument with either in group.verion.kind format or its shorthand name"), }, } diff --git a/command/resource/helper.go b/command/resource/helper.go index 7c4c94a6c0a..5b39fbaa435 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -227,14 +227,15 @@ func (resource *Resource) List(gvk *GVK, q *client.QueryOptions) (*ListResponse, } func inferGVKFromResourceType(resourceType string) (*GVK, error) { - s := strings.Split(strings.ToLower(resourceType), ".") + s := strings.Split(resourceType, ".") if len(s) == 1 { kindToGVKMap := BuildKindToGVKMap() - if len(kindToGVKMap[s[0]]) == 0 { + kind := strings.ToLower(s[0]) + if len(kindToGVKMap[kind]) == 0 { return nil, fmt.Errorf("The shorthand name does not map to any existing resource type, please check `consul api-resources`") - } else if len(kindToGVKMap[s[0]]) == 1 { + } else if len(kindToGVKMap[kind]) == 1 { // infer gvk from resource kind - gvkSplit := strings.Split(kindToGVKMap[s[0]][0], ".") + gvkSplit := strings.Split(kindToGVKMap[kind][0], ".") return &GVK{ Group: gvkSplit[0], Version: gvkSplit[1], diff --git a/command/resource/read/read_test.go b/command/resource/read/read_test.go index 766f86b02cc..a293a9faf5e 100644 --- a/command/resource/read/read_test.go +++ b/command/resource/read/read_test.go @@ -67,7 +67,7 @@ func TestResourceReadInvalidArgs(t *testing.T) { "invalid resource type format": { args: []string{"a.", "name", "-namespace", "default"}, expectedCode: 1, - expectedErr: errors.New("Incorrect argument format: Must include resource type argument in group.verion.kind format"), + expectedErr: errors.New("Incorrect argument format: Must provide resource type argument with either in group.verion.kind format or its shorthand name"), }, } From abd84627f239714562a35a2c4a932c90e382a138 Mon Sep 17 00:00:00 2001 From: Xinyi Wang Date: Thu, 19 Oct 2023 15:00:35 -0700 Subject: [PATCH 4/5] refactor code --- command/apiresources/apiresources.go | 59 ---------------------------- command/registry.go | 2 - command/resource/helper.go | 37 ++++++----------- 3 files changed, 13 insertions(+), 85 deletions(-) delete mode 100644 command/apiresources/apiresources.go diff --git a/command/apiresources/apiresources.go b/command/apiresources/apiresources.go deleted file mode 100644 index d3fa4566ca3..00000000000 --- a/command/apiresources/apiresources.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package apiresources - -import ( - "encoding/json" - "flag" - - "github.com/hashicorp/consul/command/cli" - - "github.com/hashicorp/consul/command/flags" - "github.com/hashicorp/consul/command/resource" -) - -func New(ui cli.Ui) *cmd { - c := &cmd{UI: ui} - c.init() - return c -} - -type cmd struct { - UI cli.Ui - flags *flag.FlagSet - help string -} - -func (c *cmd) init() { - c.flags = flag.NewFlagSet("", flag.ContinueOnError) - c.help = flags.Usage(help, c.flags) -} - -func (c *cmd) Run(args []string) int { - kindToGVKMap := resource.BuildKindToGVKMap() - b, err := json.MarshalIndent(kindToGVKMap, "", " ") - if err != nil { - c.UI.Error("Failed to encode output data") - return 1 - } - - c.UI.Info(string(b)) - return 0 -} - -func (c *cmd) Synopsis() string { - return synopsis -} - -func (c *cmd) Help() string { - return flags.Usage(c.help, nil) -} - -const synopsis = "Reads resource map keyed with the kind and valued with the GVK" -const help = ` -Usage: consul api-resources - -Lists all the resources map whose keys are resource kind and values are GVK format. -User could use the kind as abbreviation to refer to the resource GVK. -` diff --git a/command/registry.go b/command/registry.go index aa8637c2053..559189b6f98 100644 --- a/command/registry.go +++ b/command/registry.go @@ -48,7 +48,6 @@ import ( acltread "github.com/hashicorp/consul/command/acl/token/read" acltupdate "github.com/hashicorp/consul/command/acl/token/update" "github.com/hashicorp/consul/command/agent" - "github.com/hashicorp/consul/command/apiresources" "github.com/hashicorp/consul/command/catalog" catlistdc "github.com/hashicorp/consul/command/catalog/list/dc" catlistnodes "github.com/hashicorp/consul/command/catalog/list/nodes" @@ -188,7 +187,6 @@ func RegisteredCommands(ui cli.Ui) map[string]mcli.CommandFactory { entry{"acl templated-policy read", func(ui cli.Ui) (cli.Command, error) { return acltpread.New(ui), nil }}, entry{"acl templated-policy preview", func(ui cli.Ui) (cli.Command, error) { return acltppreview.New(ui), nil }}, entry{"agent", func(ui cli.Ui) (cli.Command, error) { return agent.New(ui), nil }}, - entry{"api-resources", func(ui cli.Ui) (cli.Command, error) { return apiresources.New(ui), nil }}, entry{"catalog", func(cli.Ui) (cli.Command, error) { return catalog.New(), nil }}, entry{"catalog datacenters", func(ui cli.Ui) (cli.Command, error) { return catlistdc.New(ui), nil }}, entry{"catalog nodes", func(ui cli.Ui) (cli.Command, error) { return catlistnodes.New(ui), nil }}, diff --git a/command/resource/helper.go b/command/resource/helper.go index 309a277014a..770b04ed420 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -96,21 +96,7 @@ func parseJson(js string) (*pbresource.Resource, error) { } func ParseResourceFromFile(filePath string) (*pbresource.Resource, error) { - data, err := helpers.LoadDataSourceNoRaw(filePath, nil) - if err != nil { - return nil, fmt.Errorf("Failed to load data: %v", err) - } - var parsedResource *pbresource.Resource - if isHCL([]byte(data)) { - parsedResource, err = resourcehcl.Unmarshal([]byte(data), consul.NewTypeRegistry()) - } else { - parsedResource, err = parseJson(data) - } - if err != nil { - return nil, fmt.Errorf("Failed to decode resource from input file: %v", err) - } - - return parsedResource, nil + return ParseResourceInput(filePath, nil) } // this is an inlined variant of hcl.lexMode() @@ -276,12 +262,17 @@ func (resource *Resource) List(gvk *GVK, q *client.QueryOptions) (*ListResponse, func inferGVKFromResourceType(resourceType string) (*GVK, error) { s := strings.Split(resourceType, ".") - if len(s) == 1 { + switch length := len(s); { + // only kind is provided + case length == 1: kindToGVKMap := BuildKindToGVKMap() kind := strings.ToLower(s[0]) - if len(kindToGVKMap[kind]) == 0 { + switch len(kindToGVKMap[kind]) { + // no g.v.k is found + case 0: return nil, fmt.Errorf("The shorthand name does not map to any existing resource type, please check `consul api-resources`") - } else if len(kindToGVKMap[kind]) == 1 { + // only one is found + case 1: // infer gvk from resource kind gvkSplit := strings.Split(kindToGVKMap[kind][0], ".") return &GVK{ @@ -289,12 +280,11 @@ func inferGVKFromResourceType(resourceType string) (*GVK, error) { Version: gvkSplit[1], Kind: gvkSplit[2], }, nil - } else { + // it alerts error if any conflict is found + default: return nil, fmt.Errorf("The shorthand name has conflicts %v, please use the full name", kindToGVKMap[s[0]]) } - } - - if len(s) != 3 { + case length != 3: return nil, fmt.Errorf("Must provide resource type argument with either in group.verion.kind format or its shorthand name") } @@ -306,8 +296,7 @@ func inferGVKFromResourceType(resourceType string) (*GVK, error) { } func BuildKindToGVKMap() map[string][]string { - // this will generate the map everytime when we execute the CLI - // do we need to build this beforehand and save it somewhere? + // this use the local copy of registration to build map typeRegistry := consul.NewTypeRegistry() kindToGVKMap := map[string][]string{} for _, r := range typeRegistry.Types() { From 7aeee0d5d7b5e83c3d99bae94587df434d79c7e1 Mon Sep 17 00:00:00 2001 From: Xinyi Wang Date: Mon, 23 Oct 2023 09:22:43 -0700 Subject: [PATCH 5/5] refactor code --- command/resource/delete/delete.go | 4 ---- command/resource/helper.go | 17 ++++++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/command/resource/delete/delete.go b/command/resource/delete/delete.go index 2679951da8b..06421d6d1e2 100644 --- a/command/resource/delete/delete.go +++ b/command/resource/delete/delete.go @@ -84,10 +84,6 @@ func (c *cmd) Run(args []string) int { return 1 } } else { - if len(args) < 2 { - c.UI.Error("Incorrect argument format: Must specify two arguments: resource type and resource name") - return 1 - } var err error gvk, resourceName, err = resource.GetTypeAndResourceName(args) if err != nil { diff --git a/command/resource/helper.go b/command/resource/helper.go index 770b04ed420..221a018599a 100644 --- a/command/resource/helper.go +++ b/command/resource/helper.go @@ -151,6 +151,9 @@ func ParseInputParams(inputArgs []string, flags *flag.FlagSet) error { } func GetTypeAndResourceName(args []string) (gvk *GVK, resourceName string, e error) { + if len(args) < 2 { + return nil, "", fmt.Errorf("Must specify two arguments: resource type and resource name") + } // it has to be resource name after the type if strings.HasPrefix(args[1], "-") { return nil, "", fmt.Errorf("Must provide resource name right after type") @@ -284,15 +287,15 @@ func inferGVKFromResourceType(resourceType string) (*GVK, error) { default: return nil, fmt.Errorf("The shorthand name has conflicts %v, please use the full name", kindToGVKMap[s[0]]) } - case length != 3: + case length == 3: + return &GVK{ + Group: s[0], + Version: s[1], + Kind: s[2], + }, nil + default: return nil, fmt.Errorf("Must provide resource type argument with either in group.verion.kind format or its shorthand name") } - - return &GVK{ - Group: s[0], - Version: s[1], - Kind: s[2], - }, nil } func BuildKindToGVKMap() map[string][]string {