diff --git a/go.mod b/go.mod index aaa84b64e..95503d535 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( k8s.io/kubectl v0.31.1 k8s.io/kubelet v0.31.1 k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 - sigs.k8s.io/controller-runtime v0.19.0 + sigs.k8s.io/controller-runtime v0.19.4 sigs.k8s.io/yaml v1.4.0 ) diff --git a/go.sum b/go.sum index 5218d3078..b808f5f20 100644 --- a/go.sum +++ b/go.sum @@ -456,8 +456,6 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -605,8 +603,8 @@ k8s.io/utils v0.0.0-20240921022957-49e7df575cb6 h1:MDF6h2H/h4tbzmtIKTuctcwZmY0tY k8s.io/utils v0.0.0-20240921022957-49e7df575cb6/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo= oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo= -sigs.k8s.io/controller-runtime v0.19.0 h1:nWVM7aq+Il2ABxwiCizrVDSlmDcshi9llbaFbC0ji/Q= -sigs.k8s.io/controller-runtime v0.19.0/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +sigs.k8s.io/controller-runtime v0.19.4 h1:SUmheabttt0nx8uJtoII4oIP27BVVvAKFvdvGFwV/Qo= +sigs.k8s.io/controller-runtime v0.19.4/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g= diff --git a/pkg/tasks/cleanup.go b/pkg/tasks/cleanup.go new file mode 100644 index 000000000..183dc2d7f --- /dev/null +++ b/pkg/tasks/cleanup.go @@ -0,0 +1,130 @@ +/* +Copyright 2025 The KubeOne Authors. + +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. +*/ + +package tasks + +import ( + "maps" + + "k8c.io/kubeone/pkg/fail" + "k8c.io/kubeone/pkg/state" + + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + crclient "sigs.k8s.io/controller-runtime/pkg/client" +) + +func cleanupLeftovers(st *state.State) error { + st.Logger.Infoln("Cleanup stale objects...") + + var cleanupObjects []crclient.Object + cleanupObjects = append(cleanupObjects, kuredObjects()...) + +NextObject: + for _, obj := range cleanupObjects { + originalLabels := maps.Clone(obj.GetLabels()) + obj.SetLabels(map[string]string{}) + + err := st.DynamicClient.Get(st.Context, crclient.ObjectKeyFromObject(obj), obj) + switch { + case apierrors.IsNotFound(err): + continue NextObject + case err != nil: + return fail.KubeClient(err, "checking stale object %s %q", obj.GetObjectKind().GroupVersionKind().String(), crclient.ObjectKeyFromObject(obj)) + } + + realLabels := obj.GetLabels() + + // compare requested labels to the real of, if not match -> let the object live + for cleanupKey, cleanupValue := range originalLabels { + if val, ok := realLabels[cleanupKey]; !ok || val != cleanupValue { + st.Logger.Debugf("skip deleting object as labels are different: %s %q", obj.GetObjectKind().GroupVersionKind().String(), crclient.ObjectKeyFromObject(obj)) + + continue NextObject + } + } + + if err := st.DynamicClient.Delete(st.Context, obj); crclient.IgnoreNotFound(err) != nil { + return fail.KubeClient(err, "deleting stale object %s %q", obj.GetObjectKind().GroupVersionKind().String(), crclient.ObjectKeyFromObject(obj)) + } + + st.Logger.Debugf("deleted stale object %s %q", obj.GetObjectKind().GroupVersionKind().String(), crclient.ObjectKeyFromObject(obj)) + } + + return nil +} + +func kuredObjects() []crclient.Object { + labels := map[string]string{"kubeone.io/addon": "unattended-upgrades"} + unstructuredLabels := withLabels(labels) + + cleanupObjects := []crclient.Object{ + &rbacv1.ClusterRole{ + ObjectMeta: metav1.ObjectMeta{ + Name: "kured", + Labels: labels, + }, + }, + newUnstructured( + "rbac.authorization.k8s.io/v1", + "ClusterRoleBinding", + crclient.ObjectKey{Name: "kured"}, + unstructuredLabels, + ), + newUnstructured( + "rbac.authorization.k8s.io/v1", + "Role", + crclient.ObjectKey{Name: "kured", Namespace: "kube-system"}, + unstructuredLabels, + ), + newUnstructured( + "rbac.authorization.k8s.io/v1", + "RoleBinding", + crclient.ObjectKey{Name: "kured", Namespace: "kube-system"}, + unstructuredLabels, + ), + newUnstructured( + "v1", + "ServiceAccount", + crclient.ObjectKey{Name: "kured", Namespace: "kube-system"}, + unstructuredLabels, + ), + } + + return cleanupObjects +} + +func newUnstructured(apiVersion string, kind string, identity crclient.ObjectKey, opts ...func(*metav1unstructured.Unstructured)) crclient.Object { + obj := &metav1unstructured.Unstructured{} + obj.SetAPIVersion(apiVersion) + obj.SetKind(kind) + obj.SetName(identity.Name) + obj.SetNamespace(identity.Namespace) + + for _, opt := range opts { + opt(obj) + } + + return obj +} + +func withLabels(labels map[string]string) func(*metav1unstructured.Unstructured) { + return func(u *metav1unstructured.Unstructured) { + u.SetLabels(labels) + } +} diff --git a/pkg/tasks/tasks.go b/pkg/tasks/tasks.go index 92ea1b20f..f7fc7c40e 100644 --- a/pkg/tasks/tasks.go +++ b/pkg/tasks/tasks.go @@ -276,6 +276,11 @@ func WithResources(t Tasks) Tasks { Description: "ensure caBundle configMap", Predicate: func(s *state.State) bool { return s.Cluster.CABundle != "" }, }, + { + Fn: cleanupLeftovers, + Operation: "cleaning up any addons leftovers", + Description: "clean up any addons leftovers", + }, { Fn: addons.Ensure, Operation: "applying addons",