Skip to content

Commit

Permalink
Merge pull request #39 from keisku/fix-bug
Browse files Browse the repository at this point in the history
Fix resource name detection logic from user input
  • Loading branch information
keisku authored May 3, 2024
2 parents 9234322 + 1251317 commit 025cb11
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 64 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ jobs:
diff <(sudo microk8s kubectl explore --disable-print-path no.*pro | tr -d '[:space:]') <(sudo microk8s kubectl explain node.spec.providerID | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path node.*pro | tr -d '[:space:]') <(sudo microk8s kubectl explain node.spec.providerID | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path nodes.*pro | tr -d '[:space:]') <(sudo microk8s kubectl explain node.spec.providerID | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path Node.*pro | tr -d '[:space:]') <(sudo microk8s kubectl explain node.spec.providerID | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path provider | tr -d '[:space:]') <(sudo microk8s kubectl explain node.spec.providerID | tr -d [':space:'])
- name: Since 1.27+, kubectl-explain has --disable-print-path been upgraded to v2 which enables OpenAPI v3 by default
- name: For 1.27+
run: |
diff <(sudo microk8s kubectl explore --disable-print-path hpa.*own.*id | tr -d '[:space:]') <(sudo microk8s kubectl explain horizontalpodautoscaler.metadata.ownerReferences.uid | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path csistoragecapacity.maximumVolumeSize | tr -d '[:space:]') <(sudo microk8s kubectl explain csistoragecapacity.maximumVolumeSize | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path csistoragecapacities.maximumVolumeSize | tr -d '[:space:]') <(sudo microk8s kubectl explain csistoragecapacity.maximumVolumeSize | tr -d [':space:'])
diff <(sudo microk8s kubectl explore --disable-print-path CSIStorageCapacity.*VolumeSize | tr -d '[:space:]') <(sudo microk8s kubectl explain csistoragecapacity.maximumVolumeSize | tr -d [':space:'])
if: ${{ 26 < matrix.k8s-minor }}
117 changes: 71 additions & 46 deletions explore/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import (
"github.com/ktr0731/go-fuzzyfinder"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
openapiclient "k8s.io/client-go/openapi"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/kube-openapi/pkg/util/proto"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/explain"
"k8s.io/kubectl/pkg/util/openapi"
)

Expand Down Expand Up @@ -135,44 +135,54 @@ func (o *Options) Complete(f cmdutil.Factory, args []string) error {
return nil
}

