diff --git a/provider/pkg/gen/testdata/identify-list-kinds/list-kind b/provider/pkg/gen/testdata/identify-list-kinds/list-kind new file mode 100644 index 0000000000..4d416f288b --- /dev/null +++ b/provider/pkg/gen/testdata/identify-list-kinds/list-kind @@ -0,0 +1,153 @@ +-- definitions -- +{ + "com.pulumi.k8sversion.test.TestResource": { + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "type": "object", + "properties": { + "fakeField": { + "type": "string" + } + } + }, + "spec": { + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "k8sversion.pulumi.com", + "kind": "TestResource", + "version": "test" + } + ], + "x-kubernetes-selectable-fields": [] + }, + "com.pulumi.k8sversion.test.TestResourceList": { + "description": "TestResourceList is a list of TestResource", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of testresources. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", + "type": "array", + "items": { + "$ref": "#/definitions/com.pulumi.k8sversion.test.TestResource" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "object", + "properties": { + "fakeField": { + "type": "string" + } + } + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "k8sversion.pulumi.com", + "kind": "TestResourceList", + "version": "test" + } + ], + "x-kubernetes-selectable-fields": [] + }, + "com.pulumi.k8sversion.test.TestResourceNotRealList": { + "description": "TestResourceNotRealList is not a real list object.", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + "type": "object", + "properties": { + "fakeField": { + "type": "string" + } + } + }, + "spec": { + "type": "string" + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "k8sversion.pulumi.com", + "kind": "TestResourceNotRealList", + "version": "test" + } + ], + "x-kubernetes-selectable-fields": [] + }, + "com.pulumi.k8sversion.test.TestResourceNotRealListList": { + "description": "TestResourceNotRealListList is a list of TestResourceNotRealList", + "type": "object", + "properties": { + "apiVersion": { + "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + "type": "string" + }, + "items": { + "description": "List of testresourcelists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", + "type": "array", + "items": { + "$ref": "#/definitions/com.pulumi.k8sversion.test.TestResourceNotRealList" + } + }, + "kind": { + "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "string" + }, + "metadata": { + "description": "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + "type": "object", + "properties": { + "fakeField": { + "type": "string" + } + } + } + }, + "x-kubernetes-group-version-kind": [ + { + "group": "k8sversion.pulumi.com", + "kind": "TestResourceNotRealListList", + "version": "test" + } + ], + "x-kubernetes-selectable-fields": [] + } +} + +-- kinds -- +- k8sversion.test.TestResource +- k8sversion.test.TestResourceNotRealList + +-- listKinds -- +- k8sversion.test.TestResourceList +- k8sversion.test.TestResourceNotRealListList \ No newline at end of file diff --git a/provider/pkg/gen/typegen_test.go b/provider/pkg/gen/typegen_test.go new file mode 100644 index 0000000000..83a5b5a5f7 --- /dev/null +++ b/provider/pkg/gen/typegen_test.go @@ -0,0 +1,102 @@ +// Copyright 2016-2024, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// nolint: lll +package gen + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/tools/txtar" + "gopkg.in/yaml.v2" +) + +// TestCreateGroups_IdentifyListKinds loads txtar files under testdata/identify-list-kinds and uses them to +// craft an unstructured map[string]any definitions file for createGroups. +// The goal of this test is to ensure we can accurately distinuguish between singletons and lists of kinds. +// +// The test files should contain the following files: +// - definitions: a JSON file containing the definitions +// - kinds: a YAML file containing a list of kinds that are singletons +// - listKinds: a YAML file containing a list of kinds that are lists/collections of singleton kinds +func TestCreateGroups_IdentifyListKinds(t *testing.T) { + dir := filepath.Join("testdata/identify-list-kinds") + tests, err := os.ReadDir(dir) + require.NoError(t, err) + + for _, tt := range tests { + t.Run(tt.Name(), func(t *testing.T) { + archive, err := txtar.ParseFile(filepath.Join(dir, tt.Name())) + require.NoError(t, err) + + var definitions map[string]any + var kinds, listKinds map[string]struct{} + + for _, f := range archive.Files { + var parsed []string + switch f.Name { + case "definitions": + err := json.Unmarshal(f.Data, &definitions) + require.NoError(t, err, f.Name) + case "kinds": + err = yaml.Unmarshal(f.Data, &parsed) + require.NoError(t, err, f.Name) + kinds = sliceToSet(parsed) + case "listKinds": + err = yaml.Unmarshal(f.Data, &parsed) + require.NoError(t, err, f.Name) + listKinds = sliceToSet(parsed) + default: + t.Fatal("unrecognized filename", f.Name) + } + } + + configGroups := createGroups(definitions, true) + + // Loop through all parsed kinds and ensure they are accounted for. + for _, g := range configGroups { + for _, v := range g.versions { + for _, kind := range v.kinds { + gvk := gvkToString(kind.gvk.Group, kind.gvk.Version, kind.gvk.Kind) + if kind.isList { + delete(listKinds, gvk) + } else { + delete(kinds, gvk) + } + } + } + } + + assert.Equal(t, 0, len(kinds), "kinds not found while parsing: %v", kinds) + assert.Equal(t, 0, len(listKinds), "listKinds not found while parsing: %v", listKinds) + }) + } +} + +func gvkToString(group, version, kind string) string { + return group + "." + version + "." + kind +} + +func sliceToSet(slice []string) map[string]struct{} { + set := make(map[string]struct{}) + for _, item := range slice { + set[item] = struct{}{} + } + return set +}