@@ -19,39 +19,154 @@ package discovery
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "strings"
22
23
24
+ "github.com/kcp-dev/kcp/pkg/crdpuller"
25
+
26
+ "k8s.io/apiextensions-apiserver/pkg/apihelpers"
23
27
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
28
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24
29
"k8s.io/apimachinery/pkg/runtime/schema"
25
- ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
30
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
31
+ "k8s.io/apimachinery/pkg/util/sets"
32
+ "k8s.io/apiserver/pkg/endpoints/openapi"
33
+ "k8s.io/client-go/discovery"
34
+ "k8s.io/client-go/rest"
35
+ "k8s.io/kube-openapi/pkg/util/proto"
26
36
)
27
37
28
38
type Client struct {
29
- kubeClient ctrlruntimeclient. Reader
39
+ discoveryClient discovery. DiscoveryInterface
30
40
}
31
41
32
- func NewClient (kubeClient ctrlruntimeclient.Client ) * Client {
33
- return & Client {
34
- kubeClient : kubeClient ,
42
+ func NewClient (config * rest.Config ) (* Client , error ) {
43
+ discoveryClient , err := discovery .NewDiscoveryClientForConfig (config )
44
+ if err != nil {
45
+ return nil , err
35
46
}
47
+
48
+ return & Client {
49
+ discoveryClient : discoveryClient ,
50
+ }, nil
36
51
}
37
52
38
- func (c * Client ) DiscoverResourceType (ctx context.Context , gk schema.GroupKind ) (* apiextensionsv1.CustomResourceDefinition , error ) {
39
- crds := & apiextensionsv1.CustomResourceDefinitionList {}
40
- if err := c .kubeClient .List (ctx , crds ); err != nil {
41
- return nil , fmt .Errorf ("failed to list CRDs: %w" , err )
53
+ func (c * Client ) RetrieveCRD (ctx context.Context , gvk schema.GroupVersionKind ) (* apiextensionsv1.CustomResourceDefinition , error ) {
54
+ openapiSchema , err := c .discoveryClient .OpenAPISchema ()
55
+ if err != nil {
56
+ return nil , err
57
+ }
58
+
59
+ // Most of this code follows the logic in kcp's crd-puller, but is slimmed down
60
+ // to a) only support openapi and b) extract a specific version, not necessarily
61
+ // the preferred version.
62
+
63
+ models , err := proto .NewOpenAPIData (openapiSchema )
64
+ if err != nil {
65
+ return nil , err
66
+ }
67
+ modelsByGKV , err := openapi .GetModelsByGKV (models )
68
+ if err != nil {
69
+ return nil , err
42
70
}
43
71
44
- for _ , crd := range crds .Items {
45
- if crd .Spec .Group != gk .Group {
46
- continue
72
+ protoSchema := modelsByGKV [gvk ]
73
+ if protoSchema == nil {
74
+ return nil , fmt .Errorf ("no models for %v" , gvk )
75
+ }
76
+
77
+ var schemaProps apiextensionsv1.JSONSchemaProps
78
+ errs := crdpuller .Convert (protoSchema , & schemaProps )
79
+ if len (errs ) > 0 {
80
+ return nil , utilerrors .NewAggregate (errs )
81
+ }
82
+
83
+ _ , resourceLists , err := c .discoveryClient .ServerGroupsAndResources ()
84
+ if err != nil {
85
+ return nil , err
86
+ }
87
+
88
+ var resource * metav1.APIResource
89
+ allResourceNames := sets .New [string ]()
90
+ for _ , resList := range resourceLists {
91
+ for _ , res := range resList .APIResources {
92
+ allResourceNames .Insert (res .Name )
93
+
94
+ // find the requested resource based on the Kind, but ensure that subresources
95
+ // are not misinterpreted as the main resource by checking for "/"
96
+ if resList .GroupVersion == gvk .GroupVersion ().String () && res .Kind == gvk .Kind && ! strings .Contains (res .Name , "/" ) {
97
+ resource = & res
98
+ }
47
99
}
100
+ }
101
+
102
+ if resource == nil {
103
+ return nil , fmt .Errorf ("could not find %v in APIs" , gvk )
104
+ }
105
+
106
+ hasSubResource := func (subResource string ) bool {
107
+ return allResourceNames .Has (resource .Name + "/" + subResource )
108
+ }
109
+
110
+ var statusSubResource * apiextensionsv1.CustomResourceSubresourceStatus
111
+ if hasSubResource ("status" ) {
112
+ statusSubResource = & apiextensionsv1.CustomResourceSubresourceStatus {}
113
+ }
48
114
49
- if crd .Spec .Names .Kind != gk .Kind {
50
- continue
115
+ var scaleSubResource * apiextensionsv1.CustomResourceSubresourceScale
116
+ if hasSubResource ("scale" ) {
117
+ scaleSubResource = & apiextensionsv1.CustomResourceSubresourceScale {
118
+ SpecReplicasPath : ".spec.replicas" ,
119
+ StatusReplicasPath : ".status.replicas" ,
51
120
}
121
+ }
122
+
123
+ scope := apiextensionsv1 .ClusterScoped
124
+ if resource .Namespaced {
125
+ scope = apiextensionsv1 .NamespaceScoped
126
+ }
52
127
53
- return & crd , nil
128
+ crd := & apiextensionsv1.CustomResourceDefinition {
129
+ TypeMeta : metav1.TypeMeta {
130
+ Kind : "CustomResourceDefinition" ,
131
+ APIVersion : "apiextensions.k8s.io/v1" ,
132
+ },
133
+ ObjectMeta : metav1.ObjectMeta {
134
+ Name : fmt .Sprintf ("%s.%s" , resource .Name , gvk .Group ),
135
+ },
136
+ Spec : apiextensionsv1.CustomResourceDefinitionSpec {
137
+ Group : gvk .Group ,
138
+ Versions : []apiextensionsv1.CustomResourceDefinitionVersion {
139
+ {
140
+ Name : gvk .Version ,
141
+ Schema : & apiextensionsv1.CustomResourceValidation {
142
+ OpenAPIV3Schema : & schemaProps ,
143
+ },
144
+ Subresources : & apiextensionsv1.CustomResourceSubresources {
145
+ Status : statusSubResource ,
146
+ Scale : scaleSubResource ,
147
+ },
148
+ Served : true ,
149
+ Storage : true ,
150
+ },
151
+ },
152
+ Scope : scope ,
153
+ Names : apiextensionsv1.CustomResourceDefinitionNames {
154
+ Plural : resource .Name ,
155
+ Kind : resource .Kind ,
156
+ Categories : resource .Categories ,
157
+ ShortNames : resource .ShortNames ,
158
+ Singular : resource .SingularName ,
159
+ },
160
+ },
161
+ }
162
+
163
+ apiextensionsv1 .SetDefaults_CustomResourceDefinition (crd )
164
+
165
+ if apihelpers .IsProtectedCommunityGroup (gvk .Group ) {
166
+ crd .Annotations = map [string ]string {
167
+ apiextensionsv1 .KubeAPIApprovedAnnotation : "https://github.com/kcp-dev/kubernetes/pull/4" ,
168
+ }
54
169
}
55
170
56
- return nil , fmt . Errorf ( "CustomResourceDefinition for %s/%s does not exist" , gk . Group , gk . Kind )
171
+ return crd , nil
57
172
}
0 commit comments