var gotGVR schema.GroupVersionResource
var idx int
// Find the first valid resource name in the inputFieldPath.
for i := 1; i <= len(o.inputFieldPath); i++ {
gotGVR, err = GetGVR(o, o.inputFieldPath[:i])
if err != nil {
continue
gvarMap, gvrs, err := o.discover()
if err != nil {
return err
}

var gvar *groupVersionAPIResource
var resourceIdx int
for i := len(o.inputFieldPath); i > 0; i-- {
var ok bool
gvar, ok = gvarMap[o.inputFieldPath[:i]]
if ok {
resourceIdx = i
break
}
idx = i
break
}
// If the inputFieldPath does not contain a valid resource name,
// inputFiledPath is treated as a regex directly.
if gotGVR.Empty() {
o.gvrs, err = o.listGVRs()
if err != nil {
return err
}
// inputFiledPath is treated as a regex.
if gvar == nil {
o.gvrs = gvrs
return nil
}
// Overwrite the regex if the inputFieldPath contains a valid resource name.
_, ok := gvarMap[o.inputFieldPath[:resourceIdx]]
if !ok {
return fmt.Errorf("no resource found for %s", o.inputFieldPath)
}
var re string
if strings.HasPrefix(o.inputFieldPath, gotGVR.Resource) {
// E.g., "nodes.*spec" -> ".*spec"
re = strings.TrimPrefix(o.inputFieldPath, gotGVR.Resource)
} else if strings.HasPrefix(o.inputFieldPath, singularResource(gotGVR.Resource)) {
// E.g., "node.*spec" -> ".*spec"
re = strings.TrimPrefix(o.inputFieldPath, singularResource(gotGVR.Resource))
if strings.HasPrefix(o.inputFieldPath, gvar.Resource) {
re = strings.TrimPrefix(o.inputFieldPath, gvar.Resource)
} else if strings.HasPrefix(o.inputFieldPath, gvar.Kind) {
re = strings.TrimPrefix(o.inputFieldPath, gvar.Kind)
} else if strings.HasPrefix(o.inputFieldPath, gvar.SingularName) {
re = strings.TrimPrefix(o.inputFieldPath, gvar.SingularName)
} else {
// E.g., "no.*spec" -> ".*spec"
prefix := o.inputFieldPath[:idx]
re = strings.TrimPrefix(o.inputFieldPath, prefix)
for _, shortName := range gvar.ShortNames {
if strings.HasPrefix(o.inputFieldPath, shortName) {
re = strings.TrimPrefix(o.inputFieldPath, shortName)
}
}
}
if re == "" {
return fmt.Errorf("cannot find resource name in %s", o.inputFieldPath)
}
o.inputFieldPathRegex, err = regexp.Compile(re)
if err != nil {
return err
}
o.gvrs = []schema.GroupVersionResource{gotGVR}
o.gvrs = []schema.GroupVersionResource{gvar.GroupVersionResource}

return nil
}
Expand Down Expand Up @@ -237,13 +247,6 @@ func (o *Options) Run() error {
return pathExplainers[paths[idx]].explain(o.Out, paths[idx])
}

func singularResource(resource string) string {
if strings.HasSuffix(resource, "s") {
return resource[:len(resource)-1]
}
return resource
}

func (o *Options) listGVRs() ([]schema.GroupVersionResource, error) {
lists, err := o.discovery.ServerPreferredResources()
if err != nil {
Expand Down Expand Up @@ -287,21 +290,43 @@ func (o *Options) findGVR() (schema.GroupVersionResource, error) {
return gvrs[idx], nil
}

// TODO: Find a way to mock meta.RESTMapper to avoid defining it as a variable.
var GetGVR = func(o *Options, name string) (schema.GroupVersionResource, error) {
return o.getGVR(name)
type groupVersionAPIResource struct {
schema.GroupVersionResource
metav1.APIResource
}

func (o *Options) getGVR(name string) (schema.GroupVersionResource, error) {
var ret schema.GroupVersionResource
var err error
if len(o.apiVersion) == 0 {
ret, _, err = explain.SplitAndParseResourceRequestWithMatchingPrefix(name, o.mapper)
} else {
ret, _, err = explain.SplitAndParseResourceRequest(name, o.mapper)
}
func (o *Options) discover() (map[string]*groupVersionAPIResource, []schema.GroupVersionResource, error) {
lists, err := o.discovery.ServerPreferredResources()
if err != nil {
return schema.GroupVersionResource{}, fmt.Errorf("get the group version resource by %s %s: %w", o.apiVersion, name, err)
return nil, nil, err
}
var gvrs []schema.GroupVersionResource
m := make(map[string]*groupVersionAPIResource)
for _, list := range lists {
if len(list.APIResources) == 0 {
continue
}
gv, err := schema.ParseGroupVersion(list.GroupVersion)
if err != nil {
continue
}
for _, resource := range list.APIResources {
gvr := gv.WithResource(resource.Name)
gvrs = append(gvrs, gvr)
r := groupVersionAPIResource{
GroupVersionResource: gvr,
APIResource: resource,
}
m[resource.Name] = &r
m[resource.Kind] = &r
m[resource.SingularName] = &r
for _, shortName := range resource.ShortNames {
m[shortName] = &r
}
}
}
return ret, nil
sort.SliceStable(gvrs, func(i, j int) bool {
return gvrs[i].String() < gvrs[j].String()
})
return m, gvrs, nil
}
91 changes: 74 additions & 17 deletions explore/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
"github.com/keisku/kubectl-explore/explore"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/discovery"
openapiclient "k8s.io/client-go/openapi"
Expand Down Expand Up @@ -188,22 +187,30 @@ func Test_Run(t *testing.T) {
},
},
},
}
explore.GetGVR = func(_ *explore.Options, inputFieldPath string) (schema.GroupVersionResource, error) {
node := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "nodes"}
hpa := schema.GroupVersionResource{Group: "autoscaling", Version: "v2", Resource: "horizontalpodautoscalers"}
gvr, ok := map[string]schema.GroupVersionResource{
"no": node,
"node": node,
"nodes": node,
"hpa": hpa,
"horizontalpodautoscaler": hpa,
"horizontalpodautoscalers": hpa,
}[inputFieldPath]
if !ok {
return schema.GroupVersionResource{}, fmt.Errorf("no resource found for %s", inputFieldPath)
}
return gvr, nil
{
GroupVersion: "storage.k8s.io/v1",
APIResources: []v1.APIResource{
{
Name: "csistoragecapacities",
SingularName: "csistoragecapacity",
Namespaced: true,
Kind: "CSIStorageCapacity",
ShortNames: []string{},
},
},
},
{
GroupVersion: "v1",
APIResources: []v1.APIResource{
{
Name: "componentstatuses",
SingularName: "componentstatus",
Namespaced: false,
Kind: "ComponentStatus",
ShortNames: []string{"cs"},
},
},
},
}
tests := []struct {
inputFieldPath string
Expand Down Expand Up @@ -256,6 +263,56 @@ func Test_Run(t *testing.T) {
"PATH: horizontalpodautoscalers.metadata.ownerReferences.uid",
},
},
{
inputFieldPath: "horizontalpodautoscalers.*own.*id",
expectRunError: false,
expectKeywords: []string{
"autoscaling",
"HorizontalPodAutoscaler",
"v2",
"PATH: horizontalpodautoscalers.metadata.ownerReferences.uid",
},
},
{
inputFieldPath: "horizontalpodautoscaler.*own.*id",
expectRunError: false,
expectKeywords: []string{
"autoscaling",
"HorizontalPodAutoscaler",
"v2",
"PATH: horizontalpodautoscalers.metadata.ownerReferences.uid",
},
},
{
inputFieldPath: "csistoragecapacity.maximumVolumeSize",
expectRunError: false,
expectKeywords: []string{
"CSIStorageCapacity",
"storage.k8s.io",
"v1",
"PATH: csistoragecapacities.maximumVolumeSize",
},
},
{
inputFieldPath: "csistoragecapacities.maximumVolumeSize",
expectRunError: false,
expectKeywords: []string{
"CSIStorageCapacity",
"storage.k8s.io",
"v1",
"PATH: csistoragecapacities.maximumVolumeSize",
},
},
{
inputFieldPath: "CSIStorageCapacity.*VolumeSize",
expectRunError: false,
expectKeywords: []string{
"CSIStorageCapacity",
"storage.k8s.io",
"v1",
"PATH: csistoragecapacities.maximumVolumeSize",
},
},
}
for _, tt := range tests {
for _, version := range k8sVersions {
Expand Down

0 comments on commit 025cb11

Please sign in to comment.