diff --git a/api/accurate/v1/subnamespace_types.go b/api/accurate/v1/subnamespace_types.go index ac083e7..0ef252f 100644 --- a/api/accurate/v1/subnamespace_types.go +++ b/api/accurate/v1/subnamespace_types.go @@ -28,7 +28,6 @@ type SubNamespaceSpec struct { } //+kubebuilder:object:root=true -//+kubebuilder:storageversion // SubNamespace is the Schema for the subnamespaces API type SubNamespace struct { diff --git a/api/accurate/v2alpha1/subnamespace_types.go b/api/accurate/v2alpha1/subnamespace_types.go index 73cfeb2..67e5adf 100644 --- a/api/accurate/v2alpha1/subnamespace_types.go +++ b/api/accurate/v2alpha1/subnamespace_types.go @@ -32,6 +32,7 @@ type SubNamespaceSpec struct { } //+kubebuilder:object:root=true +//+kubebuilder:storageversion //+kubebuilder:subresource:status //+genclient @@ -61,3 +62,7 @@ type SubNamespaceList struct { func init() { SchemeBuilder.Register(&SubNamespace{}, &SubNamespaceList{}) } + +const ( + SubNamespaceConflict string = "Conflict" +) diff --git a/charts/accurate/templates/generated/crds.yaml b/charts/accurate/templates/generated/crds.yaml index df07db7..878ea9a 100644 --- a/charts/accurate/templates/generated/crds.yaml +++ b/charts/accurate/templates/generated/crds.yaml @@ -65,7 +65,7 @@ spec: type: string type: object served: true - storage: true + storage: false - name: v2alpha1 schema: openAPIV3Schema: @@ -150,7 +150,7 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} {{- end }} diff --git a/config/crd/bases/accurate.cybozu.com_subnamespaces.yaml b/config/crd/bases/accurate.cybozu.com_subnamespaces.yaml index e957791..8cacd4b 100644 --- a/config/crd/bases/accurate.cybozu.com_subnamespaces.yaml +++ b/config/crd/bases/accurate.cybozu.com_subnamespaces.yaml @@ -54,7 +54,7 @@ spec: type: string type: object served: true - storage: true + storage: false - name: v2alpha1 schema: openAPIV3Schema: @@ -156,6 +156,6 @@ spec: type: object type: object served: true - storage: false + storage: true subresources: status: {} diff --git a/controllers/namespace_controller.go b/controllers/namespace_controller.go index b761e0b..457af8c 100644 --- a/controllers/namespace_controller.go +++ b/controllers/namespace_controller.go @@ -6,7 +6,7 @@ import ( "path" "reflect" - accuratev1 "github.com/cybozu-go/accurate/api/accurate/v1" + accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1" "github.com/cybozu-go/accurate/pkg/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -101,7 +101,7 @@ func (r *NamespaceReconciler) propagateMeta(ctx context.Context, ns, parent *cor } if _, ok := ns.Labels[constants.LabelParent]; ok { - subNS := &accuratev1.SubNamespace{} + subNS := &accuratev2alpha1.SubNamespace{} err := r.Get(ctx, types.NamespacedName{Name: ns.Name, Namespace: parent.Name}, subNS) if err != nil { if !apierrors.IsNotFound(err) { @@ -385,7 +385,7 @@ func (r *NamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&corev1.Namespace{}). - Watches(&accuratev1.SubNamespace{}, handler.Funcs{ + Watches(&accuratev2alpha1.SubNamespace{}, handler.Funcs{ CreateFunc: func(ctx context.Context, ev event.CreateEvent, q workqueue.RateLimitingInterface) { subNSHandler(ev.Object, q) }, diff --git a/controllers/namespace_controller_test.go b/controllers/namespace_controller_test.go index 01772e0..3e8f849 100644 --- a/controllers/namespace_controller_test.go +++ b/controllers/namespace_controller_test.go @@ -5,7 +5,7 @@ import ( "errors" "time" - accuratev1 "github.com/cybozu-go/accurate/api/accurate/v1" + accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1" "github.com/cybozu-go/accurate/pkg/constants" "github.com/cybozu-go/accurate/pkg/indexing" . "github.com/onsi/ginkgo/v2" @@ -529,7 +529,7 @@ var _ = Describe("Namespace controller", func() { Expect(err).NotTo(HaveOccurred()) By("creating a SubNamespace for sub1 namespace") - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} sn.Namespace = "root" sn.Name = "sub1" sn.Spec.Labels = map[string]string{ diff --git a/controllers/subnamespace_controller.go b/controllers/subnamespace_controller.go index db5e6fc..0e4d6c5 100644 --- a/controllers/subnamespace_controller.go +++ b/controllers/subnamespace_controller.go @@ -2,14 +2,20 @@ package controllers import ( "context" + "encoding/json" "fmt" + "time" - accuratev1 "github.com/cybozu-go/accurate/api/accurate/v1" + accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1" + accuratev2alpha1ac "github.com/cybozu-go/accurate/internal/applyconfigurations/accurate/v2alpha1" "github.com/cybozu-go/accurate/pkg/constants" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" + kstatus "sigs.k8s.io/cli-utils/pkg/kstatus/status" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -19,6 +25,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) +const ( + fieldOwner client.FieldOwner = "accurate-controller" +) + // SubNamespaceReconciler reconciles a SubNamespace object type SubNamespaceReconciler struct { client.Client @@ -33,7 +43,7 @@ type SubNamespaceReconciler struct { func (r *SubNamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} if err := r.Get(ctx, req.NamespacedName, sn); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -54,7 +64,7 @@ func (r *SubNamespaceReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, nil } -func (r *SubNamespaceReconciler) finalize(ctx context.Context, sn *accuratev1.SubNamespace) error { +func (r *SubNamespaceReconciler) finalize(ctx context.Context, sn *accuratev2alpha1.SubNamespace) error { if !controllerutil.ContainsFinalizer(sn, constants.Finalizer) { return nil } @@ -89,7 +99,7 @@ DELETE: return r.Update(ctx, sn) } -func (r *SubNamespaceReconciler) reconcileNS(ctx context.Context, sn *accuratev1.SubNamespace) error { +func (r *SubNamespaceReconciler) reconcileNS(ctx context.Context, sn *accuratev2alpha1.SubNamespace) error { logger := log.FromContext(ctx) ns := &corev1.Namespace{} @@ -110,14 +120,27 @@ func (r *SubNamespaceReconciler) reconcileNS(ctx context.Context, sn *accuratev1 logger.Info("created a sub namespace", "name", sn.Name) } - if ns.Labels[constants.LabelParent] == sn.Namespace { - sn.Status = accuratev1.SubNamespaceOK - } else { + ac := accuratev2alpha1ac.SubNamespace(sn.Name, sn.Namespace). + WithStatus( + accuratev2alpha1ac.SubNamespaceStatus(). + WithObservedGeneration(sn.Generation), + ) + + if ns.Labels[constants.LabelParent] != sn.Namespace { logger.Info("a conflicting namespace already exists") - sn.Status = accuratev1.SubNamespaceConflict + ac.Status.WithConditions( + newStatusCondition(sn.Status.Conditions, + metav1.Condition{ + Type: string(kstatus.ConditionStalled), + Status: metav1.ConditionTrue, + ObservedGeneration: sn.Generation, + Reason: accuratev2alpha1.SubNamespaceConflict, + Message: "Conflicting namespace already exists", + }), + ) } - return r.Update(ctx, sn) + return r.patchSubNamespaceStatus(ctx, ac) } // SetupWithManager sets up the controller with the Manager. @@ -134,7 +157,7 @@ func (r *SubNamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). - For(&accuratev1.SubNamespace{}). + For(&accuratev2alpha1.SubNamespace{}). Watches(&corev1.Namespace{}, handler.Funcs{ UpdateFunc: func(ctx context.Context, ev event.UpdateEvent, q workqueue.RateLimitingInterface) { if ev.ObjectNew.GetDeletionTimestamp() != nil { @@ -148,3 +171,46 @@ func (r *SubNamespaceReconciler) SetupWithManager(mgr ctrl.Manager) error { }). Complete(r) } + +func newStatusCondition(existingConditions []metav1.Condition, newCondition metav1.Condition) metav1.Condition { + existingCondition := meta.FindStatusCondition(existingConditions, newCondition.Type) + if existingCondition != nil && existingCondition.Status == newCondition.Status { + newCondition.LastTransitionTime = existingCondition.LastTransitionTime + } + + if newCondition.LastTransitionTime.IsZero() { + newCondition.LastTransitionTime = metav1.NewTime(time.Now()) + } + + return newCondition +} + +func (r *SubNamespaceReconciler) patchSubNamespaceStatus(ctx context.Context, ac *accuratev2alpha1ac.SubNamespaceApplyConfiguration) error { + sn := &accuratev2alpha1.SubNamespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: *ac.Name, + Namespace: *ac.Namespace, + }, + } + + encodedPatch, err := json.Marshal(ac) + if err != nil { + return err + } + + return r.Status().Patch(ctx, sn, applyPatch{encodedPatch}, fieldOwner, client.ForceOwnership) +} + +type applyPatch struct { + patch []byte +} + +var _ client.Patch = applyPatch{} + +func (p applyPatch) Type() types.PatchType { + return types.ApplyPatchType +} + +func (p applyPatch) Data(_ client.Object) ([]byte, error) { + return p.patch, nil +} diff --git a/controllers/subnamespace_controller_test.go b/controllers/subnamespace_controller_test.go index 21b7c01..220a5cc 100644 --- a/controllers/subnamespace_controller_test.go +++ b/controllers/subnamespace_controller_test.go @@ -4,7 +4,7 @@ import ( "context" "time" - accuratev1 "github.com/cybozu-go/accurate/api/accurate/v1" + accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1" "github.com/cybozu-go/accurate/pkg/constants" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" @@ -57,7 +57,7 @@ var _ = Describe("SubNamespace controller", func() { err := k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} sn.Namespace = "test1" sn.Name = "test1-sub1" sn.Finalizers = []string{constants.Finalizer} @@ -72,14 +72,15 @@ var _ = Describe("SubNamespace controller", func() { Expect(sub1.Labels).To(HaveKeyWithValue(constants.LabelCreatedBy, "accurate")) Expect(sub1.Labels).To(HaveKeyWithValue(constants.LabelParent, "test1")) - Eventually(func() accuratev1.SubNamespaceStatus { - sn = &accuratev1.SubNamespace{} + Eventually(func() int64 { + sn = &accuratev2alpha1.SubNamespace{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "test1", Name: "test1-sub1"}, sn) if err != nil { - return "" + return 0 } - return sn.Status - }).Should(Equal(accuratev1.SubNamespaceOK)) + return sn.Status.ObservedGeneration + }).Should(BeNumerically(">", 0)) + Expect(sn.Status.Conditions).To(BeEmpty()) err = k8sClient.Delete(ctx, sn) Expect(err).NotTo(HaveOccurred()) @@ -105,19 +106,21 @@ var _ = Describe("SubNamespace controller", func() { err = k8sClient.Create(ctx, ns2) Expect(err).NotTo(HaveOccurred()) - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} sn.Namespace = "test2" sn.Name = "test2-sub1" err = k8sClient.Create(ctx, sn) Expect(err).NotTo(HaveOccurred()) - Eventually(func() accuratev1.SubNamespaceStatus { - sn = &accuratev1.SubNamespace{} + Eventually(func() int64 { + sn = &accuratev2alpha1.SubNamespace{} if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "test2", Name: "test2-sub1"}, sn); err != nil { - return "" + return 0 } - return sn.Status - }).Should(Equal(accuratev1.SubNamespaceConflict)) + return sn.Status.ObservedGeneration + }).Should(BeNumerically(">", 0)) + Expect(sn.Status.Conditions).To(HaveLen(1)) + Expect(sn.Status.Conditions[0].Reason).To(Equal(accuratev2alpha1.SubNamespaceConflict)) }) It("should not delete a conflicting sub namespace", func() { @@ -126,7 +129,7 @@ var _ = Describe("SubNamespace controller", func() { err := k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} sn.Namespace = "test3" sn.Name = "test3-sub1" sn.Finalizers = []string{constants.Finalizer} @@ -143,14 +146,15 @@ var _ = Describe("SubNamespace controller", func() { err = k8sClient.Update(ctx, sub1) Expect(err).NotTo(HaveOccurred()) - Eventually(func() accuratev1.SubNamespaceStatus { - sn = &accuratev1.SubNamespace{} + Eventually(func() []metav1.Condition { + sn = &accuratev2alpha1.SubNamespace{} err = k8sClient.Get(ctx, client.ObjectKey{Namespace: "test3", Name: "test3-sub1"}, sn) if err != nil { - return "" + return nil } - return sn.Status - }).Should(Equal(accuratev1.SubNamespaceConflict)) + return sn.Status.Conditions + }).Should(HaveLen(1)) + Expect(sn.Status.Conditions[0].Reason).To(Equal(accuratev2alpha1.SubNamespaceConflict)) err = k8sClient.Delete(ctx, sn) Expect(err).NotTo(HaveOccurred()) @@ -170,7 +174,7 @@ var _ = Describe("SubNamespace controller", func() { err := k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) - sn := &accuratev1.SubNamespace{} + sn := &accuratev2alpha1.SubNamespace{} sn.Namespace = "test4" sn.Name = "test4-sub1" err = k8sClient.Create(ctx, sn)