Skip to content

Commit 2275e13

Browse files
committed
applyset: Switch to contains-group-kinds annotation
A comparable change is being made in kubectl also; kind is clearer for end-users, and this enables creation of a CRD and an instance of the CRD in one applyset.
1 parent 33c89c9 commit 2275e13

File tree

4 files changed

+92
-40
lines changed

4 files changed

+92
-40
lines changed

applylib/applyset/applyset.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,9 @@ func (a *ApplySet) updateParentLabelsAndAnnotations(ctx context.Context, kapplys
319319
for k, v := range partialParent.Annotations {
320320
annotations[k] = v
321321
}
322+
if v, found := annotations[kubectlapply.DeprecatedApplySetGRsAnnotation]; found && v == "" {
323+
delete(annotations, kubectlapply.DeprecatedApplySetGRsAnnotation)
324+
}
322325
parent.SetAnnotations(annotations)
323326

324327
// update labels
@@ -376,8 +379,8 @@ func (a *ApplySet) WithParent(ctx context.Context, kapplyset *kubectlapply.Apply
376379
annotations = make(map[string]string)
377380
}
378381
annotations[kubectlapply.ApplySetToolingAnnotation] = a.tooling.String()
379-
if _, ok := annotations[kubectlapply.ApplySetGRsAnnotation]; !ok {
380-
annotations[kubectlapply.ApplySetGRsAnnotation] = ""
382+
if _, ok := annotations[kubectlapply.ApplySetGKsAnnotation]; !ok {
383+
annotations[kubectlapply.ApplySetGKsAnnotation] = ""
381384
}
382385
object.SetAnnotations(annotations)
383386

applylib/third_party/forked/github.com/kubernetes/kubectl/pkg/cmd/apply/applyset.go

Lines changed: 78 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,21 @@ const (
4848
// Example value: "kube-system,ns1,ns2".
4949
ApplySetAdditionalNamespacesAnnotation = "applyset.kubernetes.io/additional-namespaces"
5050

51-
// ApplySetGRsAnnotation is a list of group-resources used to optimize listing of ApplySet member objects.
51+
// Deprecated: ApplySetGRsAnnotation is a list of group-resources used to optimize listing of ApplySet member objects.
5252
// It is optional in the ApplySet specification, as tools can perform discovery or use a different optimization.
5353
// However, it is currently required in kubectl.
54-
// When present, the value of this annotation must be a comma separated list of the group-kinds,
54+
// When present, the value of this annotation must be a comma separated list of the group-resources,
5555
// in the fully-qualified name format, i.e. <resourcename>.<group>.
5656
// Example value: "certificates.cert-manager.io,configmaps,deployments.apps,secrets,services"
57-
ApplySetGRsAnnotation = "applyset.kubernetes.io/contains-group-resources"
57+
DeprecatedApplySetGRsAnnotation = "applyset.kubernetes.io/contains-group-resources"
58+
59+
// ApplySetGKsAnnotation is a list of group-kinds used to optimize listing of ApplySet member objects.
60+
// It is optional in the ApplySet specification, as tools can perform discovery or use a different optimization.
61+
// However, it is currently required in kubectl.
62+
// When present, the value of this annotation must be a comma separated list of the group-kinds,
63+
// in the fully-qualified name format, i.e. <kind>.<group>.
64+
// Example value: "Certificate.cert-manager.io,ConfigMap,deployments.apps,Secret,Service"
65+
ApplySetGKsAnnotation = "applyset.kubernetes.io/contains-group-kinds"
5866

5967
// ApplySetParentIDLabel is the key of the label that makes object an ApplySet parent object.
6068
// Its value MUST use the format specified in V1ApplySetIdFormat below
@@ -86,13 +94,13 @@ type ApplySet struct {
8694
toolingID ApplySetTooling
8795

8896
// currentResources is the set of resources that are part of the sever-side set as of when the current operation started.
89-
currentResources map[schema.GroupVersionResource]*meta.RESTMapping
97+
currentResources map[schema.GroupKind]*kindInfo
9098

9199
// currentNamespaces is the set of namespaces that contain objects in this applyset as of when the current operation started.
92100
currentNamespaces sets.Set[string]
93101

94102
// updatedResources is the set of resources that will be part of the set as of when the current operation completes.
95-
updatedResources map[schema.GroupVersionResource]*meta.RESTMapping
103+
updatedResources map[schema.GroupKind]*kindInfo
96104

97105
// updatedNamespaces is the set of namespaces that will contain objects in this applyset as of when the current operation completes.
98106
updatedNamespaces sets.Set[string]
@@ -138,9 +146,9 @@ func (t ApplySetTooling) String() string {
138146
func NewApplySet(parent *ApplySetParentRef, tooling ApplySetTooling, mapper meta.RESTMapper) *ApplySet {
139147
// func NewApplySet(parent *ApplySetParentRef, tooling ApplySetTooling, mapper meta.RESTMapper, client resource.RESTClient) *ApplySet {
140148
return &ApplySet{
141-
currentResources: make(map[schema.GroupVersionResource]*meta.RESTMapping),
149+
currentResources: make(map[schema.GroupKind]*kindInfo),
142150
currentNamespaces: make(sets.Set[string]),
143-
updatedResources: make(map[schema.GroupVersionResource]*meta.RESTMapping),
151+
updatedResources: make(map[schema.GroupKind]*kindInfo),
144152
updatedNamespaces: make(sets.Set[string]),
145153
parentRef: parent,
146154
toolingID: tooling,
@@ -283,7 +291,7 @@ func (a *ApplySet) FetchParent(obj runtime.Object) error {
283291
return fmt.Errorf("ApplySet parent object %q exists and has incorrect value for label %q (got: %s, want: %s)", a.parentRef, ApplySetParentIDLabel, idLabel, a.ID())
284292
}
285293

286-
if a.currentResources, err = parseResourcesAnnotation(annotations, a.restMapper); err != nil {
294+
if a.currentResources, err = parseKindAnnotation(annotations, a.restMapper); err != nil {
287295
// TODO: handle GVRs for now-deleted CRDs
288296
return fmt.Errorf("parsing ApplySet annotation on %q: %w", a.parentRef, err)
289297
}
@@ -302,8 +310,8 @@ func (a *ApplySet) LabelSelectorForMembers() string {
302310

303311
// AllPrunableResources returns the list of all resources that should be considered for pruning.
304312
// This is potentially a superset of the resources types that actually contain resources.
305-
func (a *ApplySet) AllPrunableResources() []*meta.RESTMapping {
306-
var ret []*meta.RESTMapping
313+
func (a *ApplySet) AllPrunableResources() []*kindInfo {
314+
var ret []*kindInfo
307315
for _, m := range a.currentResources {
308316
ret = append(ret, m)
309317
}
@@ -336,14 +344,46 @@ func toolingBaseName(toolAnnotation string) string {
336344
return toolAnnotation
337345
}
338346

339-
func parseResourcesAnnotation(annotations map[string]string, mapper meta.RESTMapper) (map[schema.GroupVersionResource]*meta.RESTMapping, error) {
340-
annotation, ok := annotations[ApplySetGRsAnnotation]
347+
type kindInfo struct {
348+
restMapping *meta.RESTMapping
349+
}
350+
351+
func parseKindAnnotation(annotations map[string]string, mapper meta.RESTMapper) (map[schema.GroupKind]*kindInfo, error) {
352+
annotation, ok := annotations[ApplySetGKsAnnotation]
341353
if !ok {
354+
if annotations[DeprecatedApplySetGRsAnnotation] != "" {
355+
return parseDeprecatedResourceAnnotation(annotations[DeprecatedApplySetGRsAnnotation], mapper)
356+
}
357+
342358
// The spec does not require this annotation. However, 'missing' means 'perform discovery'.
343359
// We return an error because we do not currently support dynamic discovery in kubectl apply.
344-
return nil, fmt.Errorf("kubectl requires the %q annotation to be set on all ApplySet parent objects", ApplySetGRsAnnotation)
360+
return nil, fmt.Errorf("kubectl requires the %q annotation to be set on all ApplySet parent objects", ApplySetGKsAnnotation)
345361
}
346-
mappings := make(map[schema.GroupVersionResource]*meta.RESTMapping)
362+
mappings := make(map[schema.GroupKind]*kindInfo)
363+
// Annotation present but empty means that this is currently an empty set.
364+
if annotation == "" {
365+
return mappings, nil
366+
}
367+
for _, grString := range strings.Split(annotation, ",") {
368+
gk := schema.ParseGroupKind(grString)
369+
// gvk, err := mapper.KindFor(gr.WithVersion(""))
370+
// if err != nil {
371+
// return nil, fmt.Errorf("invalid group resource in %q annotation: %w", ApplySetGKsAnnotation, err)
372+
// }
373+
restMapping, err := mapper.RESTMapping(gk)
374+
if err != nil {
375+
return nil, fmt.Errorf("could not find mapping for kind in %q annotation: %w", ApplySetGKsAnnotation, err)
376+
}
377+
mappings[gk] = &kindInfo{
378+
restMapping: restMapping,
379+
}
380+
}
381+
382+
return mappings, nil
383+
}
384+
385+
func parseDeprecatedResourceAnnotation(annotation string, mapper meta.RESTMapper) (map[schema.GroupKind]*kindInfo, error) {
386+
mappings := make(map[schema.GroupKind]*kindInfo)
347387
// Annotation present but empty means that this is currently an empty set.
348388
if annotation == "" {
349389
return mappings, nil
@@ -352,13 +392,15 @@ func parseResourcesAnnotation(annotations map[string]string, mapper meta.RESTMap
352392
gr := schema.ParseGroupResource(grString)
353393
gvk, err := mapper.KindFor(gr.WithVersion(""))
354394
if err != nil {
355-
return nil, fmt.Errorf("invalid group resource in %q annotation: %w", ApplySetGRsAnnotation, err)
395+
return nil, fmt.Errorf("invalid group resource in %q annotation: %w", DeprecatedApplySetGRsAnnotation, err)
356396
}
357-
mapping, err := mapper.RESTMapping(gvk.GroupKind())
397+
restMapping, err := mapper.RESTMapping(gvk.GroupKind())
358398
if err != nil {
359-
return nil, fmt.Errorf("could not find kind for resource in %q annotation: %w", ApplySetGRsAnnotation, err)
399+
return nil, fmt.Errorf("could not find kind for resource in %q annotation: %w", DeprecatedApplySetGRsAnnotation, err)
400+
}
401+
mappings[gvk.GroupKind()] = &kindInfo{
402+
restMapping: restMapping,
360403
}
361-
mappings[mapping.Resource] = mapping
362404
}
363405
return mappings, nil
364406
}
@@ -378,9 +420,14 @@ func parseNamespacesAnnotation(annotations map[string]string) sets.Set[string] {
378420
// Modified. change private to public
379421
// addResource registers the given resource and namespace as being part of the updated set of
380422
// resources being applied by the current operation.
381-
func (a *ApplySet) AddResource(resource *meta.RESTMapping, namespace string) {
382-
a.updatedResources[resource.Resource] = resource
383-
if resource.Scope == meta.RESTScopeNamespace && namespace != "" {
423+
func (a *ApplySet) AddResource(restMapping *meta.RESTMapping, namespace string) {
424+
gk := restMapping.GroupVersionKind.GroupKind()
425+
if _, found := a.updatedResources[gk]; !found {
426+
a.updatedResources[gk] = &kindInfo{
427+
restMapping: restMapping,
428+
}
429+
}
430+
if restMapping.Scope == meta.RESTScopeNamespace && namespace != "" {
384431
a.updatedNamespaces.Insert(namespace)
385432
}
386433
}
@@ -433,17 +480,17 @@ func serverSideApplyRequest(a *ApplySet, data []byte, dryRun cmdutil.DryRunStrat
433480

434481
// Modified from private to public buildParentPatch --> BuildParentPatch
435482
func (a *ApplySet) BuildParentPatch(mode ApplySetUpdateMode) *metav1.PartialObjectMetadata {
436-
var newGRsAnnotation, newNsAnnotation string
483+
var newGKsAnnotation, newNsAnnotation string
437484
switch mode {
438485
case updateToSuperset:
439486
// If the apply succeeded but pruning failed, the set of group resources that
440487
// the ApplySet should track is the superset of the previous and current resources.
441488
// This ensures that the resources that failed to be pruned are not orphaned from the set.
442489
grSuperset := sets.KeySet(a.currentResources).Union(sets.KeySet(a.updatedResources))
443-
newGRsAnnotation = generateResourcesAnnotation(grSuperset)
490+
newGKsAnnotation = generateResourcesAnnotation(grSuperset)
444491
newNsAnnotation = generateNamespacesAnnotation(a.currentNamespaces.Union(a.updatedNamespaces), a.parentRef.Namespace)
445492
case updateToLatestSet:
446-
newGRsAnnotation = generateResourcesAnnotation(sets.KeySet(a.updatedResources))
493+
newGKsAnnotation = generateResourcesAnnotation(sets.KeySet(a.updatedResources))
447494
newNsAnnotation = generateNamespacesAnnotation(a.updatedNamespaces, a.parentRef.Namespace)
448495
}
449496

@@ -457,7 +504,7 @@ func (a *ApplySet) BuildParentPatch(mode ApplySetUpdateMode) *metav1.PartialObje
457504
Namespace: a.parentRef.Namespace,
458505
Annotations: map[string]string{
459506
ApplySetToolingAnnotation: a.toolingID.String(),
460-
ApplySetGRsAnnotation: newGRsAnnotation,
507+
ApplySetGKsAnnotation: newGKsAnnotation,
461508
ApplySetAdditionalNamespacesAnnotation: newNsAnnotation,
462509
},
463510
Labels: map[string]string{
@@ -473,13 +520,13 @@ func generateNamespacesAnnotation(namespaces sets.Set[string], skip string) stri
473520
return strings.Join(nsList, ",")
474521
}
475522

476-
func generateResourcesAnnotation(resources sets.Set[schema.GroupVersionResource]) string {
477-
var grs []string
478-
for gvr := range resources {
479-
grs = append(grs, gvr.GroupResource().String())
523+
func generateResourcesAnnotation(resources sets.Set[schema.GroupKind]) string {
524+
var gks []string
525+
for gk := range resources {
526+
gks = append(gks, gk.String())
480527
}
481-
sort.Strings(grs)
482-
return strings.Join(grs, ",")
528+
sort.Strings(gks)
529+
return strings.Join(gks, ",")
483530
}
484531

485532
func (a ApplySet) FieldManager() string {

applylib/third_party/forked/github.com/kubernetes/kubectl/pkg/cmd/apply/applyset_pruner.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,9 @@ func (a *ApplySet) FindAllObjectsToPrune(ctx context.Context, dynamicClient dyna
7575

7676
// We run discovery in parallel, in as many goroutines as priority and fairness will allow
7777
// (We don't expect many requests in real-world scenarios - maybe tens, unlikely to be hundreds)
78-
for _, restMapping := range a.AllPrunableResources() {
78+
for _, kindInfo := range a.AllPrunableResources() {
79+
restMapping := kindInfo.restMapping
80+
7981
switch restMapping.Scope.Name() {
8082
case meta.RESTScopeNameNamespace:
8183
for _, namespace := range a.AllPrunableNamespaces() {

pkg/test/testreconciler/simpletest/testdata/reconcile/ssa/create/expected-http.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,16 +118,16 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple
118118
Accept: application/json, */*
119119
Content-Type: application/json
120120

121-
{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-resources":"configmaps,deployments.apps","applyset.kubernetes.io/tooling":"SimpleTest/"}},"spec":{"channel":"stable"},"status":{"healthy":false}}
121+
{"kind":"SimpleTest","apiVersion":"addons.example.org/v1alpha1","metadata":{"name":"simple1","namespace":"ns1","uid":"00000000-0000-0000-0000-000000000002","resourceVersion":"2","creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"}},"spec":{"channel":"stable"},"status":{"healthy":false}}
122122

123123

124124
200 OK
125125
Cache-Control: no-cache, private
126-
Content-Length: 567
126+
Content-Length: 561
127127
Content-Type: application/json
128128
Date: (removed)
129129

130-
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-resources":"configmaps,deployments.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":false}}
130+
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"healthy":false}}
131131

132132
---
133133

@@ -193,15 +193,15 @@ PUT http://kube-apiserver/apis/addons.example.org/v1alpha1/namespaces/ns1/simple
193193
Accept: application/json, */*
194194
Content-Type: application/json
195195

196-
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-resources":"configmaps,deployments.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}}
196+
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"3","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}}
197197

198198
200 OK
199199
Cache-Control: no-cache, private
200-
Content-Length: 736
200+
Content-Length: 730
201201
Content-Type: application/json
202202
Date: (removed)
203203

204-
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-resources":"configmaps,deployments.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"6","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}}
204+
{"apiVersion":"addons.example.org/v1alpha1","kind":"SimpleTest","metadata":{"annotations":{"applyset.kubernetes.io/additional-namespaces":"","applyset.kubernetes.io/contains-group-kinds":"ConfigMap,Deployment.apps","applyset.kubernetes.io/tooling":"SimpleTest/"},"creationTimestamp":"2022-01-01T00:00:01Z","labels":{"applyset.kubernetes.io/id":"applyset-xbxAWnAItX3p1Gxrs86F-ZQAGwGoys9xxQGK3IED7bY-v1"},"name":"simple1","namespace":"ns1","resourceVersion":"6","uid":"00000000-0000-0000-0000-000000000002"},"spec":{"channel":"stable"},"status":{"conditions":[{"lastTransitionTime":"2022-01-01T00:00:00Z","message":"all manifests are reconciled.","reason":"Normal","status":"True","type":"Ready"}],"healthy":true,"phase":"Current"}}
205205

206206
---
207207

0 commit comments

Comments
 (0)