From 02ceef63abc3227483af8ce0c99d7aa2d1fc1ff2 Mon Sep 17 00:00:00 2001 From: nocturnalastro Date: Mon, 7 Oct 2024 16:33:33 +0100 Subject: [PATCH] Add warning if any api resources are missing a group --- pkg/compare/compare.go | 53 ++++++++++++++++--- pkg/compare/compare_test.go | 24 +++++++-- .../livebadAPIout.golden | 7 +++ 3 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 pkg/compare/testdata/AllRequiredTemplatesExistAndThereAreNoDiffs/livebadAPIout.golden diff --git a/pkg/compare/compare.go b/pkg/compare/compare.go index dbe71d7e..2da819bb 100644 --- a/pkg/compare/compare.go +++ b/pkg/compare/compare.go @@ -19,6 +19,7 @@ import ( "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/resource" "k8s.io/client-go/discovery" @@ -429,37 +430,73 @@ func (o *Options) setLiveSearchTypes(f kcmdutil.Factory) error { // getSupportedResourceTypes retrieves a set of resource types that are supported by the cluster. For each supported // resource type it will specify a list of groups where it exists. -func getSupportedResourceTypes(client discovery.CachedDiscoveryInterface) (map[string][]string, error) { - resources := make(map[string][]string) - lists, err := client.ServerPreferredResources() +func getSupportedResourceTypes(client discovery.CachedDiscoveryInterface) (map[string][]schema.GroupVersion, error) { + resources := make(map[string][]schema.GroupVersion) + _, lists, err := client.ServerGroupsAndResources() if err != nil { return resources, fmt.Errorf("failed to get clusters resource types: %w", err) } for _, list := range lists { if len(list.APIResources) != 0 { for _, res := range list.APIResources { - resources[res.Kind] = append(resources[res.Kind], res.Group) + gv := schema.GroupVersion{Group: res.Group, Version: res.Version} + if !slices.Contains(resources[res.Kind], gv) { + resources[res.Kind] = append(resources[res.Kind], gv) + } } } } return resources, nil +} +func getExpectedGroups(templates []ReferenceTemplate) []schema.GroupVersion { + groups := make([]schema.GroupVersion, 0) + for _, t := range templates { + gvk := t.GetMetadata().GroupVersionKind() + gv := schema.GroupVersion{Group: gvk.Group, Version: gvk.Version} + if gvk.Group != "" && !slices.Contains(groups, gv) { + groups = append(groups, gv) + } + } + return groups } // findAllRequestedSupportedTypes divides the requested types in to two groups: supported types and unsupported types based on if they are specified as supported. // The list of supported types will include the types in the form of {kind}.{group}. -func findAllRequestedSupportedTypes(supportedTypesWithGroups map[string][]string, requestedTypes map[string][]ReferenceTemplate) ([]string, []string) { +func findAllRequestedSupportedTypes(supportedTypesWithGroups map[string][]schema.GroupVersion, requestedTypes map[string][]ReferenceTemplate) ([]string, []string) { var typesIncludingGroup []string var notSupportedTypes []string - for kind := range requestedTypes { + var badAPI []string + for kind, templates := range requestedTypes { if _, ok := supportedTypesWithGroups[kind]; ok { - for _, group := range supportedTypesWithGroups[kind] { - typesIncludingGroup = append(typesIncludingGroup, strings.Join([]string{kind, group}, ".")) + expectedGroups := getExpectedGroups(templates) + for _, gv := range supportedTypesWithGroups[kind] { + index := slices.Index(expectedGroups, gv) + if index > -1 { + expectedGroups = slices.Delete(expectedGroups, index, index+1) + } + var supported string + if gv.Group == "" { + supported = kind + } else { + supported = strings.Join([]string{kind, gv.Version, gv.Group}, ".") + } + + typesIncludingGroup = append(typesIncludingGroup, supported) + } + for _, gv := range expectedGroups { + badAPI = append(badAPI, strings.Join([]string{kind, gv.Group + "/" + gv.Version}, ".")) } } else { notSupportedTypes = append(notSupportedTypes, kind) } } + if len(badAPI) > 0 { + slices.Sort(badAPI) + klog.Warningf( + "There may be an issue with the API resources exposed by the cluster. Found kind but missing group/version for %s ", + strings.Join(badAPI, ", ")) + } return typesIncludingGroup, notSupportedTypes } diff --git a/pkg/compare/compare_test.go b/pkg/compare/compare_test.go index 1a043054..3f364e72 100644 --- a/pkg/compare/compare_test.go +++ b/pkg/compare/compare_test.go @@ -181,6 +181,7 @@ type Test struct { outputFormat string checks Checks verboseOutput bool + badAPIResources bool userOverridePath string templToGenPatchFor []string @@ -208,6 +209,7 @@ func (test Test) Clone() Test { templToGenPatchFor: slices.Clone(test.templToGenPatchFor), overrideGenReason: test.overrideGenReason, referenceFileName: test.referenceFileName, + badAPIResources: test.badAPIResources, } } @@ -283,6 +285,12 @@ func (test Test) withMetadataFile(referenceFileName string) Test { return newTest } +func (test Test) withBadAPIResources() Test { + newTest := test.Clone() + newTest.badAPIResources = true + return newTest +} + func (test *Test) subTestName(mode Mode) string { name := test.name if test.subTestSuffix != "" { @@ -479,6 +487,12 @@ func TestCompareRun(t *testing.T) { withSubTestSuffix("One Of"). withMetadataFile("metadata-one-of.yaml"). withChecks(defaultChecks.withPrefixedSuffix("oneOf")), + + defaultTest("All Required Templates Exist And There Are No Diffs"). + withSubTestSuffix("Bad API Resources"). + withBadAPIResources(). + withModes([]Mode{{Live, LocalRef}}). + withChecks(defaultChecks.withPrefixedSuffix("badAPI")), } tf := cmdtesting.NewTestFactory() @@ -539,7 +553,7 @@ func getCommand(t *testing.T, test *Test, modeIndex int, tf *cmdtesting.TestFact require.NoError(t, cmd.Flags().Set("filename", resourcesDir)) require.NoError(t, cmd.Flags().Set("recursive", "true")) case Live: - discoveryResources, resources := getResources(t, resourcesDir) + discoveryResources, resources := getResources(t, *test, resourcesDir) updateTestDiscoveryClient(tf, discoveryResources) setClient(t, resources, tf) } @@ -612,7 +626,7 @@ func setClient(t *testing.T, resources []*unstructured.Unstructured, tf *cmdtest } } -func getResources(t *testing.T, resourcesDir string) ([]v1.APIResource, []*unstructured.Unstructured) { +func getResources(t *testing.T, test Test, resourcesDir string) ([]v1.APIResource, []*unstructured.Unstructured) { var resources []*unstructured.Unstructured var rL []v1.APIResource require.NoError(t, filepath.Walk(resourcesDir, @@ -634,7 +648,11 @@ func getResources(t *testing.T, resourcesDir string) ([]v1.APIResource, []*unstr } r := unstructured.Unstructured{Object: data} resources = append(resources, &r) - rL = append(rL, v1.APIResource{Name: r.GetName(), Kind: r.GetKind(), Version: r.GetAPIVersion()}) + res := v1.APIResource{Name: r.GetName(), Kind: r.GetKind(), Version: r.GroupVersionKind().Version, Group: r.GroupVersionKind().Group} + if test.badAPIResources { + res.Group = "" + } + rL = append(rL, res) return nil })) return rL, resources diff --git a/pkg/compare/testdata/AllRequiredTemplatesExistAndThereAreNoDiffs/livebadAPIout.golden b/pkg/compare/testdata/AllRequiredTemplatesExistAndThereAreNoDiffs/livebadAPIout.golden new file mode 100644 index 00000000..2832e494 --- /dev/null +++ b/pkg/compare/testdata/AllRequiredTemplatesExistAndThereAreNoDiffs/livebadAPIout.golden @@ -0,0 +1,7 @@ +There may be an issue with the API resources exposed by the cluster. Found kind but missing group/version for ClusterRole.rbac.authorization.k8s.io/v1, ClusterRoleBinding.rbac.authorization.k8s.io/v1, Deployment.apps/v1, RoleBinding.rbac.authorization.k8s.io/v1 +Summary +CRs with diffs: 0/14 +No validation issues with the cluster +No CRs are unmatched to reference CRs +Metadata Hash: 933892b7ae8a4f5232734acc34f6c93fc223844d836b37af390cfeaecf0b7a99 +No patched CRs