Merge pull request #118942 from justinsb/switch_kubectl_prune_annotation

kubectl prune v2: switch to contains-group-kinds annotation
This commit is contained in:
Kubernetes Prow Robot 2023-09-15 10:08:12 -07:00 committed by GitHub
commit 963400f1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 58 deletions

View File

@ -2403,7 +2403,7 @@ kind: Secret
metadata: metadata:
annotations: annotations:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: replicationcontrollers applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null creationTimestamp: null
labels: labels:
@ -2437,7 +2437,7 @@ kind: Secret
metadata: metadata:
annotations: annotations:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: replicationcontrollers,services applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null creationTimestamp: null
labels: labels:
@ -2472,7 +2472,7 @@ kind: Secret
metadata: metadata:
annotations: annotations:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: replicationcontrollers,services applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null creationTimestamp: null
labels: labels:
@ -2507,7 +2507,7 @@ kind: Secret
metadata: metadata:
annotations: annotations:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: services applyset.kubernetes.io/contains-group-kinds: Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null creationTimestamp: null
labels: labels:
@ -2524,60 +2524,60 @@ func TestApplySetInvalidLiveParent(t *testing.T) {
defer tf.Cleanup() defer tf.Cleanup()
type testCase struct { type testCase struct {
grsAnnotation string gksAnnotation string
toolingAnnotation string toolingAnnotation string
idLabel string idLabel string
expectErr string expectErr string
} }
validIDLabel := "applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1" validIDLabel := "applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1"
validToolingAnnotation := "kubectl/v1.27.0" validToolingAnnotation := "kubectl/v1.27.0"
validGrsAnnotation := "deployments.apps,namespaces,secrets" validGksAnnotation := "Deployment.apps,Namespace,Secret"
for name, test := range map[string]testCase{ for name, test := range map[string]testCase{
"group-resources annotation is required": { "group-resources annotation is required": {
grsAnnotation: "", gksAnnotation: "",
toolingAnnotation: validToolingAnnotation, toolingAnnotation: validToolingAnnotation,
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: parsing ApplySet annotation on \"secrets./my-set\": kubectl requires the \"applyset.kubernetes.io/contains-group-resources\" annotation to be set on all ApplySet parent objects", expectErr: "error: parsing ApplySet annotation on \"secrets./my-set\": kubectl requires the \"applyset.kubernetes.io/contains-group-kinds\" annotation to be set on all ApplySet parent objects",
}, },
"group-resources annotation should not contain invalid resources": { "group-resources annotation should not contain invalid resources": {
grsAnnotation: "does-not-exist", gksAnnotation: "does-not-exist",
toolingAnnotation: validToolingAnnotation, toolingAnnotation: validToolingAnnotation,
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: parsing ApplySet annotation on \"secrets./my-set\": invalid group resource in \"applyset.kubernetes.io/contains-group-resources\" annotation: no matches for /, Resource=does-not-exist", expectErr: "error: parsing ApplySet annotation on \"secrets./my-set\": could not find mapping for kind in \"applyset.kubernetes.io/contains-group-kinds\" annotation: no matches for kind \"does-not-exist\" in group \"\"",
}, },
"tooling annotation is required": { "tooling annotation is required": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: "", toolingAnnotation: "",
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is missing required annotation \"applyset.kubernetes.io/tooling\"", expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is missing required annotation \"applyset.kubernetes.io/tooling\"",
}, },
"tooling annotation must have kubectl prefix": { "tooling annotation must have kubectl prefix": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: "helm/v3", toolingAnnotation: "helm/v3",
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"helm\" instead of \"kubectl\"", expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"helm\" instead of \"kubectl\"",
}, },
"tooling annotation with invalid prefix with one segment can be parsed": { "tooling annotation with invalid prefix with one segment can be parsed": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: "helm", toolingAnnotation: "helm",
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"helm\" instead of \"kubectl\"", expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"helm\" instead of \"kubectl\"",
}, },
"tooling annotation with invalid prefix with many segments can be parsed": { "tooling annotation with invalid prefix with many segments can be parsed": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: "example.com/tool/why/v1", toolingAnnotation: "example.com/tool/why/v1",
idLabel: validIDLabel, idLabel: validIDLabel,
expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"example.com/tool/why\" instead of \"kubectl\"", expectErr: "error: ApplySet parent object \"secrets./my-set\" already exists and is managed by tooling \"example.com/tool/why\" instead of \"kubectl\"",
}, },
"ID label is required": { "ID label is required": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: validToolingAnnotation, toolingAnnotation: validToolingAnnotation,
idLabel: "", idLabel: "",
expectErr: "error: ApplySet parent object \"secrets./my-set\" exists and does not have required label applyset.kubernetes.io/id", expectErr: "error: ApplySet parent object \"secrets./my-set\" exists and does not have required label applyset.kubernetes.io/id",
}, },
"ID label must match the ApplySet's real ID": { "ID label must match the ApplySet's real ID": {
grsAnnotation: validGrsAnnotation, gksAnnotation: validGksAnnotation,
toolingAnnotation: validToolingAnnotation, toolingAnnotation: validToolingAnnotation,
idLabel: "somethingelse", idLabel: "somethingelse",
expectErr: fmt.Sprintf("error: ApplySet parent object \"secrets./my-set\" exists and has incorrect value for label \"applyset.kubernetes.io/id\" (got: somethingelse, want: %s)", validIDLabel), expectErr: fmt.Sprintf("error: ApplySet parent object \"secrets./my-set\" exists and has incorrect value for label \"applyset.kubernetes.io/id\" (got: somethingelse, want: %s)", validIDLabel),
@ -2596,8 +2596,8 @@ func TestApplySetInvalidLiveParent(t *testing.T) {
secret.SetNamespace("test") secret.SetNamespace("test")
annotations := make(map[string]string) annotations := make(map[string]string)
labels := make(map[string]string) labels := make(map[string]string)
if test.grsAnnotation != "" { if test.gksAnnotation != "" {
annotations[ApplySetGRsAnnotation] = test.grsAnnotation annotations[ApplySetGKsAnnotation] = test.gksAnnotation
} }
if test.toolingAnnotation != "" { if test.toolingAnnotation != "" {
annotations[ApplySetToolingAnnotation] = test.toolingAnnotation annotations[ApplySetToolingAnnotation] = test.toolingAnnotation
@ -2670,7 +2670,7 @@ kind: ApplySet
metadata: metadata:
annotations: annotations:
applyset.kubernetes.io/additional-namespaces: test applyset.kubernetes.io/additional-namespaces: test
applyset.kubernetes.io/contains-group-resources: replicationcontrollers applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null creationTimestamp: null
labels: labels:

View File

@ -54,13 +54,22 @@ const (
// Example value: "kube-system,ns1,ns2". // Example value: "kube-system,ns1,ns2".
ApplySetAdditionalNamespacesAnnotation = "applyset.kubernetes.io/additional-namespaces" ApplySetAdditionalNamespacesAnnotation = "applyset.kubernetes.io/additional-namespaces"
// ApplySetGRsAnnotation is a list of group-resources used to optimize listing of ApplySet member objects. // Deprecated: ApplySetGRsAnnotation is a list of group-resources used to optimize listing of ApplySet member objects.
// It is optional in the ApplySet specification, as tools can perform discovery or use a different optimization.
// However, it is currently required in kubectl.
// When present, the value of this annotation must be a comma separated list of the group-resources,
// in the fully-qualified name format, i.e. <resourcename>.<group>.
// Example value: "certificates.cert-manager.io,configmaps,deployments.apps,secrets,services"
// Deprecated and replaced by ApplySetGKsAnnotation, support for this can be removed in applyset beta or GA.
DeprecatedApplySetGRsAnnotation = "applyset.kubernetes.io/contains-group-resources"
// ApplySetGKsAnnotation is a list of group-kinds used to optimize listing of ApplySet member objects.
// It is optional in the ApplySet specification, as tools can perform discovery or use a different optimization. // It is optional in the ApplySet specification, as tools can perform discovery or use a different optimization.
// However, it is currently required in kubectl. // However, it is currently required in kubectl.
// When present, the value of this annotation must be a comma separated list of the group-kinds, // When present, the value of this annotation must be a comma separated list of the group-kinds,
// in the fully-qualified name format, i.e. <resourcename>.<group>. // in the fully-qualified name format, i.e. <kind>.<group>.
// Example value: "certificates.cert-manager.io,configmaps,deployments.apps,secrets,services" // Example value: "Certificate.cert-manager.io,ConfigMap,deployments.apps,Secret,Service"
ApplySetGRsAnnotation = "applyset.kubernetes.io/contains-group-resources" ApplySetGKsAnnotation = "applyset.kubernetes.io/contains-group-kinds"
// ApplySetParentIDLabel is the key of the label that makes object an ApplySet parent object. // ApplySetParentIDLabel is the key of the label that makes object an ApplySet parent object.
// Its value MUST use the format specified in V1ApplySetIdFormat below // Its value MUST use the format specified in V1ApplySetIdFormat below
@ -92,13 +101,13 @@ type ApplySet struct {
toolingID ApplySetTooling toolingID ApplySetTooling
// currentResources is the set of resources that are part of the sever-side set as of when the current operation started. // currentResources is the set of resources that are part of the sever-side set as of when the current operation started.
currentResources map[schema.GroupVersionResource]*meta.RESTMapping currentResources map[schema.GroupKind]*kindInfo
// currentNamespaces is the set of namespaces that contain objects in this applyset as of when the current operation started. // currentNamespaces is the set of namespaces that contain objects in this applyset as of when the current operation started.
currentNamespaces sets.Set[string] currentNamespaces sets.Set[string]
// updatedResources is the set of resources that will be part of the set as of when the current operation completes. // updatedResources is the set of resources that will be part of the set as of when the current operation completes.
updatedResources map[schema.GroupVersionResource]*meta.RESTMapping updatedResources map[schema.GroupKind]*kindInfo
// updatedNamespaces is the set of namespaces that will contain objects in this applyset as of when the current operation completes. // updatedNamespaces is the set of namespaces that will contain objects in this applyset as of when the current operation completes.
updatedNamespaces sets.Set[string] updatedNamespaces sets.Set[string]
@ -143,9 +152,9 @@ func (t ApplySetTooling) String() string {
// NewApplySet creates a new ApplySet object tracked by the given parent object. // NewApplySet creates a new ApplySet object tracked by the given parent object.
func NewApplySet(parent *ApplySetParentRef, tooling ApplySetTooling, mapper meta.RESTMapper, client resource.RESTClient) *ApplySet { func NewApplySet(parent *ApplySetParentRef, tooling ApplySetTooling, mapper meta.RESTMapper, client resource.RESTClient) *ApplySet {
return &ApplySet{ return &ApplySet{
currentResources: make(map[schema.GroupVersionResource]*meta.RESTMapping), currentResources: make(map[schema.GroupKind]*kindInfo),
currentNamespaces: make(sets.Set[string]), currentNamespaces: make(sets.Set[string]),
updatedResources: make(map[schema.GroupVersionResource]*meta.RESTMapping), updatedResources: make(map[schema.GroupKind]*kindInfo),
updatedNamespaces: make(sets.Set[string]), updatedNamespaces: make(sets.Set[string]),
parentRef: parent, parentRef: parent,
toolingID: tooling, toolingID: tooling,
@ -284,7 +293,7 @@ func (a *ApplySet) fetchParent() error {
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()) 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())
} }
if a.currentResources, err = parseResourcesAnnotation(annotations, a.restMapper); err != nil { if a.currentResources, err = parseKindAnnotation(annotations, a.restMapper); err != nil {
// TODO: handle GVRs for now-deleted CRDs // TODO: handle GVRs for now-deleted CRDs
return fmt.Errorf("parsing ApplySet annotation on %q: %w", a.parentRef, err) return fmt.Errorf("parsing ApplySet annotation on %q: %w", a.parentRef, err)
} }
@ -302,8 +311,8 @@ func (a *ApplySet) LabelSelectorForMembers() string {
// AllPrunableResources returns the list of all resources that should be considered for pruning. // AllPrunableResources returns the list of all resources that should be considered for pruning.
// This is potentially a superset of the resources types that actually contain resources. // This is potentially a superset of the resources types that actually contain resources.
func (a *ApplySet) AllPrunableResources() []*meta.RESTMapping { func (a *ApplySet) AllPrunableResources() []*kindInfo {
var ret []*meta.RESTMapping var ret []*kindInfo
for _, m := range a.currentResources { for _, m := range a.currentResources {
ret = append(ret, m) ret = append(ret, m)
} }
@ -336,14 +345,43 @@ func toolingBaseName(toolAnnotation string) string {
return toolAnnotation return toolAnnotation
} }
func parseResourcesAnnotation(annotations map[string]string, mapper meta.RESTMapper) (map[schema.GroupVersionResource]*meta.RESTMapping, error) { // kindInfo holds type information about a particular resource type.
annotation, ok := annotations[ApplySetGRsAnnotation] type kindInfo struct {
restMapping *meta.RESTMapping
}
func parseKindAnnotation(annotations map[string]string, mapper meta.RESTMapper) (map[schema.GroupKind]*kindInfo, error) {
annotation, ok := annotations[ApplySetGKsAnnotation]
if !ok { if !ok {
if annotations[DeprecatedApplySetGRsAnnotation] != "" {
return parseDeprecatedResourceAnnotation(annotations[DeprecatedApplySetGRsAnnotation], mapper)
}
// The spec does not require this annotation. However, 'missing' means 'perform discovery'. // The spec does not require this annotation. However, 'missing' means 'perform discovery'.
// We return an error because we do not currently support dynamic discovery in kubectl apply. // We return an error because we do not currently support dynamic discovery in kubectl apply.
return nil, fmt.Errorf("kubectl requires the %q annotation to be set on all ApplySet parent objects", ApplySetGRsAnnotation) return nil, fmt.Errorf("kubectl requires the %q annotation to be set on all ApplySet parent objects", ApplySetGKsAnnotation)
} }
mappings := make(map[schema.GroupVersionResource]*meta.RESTMapping) mappings := make(map[schema.GroupKind]*kindInfo)
// Annotation present but empty means that this is currently an empty set.
if annotation == "" {
return mappings, nil
}
for _, gkString := range strings.Split(annotation, ",") {
gk := schema.ParseGroupKind(gkString)
restMapping, err := mapper.RESTMapping(gk)
if err != nil {
return nil, fmt.Errorf("could not find mapping for kind in %q annotation: %w", ApplySetGKsAnnotation, err)
}
mappings[gk] = &kindInfo{
restMapping: restMapping,
}
}
return mappings, nil
}
func parseDeprecatedResourceAnnotation(annotation string, mapper meta.RESTMapper) (map[schema.GroupKind]*kindInfo, error) {
mappings := make(map[schema.GroupKind]*kindInfo)
// Annotation present but empty means that this is currently an empty set. // Annotation present but empty means that this is currently an empty set.
if annotation == "" { if annotation == "" {
return mappings, nil return mappings, nil
@ -352,13 +390,15 @@ func parseResourcesAnnotation(annotations map[string]string, mapper meta.RESTMap
gr := schema.ParseGroupResource(grString) gr := schema.ParseGroupResource(grString)
gvk, err := mapper.KindFor(gr.WithVersion("")) gvk, err := mapper.KindFor(gr.WithVersion(""))
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid group resource in %q annotation: %w", ApplySetGRsAnnotation, err) return nil, fmt.Errorf("invalid group resource in %q annotation: %w", DeprecatedApplySetGRsAnnotation, err)
} }
mapping, err := mapper.RESTMapping(gvk.GroupKind()) restMapping, err := mapper.RESTMapping(gvk.GroupKind())
if err != nil { if err != nil {
return nil, fmt.Errorf("could not find kind for resource in %q annotation: %w", ApplySetGRsAnnotation, err) return nil, fmt.Errorf("could not find kind for resource in %q annotation: %w", DeprecatedApplySetGRsAnnotation, err)
}
mappings[gvk.GroupKind()] = &kindInfo{
restMapping: restMapping,
} }
mappings[mapping.Resource] = mapping
} }
return mappings, nil return mappings, nil
} }
@ -377,9 +417,14 @@ func parseNamespacesAnnotation(annotations map[string]string) sets.Set[string] {
// addResource registers the given resource and namespace as being part of the updated set of // addResource registers the given resource and namespace as being part of the updated set of
// resources being applied by the current operation. // resources being applied by the current operation.
func (a *ApplySet) addResource(resource *meta.RESTMapping, namespace string) { func (a *ApplySet) addResource(restMapping *meta.RESTMapping, namespace string) {
a.updatedResources[resource.Resource] = resource gk := restMapping.GroupVersionKind.GroupKind()
if resource.Scope == meta.RESTScopeNamespace && namespace != "" { if _, found := a.updatedResources[gk]; !found {
a.updatedResources[gk] = &kindInfo{
restMapping: restMapping,
}
}
if restMapping.Scope == meta.RESTScopeNamespace && namespace != "" {
a.updatedNamespaces.Insert(namespace) a.updatedNamespaces.Insert(namespace)
} }
} }
@ -394,6 +439,8 @@ func (a *ApplySet) updateParent(mode ApplySetUpdateMode, dryRun cmdutil.DryRunSt
if err != nil { if err != nil {
return fmt.Errorf("failed to encode patch for ApplySet parent: %w", err) return fmt.Errorf("failed to encode patch for ApplySet parent: %w", err)
} }
// Note that because we are using SSA, we will remove any annotations we don't specify,
// which is how we remove the deprecated contains-group-resources annotation.
err = serverSideApplyRequest(a, data, dryRun, validation, false) err = serverSideApplyRequest(a, data, dryRun, validation, false)
if err != nil && errors.IsConflict(err) { if err != nil && errors.IsConflict(err) {
// Try again with conflicts forced // Try again with conflicts forced
@ -429,17 +476,17 @@ func serverSideApplyRequest(a *ApplySet, data []byte, dryRun cmdutil.DryRunStrat
} }
func (a *ApplySet) buildParentPatch(mode ApplySetUpdateMode) *metav1.PartialObjectMetadata { func (a *ApplySet) buildParentPatch(mode ApplySetUpdateMode) *metav1.PartialObjectMetadata {
var newGRsAnnotation, newNsAnnotation string var newGKsAnnotation, newNsAnnotation string
switch mode { switch mode {
case updateToSuperset: case updateToSuperset:
// If the apply succeeded but pruning failed, the set of group resources that // If the apply succeeded but pruning failed, the set of group resources that
// the ApplySet should track is the superset of the previous and current resources. // the ApplySet should track is the superset of the previous and current resources.
// This ensures that the resources that failed to be pruned are not orphaned from the set. // This ensures that the resources that failed to be pruned are not orphaned from the set.
grSuperset := sets.KeySet(a.currentResources).Union(sets.KeySet(a.updatedResources)) grSuperset := sets.KeySet(a.currentResources).Union(sets.KeySet(a.updatedResources))
newGRsAnnotation = generateResourcesAnnotation(grSuperset) newGKsAnnotation = generateKindsAnnotation(grSuperset)
newNsAnnotation = generateNamespacesAnnotation(a.currentNamespaces.Union(a.updatedNamespaces), a.parentRef.Namespace) newNsAnnotation = generateNamespacesAnnotation(a.currentNamespaces.Union(a.updatedNamespaces), a.parentRef.Namespace)
case updateToLatestSet: case updateToLatestSet:
newGRsAnnotation = generateResourcesAnnotation(sets.KeySet(a.updatedResources)) newGKsAnnotation = generateKindsAnnotation(sets.KeySet(a.updatedResources))
newNsAnnotation = generateNamespacesAnnotation(a.updatedNamespaces, a.parentRef.Namespace) newNsAnnotation = generateNamespacesAnnotation(a.updatedNamespaces, a.parentRef.Namespace)
} }
@ -453,7 +500,7 @@ func (a *ApplySet) buildParentPatch(mode ApplySetUpdateMode) *metav1.PartialObje
Namespace: a.parentRef.Namespace, Namespace: a.parentRef.Namespace,
Annotations: map[string]string{ Annotations: map[string]string{
ApplySetToolingAnnotation: a.toolingID.String(), ApplySetToolingAnnotation: a.toolingID.String(),
ApplySetGRsAnnotation: newGRsAnnotation, ApplySetGKsAnnotation: newGKsAnnotation,
ApplySetAdditionalNamespacesAnnotation: newNsAnnotation, ApplySetAdditionalNamespacesAnnotation: newNsAnnotation,
}, },
Labels: map[string]string{ Labels: map[string]string{
@ -469,13 +516,13 @@ func generateNamespacesAnnotation(namespaces sets.Set[string], skip string) stri
return strings.Join(nsList, ",") return strings.Join(nsList, ",")
} }
func generateResourcesAnnotation(resources sets.Set[schema.GroupVersionResource]) string { func generateKindsAnnotation(resources sets.Set[schema.GroupKind]) string {
var grs []string var gks []string
for gvr := range resources { for gk := range resources {
grs = append(grs, gvr.GroupResource().String()) gks = append(gks, gk.String())
} }
sort.Strings(grs) sort.Strings(gks)
return strings.Join(grs, ",") return strings.Join(gks, ",")
} }
func (a ApplySet) FieldManager() string { func (a ApplySet) FieldManager() string {

View File

@ -77,27 +77,29 @@ func (a *ApplySet) FindAllObjectsToPrune(ctx context.Context, dynamicClient dyna
// We run discovery in parallel, in as many goroutines as priority and fairness will allow // We run discovery in parallel, in as many goroutines as priority and fairness will allow
// (We don't expect many requests in real-world scenarios - maybe tens, unlikely to be hundreds) // (We don't expect many requests in real-world scenarios - maybe tens, unlikely to be hundreds)
for _, restMapping := range a.AllPrunableResources() { for gvk, resource := range a.AllPrunableResources() {
switch restMapping.Scope.Name() { scope := resource.restMapping.Scope
switch scope.Name() {
case meta.RESTScopeNameNamespace: case meta.RESTScopeNameNamespace:
for _, namespace := range a.AllPrunableNamespaces() { for _, namespace := range a.AllPrunableNamespaces() {
if namespace == "" { if namespace == "" {
// Just double-check because otherwise we get cryptic error messages // Just double-check because otherwise we get cryptic error messages
return nil, fmt.Errorf("unexpectedly encountered empty namespace during prune of namespace-scoped resource %v", restMapping.GroupVersionKind) return nil, fmt.Errorf("unexpectedly encountered empty namespace during prune of namespace-scoped resource %v", gvk)
} }
tasks = append(tasks, &task{ tasks = append(tasks, &task{
namespace: namespace, namespace: namespace,
restMapping: restMapping, restMapping: resource.restMapping,
}) })
} }
case meta.RESTScopeNameRoot: case meta.RESTScopeNameRoot:
tasks = append(tasks, &task{ tasks = append(tasks, &task{
restMapping: restMapping, restMapping: resource.restMapping,
}) })
default: default:
return nil, fmt.Errorf("unhandled scope %q", restMapping.Scope.Name()) return nil, fmt.Errorf("unhandled scope %q", scope.Name())
} }
} }

View File

@ -4,6 +4,6 @@ metadata:
name: my-set name: my-set
annotations: annotations:
applyset.kubernetes.io/tooling: kubectl/v0.0.0 applyset.kubernetes.io/tooling: kubectl/v0.0.0
applyset.kubernetes.io/contains-group-resources: "" applyset.kubernetes.io/contains-group-kinds: ""
labels: labels:
applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1 applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1