diff --git a/go.mod b/go.mod
index 2379b0a3619..96859409f71 100644
--- a/go.mod
+++ b/go.mod
@@ -58,8 +58,10 @@ require (
)
require (
+ github.com/go-test/deep v1.1.0
github.com/minio/kes-go v0.1.0
golang.org/x/mod v0.10.0
+ sigs.k8s.io/controller-runtime v0.13.1
)
require (
@@ -85,6 +87,7 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/emicklei/go-restful/v3 v3.10.0 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
+ github.com/evanphx/json-patch/v5 v5.6.0 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/gdamore/tcell/v2 v2.5.4 // indirect
@@ -188,7 +191,6 @@ require (
k8s.io/apiextensions-apiserver v0.25.4 // indirect
k8s.io/gengo v0.0.0-20220902162205-c0856e24416d // indirect
k8s.io/kube-openapi v0.0.0-20230123231816-1cb3ae25d79a // indirect
- sigs.k8s.io/controller-runtime v0.13.1 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
diff --git a/go.sum b/go.sum
index c5baee2be95..11ed65bf308 100644
--- a/go.sum
+++ b/go.sum
@@ -287,6 +287,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
+github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
@@ -296,6 +298,8 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
+github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.5.4 h1:TGU4tSjD3sCL788vFNeJnTdzpNKIw1H5dgLnJRQVv/k=
@@ -316,6 +320,8 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
+github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
@@ -351,6 +357,8 @@ github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+
github.com/go-openapi/validate v0.22.2-0.20230810035134-348543c76e92 h1:aga9z7JlDIsEmk4rFNxEP1T129jdnzi2eYtG9HIdPR0=
github.com/go-openapi/validate v0.22.2-0.20230810035134-348543c76e92/go.mod h1:kVxh31KbfsxU8ZyoHaDbLBWU5CnMdqBUEtadQ2G4d5M=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
+github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
@@ -501,6 +509,7 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jedib0t/go-pretty/v6 v6.4.4 h1:N+gz6UngBPF4M288kiMURPHELDMIhF/Em35aYuKrsSc=
github.com/jedib0t/go-pretty/v6 v6.4.4/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -676,11 +685,14 @@ github.com/navidys/tvxwidgets v0.3.0/go.mod h1:Cr8CTnbinH2X8bY/vwb8914mku3qImHQ8
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
+github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
+github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.7.0 h1:/XxtEV3I3Eif/HobnVx9YmJgk8ENdRsuUmM+fLCFNow=
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
@@ -1241,6 +1253,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY=
+gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -1470,6 +1484,8 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go
index c61402a063a..41a49cce345 100644
--- a/pkg/controller/controller.go
+++ b/pkg/controller/controller.go
@@ -37,6 +37,7 @@ import (
promclientset "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
+ "sigs.k8s.io/controller-runtime/pkg/client"
)
const (
@@ -84,11 +85,15 @@ func StartOperator(kubeconfig string) {
if kubeconfig != "" {
cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
}
-
if err != nil {
klog.Fatalf("Error building kubeconfig: %s", err.Error())
}
+ k8sClient, err := client.New(cfg, client.Options{})
+ if err != nil {
+ klog.Fatalf("Error building k8sClient: %s", err.Error())
+ }
+
kubeClient, err := kubernetes.NewForConfig(cfg)
if err != nil {
klog.Fatalf("Error building Kubernetes clientset: %s", err.Error())
@@ -130,6 +135,7 @@ func StartOperator(kubeconfig string) {
podName,
namespaces,
kubeClient,
+ k8sClient,
controllerClient,
promClient,
kubeInformerFactory.Apps().V1().StatefulSets(),
diff --git a/pkg/controller/main-controller.go b/pkg/controller/main-controller.go
index 426c96d75b2..13427a9ed2c 100644
--- a/pkg/controller/main-controller.go
+++ b/pkg/controller/main-controller.go
@@ -61,6 +61,7 @@ import (
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
appslisters "k8s.io/client-go/listers/apps/v1"
corelisters "k8s.io/client-go/listers/core/v1"
+ "sigs.k8s.io/controller-runtime/pkg/client"
promclientset "github.com/prometheus-operator/prometheus-operator/pkg/client/versioned"
@@ -122,6 +123,8 @@ type Controller struct {
podName string
// namespacesToWatch restricts the action of the opreator to a list of namespaces
namespacesToWatch set.StringSet
+ // k8sClient is a kubernetes client
+ k8sClient client.Client
// kubeClientSet is a standard kubernetes clientset
kubeClientSet kubernetes.Interface
// minioClientSet is a clientset for our own API group
@@ -213,7 +216,7 @@ type EventNotification struct {
}
// NewController returns a new sample controller
-func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSet kubernetes.Interface, minioClientSet clientset.Interface, promClient promclientset.Interface, statefulSetInformer appsinformers.StatefulSetInformer, deploymentInformer appsinformers.DeploymentInformer, podInformer coreinformers.PodInformer, tenantInformer informers.TenantInformer, policyBindingInformer stsInformers.PolicyBindingInformer, serviceInformer coreinformers.ServiceInformer, hostsTemplate, operatorVersion string) *Controller {
+func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSet kubernetes.Interface, k8sClient client.Client, minioClientSet clientset.Interface, promClient promclientset.Interface, statefulSetInformer appsinformers.StatefulSetInformer, deploymentInformer appsinformers.DeploymentInformer, podInformer coreinformers.PodInformer, tenantInformer informers.TenantInformer, policyBindingInformer stsInformers.PolicyBindingInformer, serviceInformer coreinformers.ServiceInformer, hostsTemplate, operatorVersion string) *Controller {
// Create event broadcaster
// Add minio-controller types to the default Kubernetes Scheme so Events can be
// logged for minio-controller types.
@@ -246,6 +249,7 @@ func NewController(podName string, namespacesToWatch set.StringSet, kubeClientSe
podName: podName,
namespacesToWatch: namespacesToWatch,
kubeClientSet: kubeClientSet,
+ k8sClient: k8sClient,
minioClientSet: minioClientSet,
promClient: promClient,
statefulSetLister: statefulSetInformer.Lister(),
diff --git a/pkg/controller/pdb.go b/pkg/controller/pdb.go
index 0e8a7e9d5b8..4c97a49eea1 100644
--- a/pkg/controller/pdb.go
+++ b/pkg/controller/pdb.go
@@ -16,21 +16,19 @@ package controller
import (
"context"
- "encoding/json"
"fmt"
"strings"
"sync"
v2 "github.com/minio/operator/pkg/apis/minio.min.io/v2"
+ "github.com/minio/operator/pkg/runtime"
v1 "k8s.io/api/policy/v1"
"k8s.io/api/policy/v1beta1"
- k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
- "k8s.io/apimachinery/pkg/runtime/schema"
- "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/klog/v2"
+ "sigs.k8s.io/controller-runtime/pkg/client"
)
// DeletePDB - delete PDB for tenant
@@ -39,33 +37,39 @@ func (c *Controller) DeletePDB(ctx context.Context, t *v2.Tenant) (err error) {
if !available.Available() {
return nil
}
+ listOpt := &client.ListOptions{
+ Namespace: t.Namespace,
+ }
+ client.MatchingLabels{
+ v2.TenantLabel: t.Name,
+ }.ApplyToList(listOpt)
if available.V1Available() {
- err = c.kubeClientSet.PolicyV1().PodDisruptionBudgets(t.Namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{
- LabelSelector: fmt.Sprintf("%s=%s", v2.TenantLabel, t.Name),
- })
+ pdbS := &v1.PodDisruptionBudgetList{}
+ err = c.k8sClient.List(ctx, pdbS, listOpt)
if err != nil {
- // don't exist
- if k8serrors.IsNotFound(err) {
- return nil
- }
- klog.Errorf("Delete tenant %s's V1.PDB failed:%s", t.Name, err.Error())
return err
}
+ for _, item := range pdbS.Items {
+ err = c.k8sClient.Delete(ctx, &item)
+ if err != nil {
+ return err
+ }
+ }
}
if available.V1BetaAvailable() {
- err := c.kubeClientSet.PolicyV1beta1().PodDisruptionBudgets(t.Namespace).DeleteCollection(ctx, metav1.DeleteOptions{}, metav1.ListOptions{
- LabelSelector: fmt.Sprintf("%s=%s", v2.TenantLabel, t.Name),
- })
+ pdbS := &v1beta1.PodDisruptionBudgetList{}
+ err = c.k8sClient.List(ctx, pdbS, listOpt)
if err != nil {
- // don't exist
- if k8serrors.IsNotFound(err) {
- return nil
- }
- klog.Errorf("Delete tenant %s's V1Beta.PDB failed:%s", t.Name, err.Error())
return err
}
+ for _, item := range pdbS.Items {
+ err = c.k8sClient.Delete(ctx, &item)
+ if err != nil {
+ return err
+ }
+ }
}
- return nil
+ return err
}
// CreateOrUpdatePDB - hold PDB as expected
@@ -92,127 +96,48 @@ func (c *Controller) CreateOrUpdatePDB(ctx context.Context, t *v2.Tenant) (err e
return nil
}
}
+ var pdbI client.Object
if available.V1Available() {
- pdbName := t.Name + "-" + pool.Name
- var pdb *v1.PodDisruptionBudget
- var isCreate bool
- pdb, err = c.kubeClientSet.PolicyV1().PodDisruptionBudgets(t.Namespace).Get(ctx, pdbName, metav1.GetOptions{})
- if err != nil {
- if k8serrors.IsNotFound(err) {
- pdb = &v1.PodDisruptionBudget{}
- isCreate = true
- } else {
- return err
- }
- }
- if !isCreate {
- // exist and as expected
- if pdb.Spec.MinAvailable != nil && pdb.Spec.MinAvailable.IntValue() == (int(pool.Servers/2)+1) {
- continue
- }
- }
- // set filed we expected
- pdb.Name = pdbName
- pdb.Namespace = t.Namespace
- minAvailable := intstr.FromInt(int(pool.Servers/2) + 1)
- pdb.Spec.MinAvailable = &minAvailable
- pdb.Labels = map[string]string{
- v2.TenantLabel: t.Name,
- v2.PoolLabel: pool.Name,
- }
- pdb.Spec.Selector = metav1.SetAsLabelSelector(labels.Set{
- v2.TenantLabel: t.Name,
- v2.PoolLabel: pool.Name,
- })
- pdb.OwnerReferences = []metav1.OwnerReference{
- *metav1.NewControllerRef(t, schema.GroupVersionKind{
- Group: v2.SchemeGroupVersion.Group,
- Version: v2.SchemeGroupVersion.Version,
- Kind: v2.MinIOCRDResourceKind,
- }),
- }
- if isCreate {
- _, err = c.kubeClientSet.PolicyV1().PodDisruptionBudgets(t.Namespace).Create(ctx, pdb, metav1.CreateOptions{})
- if err != nil {
- return err
- }
- } else {
- patchData := map[string]interface{}{
- "spec": map[string]interface{}{
- "minAvailable": pdb.Spec.MinAvailable,
- },
- }
- pData, err := json.Marshal(patchData)
- if err != nil {
- return err
- }
- _, err = c.kubeClientSet.PolicyV1().PodDisruptionBudgets(t.Namespace).Patch(ctx, pdbName, types.MergePatchType, pData, metav1.PatchOptions{})
- if err != nil {
- return err
- }
- }
+ pdbI = &v1.PodDisruptionBudget{}
+ } else if available.V1BetaAvailable() {
+ pdbI = &v1beta1.PodDisruptionBudget{}
+ } else {
+ return nil
}
- if available.V1BetaAvailable() {
- pdbName := t.Name + "-" + pool.Name
- var pdb *v1beta1.PodDisruptionBudget
- var isCreate bool
- pdb, err = c.kubeClientSet.PolicyV1beta1().PodDisruptionBudgets(t.Namespace).Get(ctx, pdbName, metav1.GetOptions{})
- if err != nil {
- if k8serrors.IsNotFound(err) {
- pdb = &v1beta1.PodDisruptionBudget{}
- isCreate = true
- } else {
- return err
- }
- }
- if !isCreate {
- // exist and as expected
- if pdb.Spec.MinAvailable != nil && pdb.Spec.MinAvailable.IntValue() == (int(pool.Servers/2)+1) {
- continue
+ pdbI.SetName(t.Name + "-" + pool.Name)
+ pdbI.SetNamespace(t.Namespace)
+ _, err := runtime.NewObjectSyncer(ctx, c.k8sClient, t, func() error {
+ if available.V1Available() {
+ pdb := pdbI.(*v1.PodDisruptionBudget)
+ minAvailable := intstr.FromInt(int(pool.Servers/2) + 1)
+ pdb.Spec.MinAvailable = &minAvailable
+ pdb.Labels = map[string]string{
+ v2.TenantLabel: t.Name,
+ v2.PoolLabel: pool.Name,
}
- }
- // set filed we expected
- pdb.Name = pdbName
- pdb.Namespace = t.Namespace
- minAvailable := intstr.FromInt(int(pool.Servers/2) + 1)
- pdb.Spec.MinAvailable = &minAvailable
- pdb.Labels = map[string]string{
- v2.TenantLabel: t.Name,
- v2.PoolLabel: pool.Name,
- }
- pdb.Spec.Selector = metav1.SetAsLabelSelector(labels.Set{
- v2.TenantLabel: t.Name,
- v2.PoolLabel: pool.Name,
- })
- pdb.OwnerReferences = []metav1.OwnerReference{
- *metav1.NewControllerRef(t, schema.GroupVersionKind{
- Group: v2.SchemeGroupVersion.Group,
- Version: v2.SchemeGroupVersion.Version,
- Kind: v2.MinIOCRDResourceKind,
- }),
- }
- if isCreate {
- _, err = c.kubeClientSet.PolicyV1beta1().PodDisruptionBudgets(t.Namespace).Create(ctx, pdb, metav1.CreateOptions{})
- if err != nil {
- return err
- }
- } else {
- patchData := map[string]interface{}{
- "spec": map[string]interface{}{
- "minAvailable": pdb.Spec.MinAvailable,
- },
- }
- pData, err := json.Marshal(patchData)
- if err != nil {
- return err
- }
- _, err = c.kubeClientSet.PolicyV1beta1().PodDisruptionBudgets(t.Namespace).Patch(ctx, pdbName, types.MergePatchType, pData, metav1.PatchOptions{})
- if err != nil {
- return err
+ pdb.Spec.Selector = metav1.SetAsLabelSelector(labels.Set{
+ v2.TenantLabel: t.Name,
+ v2.PoolLabel: pool.Name,
+ })
+ }
+ if available.V1BetaAvailable() {
+ pdb := pdbI.(*v1beta1.PodDisruptionBudget)
+ minAvailable := intstr.FromInt(int(pool.Servers/2) + 1)
+ pdb.Spec.MinAvailable = &minAvailable
+ pdb.Labels = map[string]string{
+ v2.TenantLabel: t.Name,
+ v2.PoolLabel: pool.Name,
}
+ pdb.Spec.Selector = metav1.SetAsLabelSelector(labels.Set{
+ v2.TenantLabel: t.Name,
+ v2.PoolLabel: pool.Name,
+ })
}
+ return nil
+ }, pdbI, runtime.SyncTypeCreateOrUpdate).Sync(ctx)
+ if err != nil {
+ return err
}
-
}
if len(t.Spec.Pools) == 0 {
return fmt.Errorf("%s empty pools", t.Name)
diff --git a/pkg/runtime/pkg.go b/pkg/runtime/pkg.go
new file mode 100644
index 00000000000..79c192aa25c
--- /dev/null
+++ b/pkg/runtime/pkg.go
@@ -0,0 +1,85 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package runtime
+
+import (
+ "context"
+ "fmt"
+ "reflect"
+
+ "k8s.io/apimachinery/pkg/runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+const (
+ eventNormal = "Normal"
+ eventWarning = "Warning"
+)
+
+var (
+ // ErrOwnerDeleted is returned when the object owner is marked for deletion.
+ ErrOwnerDeleted = fmt.Errorf("owner is deleted")
+
+ // ErrIgnore is returned for ignored errors.
+ // Ignored errors are treated by the syncer as successful syncs.
+ ErrIgnore = fmt.Errorf("ignored error")
+)
+
+// SyncType is for controlling syncer performance
+type SyncType string
+
+const (
+ // SyncTypeCreateOrUpdate - if not found will create, if existing will update the object
+ SyncTypeCreateOrUpdate = SyncType("CreateOrUpdate")
+ // SyncTypeCreateOrPatch - if not found will create, if existing will patch the object
+ SyncTypeCreateOrPatch = SyncType("CreateOrPatch")
+ // SyncTypeFoundToUpdate - if not found will do nothing, if existing will update the object
+ SyncTypeFoundToUpdate = SyncType("FoundToUpdate")
+ // SyncTypeFoundToPatch - if not found will do nothing, if existing will patch the object
+ SyncTypeFoundToPatch = SyncType("FoundToUpPatch")
+)
+
+// Syncer is for sync Object's action.
+type Syncer interface {
+ Sync(context.Context) (SyncResult, error)
+ ObjectOwner() runtime.Object
+}
+
+// SyncResult is a result of an Sync.
+type SyncResult struct {
+ Operation controllerutil.OperationResult
+ EventType string
+ EventReason string
+ EventMessage string
+}
+
+// SetEventData sets event data on an SyncResult.
+func (r *SyncResult) SetEventData(eventType, reason, message string) {
+ r.EventType = eventType
+ r.EventReason = reason
+ r.EventMessage = message
+}
+
+// EventReason sets the syncer result reason for kind
+func EventReason(obj client.Object, err error) string {
+ objKindName := reflect.TypeOf(obj).String()
+ if err != nil {
+ return fmt.Sprintf("%sSyncFailed", objKindName)
+ }
+ return fmt.Sprintf("%sSyncSuccessfull", objKindName)
+}
diff --git a/pkg/runtime/sync.go b/pkg/runtime/sync.go
new file mode 100644
index 00000000000..938453ec07a
--- /dev/null
+++ b/pkg/runtime/sync.go
@@ -0,0 +1,174 @@
+// This file is part of MinIO Operator
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package runtime
+
+import (
+ "context"
+ "errors"
+ "fmt"
+
+ "github.com/go-test/deep"
+ corev1 "k8s.io/api/core/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/klog/v2"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+// ObjectSyncer is a Syncer for sync Objects only by passing a MutateFn.
+type ObjectSyncer struct {
+ Client client.Client
+ Ctx context.Context
+ Obj client.Object
+ Owner client.Object
+ MutateFn controllerutil.MutateFn
+ SyncType SyncType
+ // runtime use that
+ previousObject runtime.Object
+}
+
+var _ Syncer = &ObjectSyncer{}
+
+// NewObjectSyncer creates a new kubernetes.Object syncer for object
+// will set owner. And it can set SyncType. Mostly we should set CreateOrUpdate
+func NewObjectSyncer(ctx context.Context, client client.Client, owner client.Object, MutateFn controllerutil.MutateFn, obj client.Object, syncType SyncType) Syncer {
+ return &ObjectSyncer{
+ Ctx: ctx,
+ Client: client,
+ Obj: obj,
+ Owner: owner,
+ MutateFn: MutateFn,
+ SyncType: syncType,
+ }
+}
+
+// mutateFn Wrap for controllerutil.MutateFn. Do something before create or update or patch.
+func (s *ObjectSyncer) mutateFn() controllerutil.MutateFn {
+ return func() error {
+ s.previousObject = s.Obj.DeepCopyObject()
+ err := s.MutateFn()
+ if err != nil {
+ return err
+ }
+ if s.Owner == nil {
+ return nil
+ }
+ // set owner reference only if owner resource is not being deleted, otherwise the owner
+ // reference will be reset in case of deleting.
+ if s.Owner.GetDeletionTimestamp().IsZero() {
+ if err := controllerutil.SetControllerReference(s.Owner, s.Obj, s.Client.Scheme()); err != nil {
+ return err
+ }
+ } else if ctime := s.Obj.GetCreationTimestamp(); ctime.IsZero() {
+ // the owner is deleted, don't recreate the resource if does not exist, because gc
+ // will not delete it again because has no owner reference set
+ return ErrOwnerDeleted
+ }
+
+ return nil
+ }
+}
+
+// objectTypeName returns the type of Object's Name
+func (s *ObjectSyncer) objectTypeName(obj runtime.Object) string {
+ if obj != nil {
+ gvk, err := apiutil.GVKForObject(obj, s.Client.Scheme())
+ if err != nil {
+ return fmt.Sprintf("%T", obj)
+ }
+ return gvk.String()
+ }
+ return "nil"
+}
+
+// Sync does the actual syncing and implements the Syncer Sync method.
+func (s *ObjectSyncer) Sync(ctx context.Context) (SyncResult, error) {
+ var err error
+ result := SyncResult{}
+ key := client.ObjectKeyFromObject(s.Obj)
+ switch s.SyncType {
+ case SyncTypeCreateOrUpdate:
+ result.Operation, err = controllerutil.CreateOrUpdate(ctx, s.Client, s.Obj, s.mutateFn())
+ case SyncTypeCreateOrPatch:
+ result.Operation, err = controllerutil.CreateOrPatch(ctx, s.Client, s.Obj, s.mutateFn())
+ case SyncTypeFoundToUpdate:
+ // found first
+ key := client.ObjectKeyFromObject(s.Obj)
+ if err := s.Client.Get(ctx, key, s.Obj); err != nil {
+ if apierrors.IsNotFound(err) {
+ result.Operation = controllerutil.OperationResultNone
+ break
+ }
+ }
+ result.Operation, err = controllerutil.CreateOrUpdate(ctx, s.Client, s.Obj, s.mutateFn())
+ case SyncTypeFoundToPatch:
+ // found first
+ key := client.ObjectKeyFromObject(s.Obj)
+ if err := s.Client.Get(ctx, key, s.Obj); err != nil {
+ if apierrors.IsNotFound(err) {
+ result.Operation = controllerutil.OperationResultNone
+ break
+ }
+ }
+ result.Operation, err = controllerutil.CreateOrPatch(ctx, s.Client, s.Obj, s.mutateFn())
+ }
+
+ // get the diff info
+ diff := deep.Equal(redact(s.previousObject), redact(s.Obj))
+ switch {
+ case errors.Is(err, ErrOwnerDeleted):
+ klog.Infof("%s key %s kind %s error %s", string(result.Operation), key, s.objectTypeName(s.Obj), err)
+ err = nil
+ case errors.Is(err, ErrIgnore):
+ klog.Infof("syncer skipped key %s kind %s error %s", key, s.objectTypeName(s.Obj), err)
+ err = nil
+ case err != nil:
+ result.SetEventData(eventWarning, EventReason(s.Obj, err),
+ fmt.Sprintf("%s %s failed syncing: %s", s.objectTypeName(s.Obj), key, err))
+ klog.Errorf("%s key %s kind %s diff %s", string(result.Operation), key, s.objectTypeName(s.Obj), diff)
+ default:
+ result.SetEventData(eventNormal, EventReason(s.Obj, err),
+ fmt.Sprintf("%s %s %s successfully", s.objectTypeName(s.Obj), key, result.Operation))
+ klog.Infof("%s key %s kind %s diff %s", string(result.Operation), key, s.objectTypeName(s.Obj), diff)
+ }
+
+ return result, err
+}
+
+// ObjectOwner returns the ObjectSyncer owner.
+func (s *ObjectSyncer) ObjectOwner() runtime.Object {
+ return s.Owner
+}
+
+// Masking of sensitive information
+func redact(obj runtime.Object) runtime.Object {
+ switch exposed := obj.(type) {
+ case *corev1.Secret:
+ redacted := exposed.DeepCopy()
+ redacted.Data = nil
+ redacted.StringData = nil
+ exposed.ObjectMeta.DeepCopyInto(&redacted.ObjectMeta)
+ return redacted
+ case *corev1.ConfigMap:
+ redacted := exposed.DeepCopy()
+ redacted.Data = nil
+ return redacted
+ }
+ return obj
+}
diff --git a/pkg/runtime/sync_test.go b/pkg/runtime/sync_test.go
new file mode 100644
index 00000000000..04cdef9c5bb
--- /dev/null
+++ b/pkg/runtime/sync_test.go
@@ -0,0 +1,202 @@
+// Copyright (C) 2023, MinIO, Inc.
+//
+// This code is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License, version 3,
+// as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License, version 3,
+// along with this program. If not, see
+
+package runtime
+
+import (
+ "context"
+ "reflect"
+ "testing"
+
+ v1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/scheme"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+func Test_NewObjectSyncer_CreateOrPatch_Patch(t *testing.T) {
+ // test patch
+ patchReg := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ Annotations: map[string]string{
+ "an1": "vn1",
+ "an2": "vn2",
+ },
+ },
+ Immutable: nil,
+ Data: map[string][]byte{
+ "before": []byte("before"),
+ },
+ }
+ // save the obj
+ cli := fake.NewClientBuilder().WithRuntimeObjects(patchReg).WithScheme(scheme.Scheme).Build()
+ patch := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ },
+ }
+ sync, err := NewObjectSyncer(context.Background(), cli, nil, func() error {
+ if patch.Data == nil {
+ patch.Data = map[string][]byte{}
+ }
+ patch.Data["after"] = []byte("after")
+ if patch.Annotations == nil {
+ patch.Annotations = map[string]string{}
+ }
+ patch.Annotations["an3"] = "vn3"
+ return nil
+ }, patch, SyncTypeCreateOrPatch).Sync(context.Background())
+ if err != nil {
+ return
+ }
+ if sync.Operation != controllerutil.OperationResultUpdated || patch.ResourceVersion == "" {
+ t.Errorf("should make a update call")
+ }
+ if !reflect.DeepEqual(patch.Data, map[string][]byte{
+ "after": []byte("after"),
+ "before": []byte("before"),
+ }) {
+ t.Errorf("patch failed")
+ }
+ if !reflect.DeepEqual(patch.Annotations, map[string]string{
+ "an1": "vn1",
+ "an2": "vn2",
+ "an3": "vn3",
+ }) {
+ t.Errorf("patch failed")
+ }
+ t.Log(sync.EventReason)
+}
+
+func Test_NewObjectSyncer_CreateOrUpdate_Update(t *testing.T) {
+ // test update
+ updateReg := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ Annotations: map[string]string{
+ "an1": "vn1",
+ "an2": "vn2",
+ },
+ },
+ Data: map[string][]byte{
+ "before": []byte("before"),
+ },
+ }
+ // save the obj
+ cli := fake.NewClientBuilder().WithRuntimeObjects(updateReg).WithScheme(scheme.Scheme).Build()
+ update := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ },
+ }
+ sync, err := NewObjectSyncer(context.Background(), cli, nil, func() error {
+ if update.Data == nil {
+ update.Data = map[string][]byte{}
+ }
+ update.Data["after"] = []byte("after")
+ if update.Annotations == nil {
+ update.Annotations = map[string]string{}
+ }
+ update.Annotations["an3"] = "vn3"
+ return nil
+ }, update, SyncTypeCreateOrUpdate).Sync(context.Background())
+ if err != nil {
+ return
+ }
+ if sync.Operation != controllerutil.OperationResultUpdated || update.ResourceVersion == "" {
+ t.Errorf("should make a update call")
+ }
+ if !reflect.DeepEqual(update.Data, map[string][]byte{
+ "after": []byte("after"),
+ "before": []byte("before"),
+ }) {
+ t.Errorf("update failed")
+ }
+ if !reflect.DeepEqual(update.Annotations, map[string]string{
+ "an1": "vn1",
+ "an2": "vn2",
+ "an3": "vn3",
+ }) {
+ t.Errorf("update failed")
+ }
+ t.Log(sync.EventReason)
+}
+
+func Test_NewObjectSyncer_CreateOrUpdate_Create(t *testing.T) {
+ // test create
+ create := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ },
+ }
+ data := map[string][]byte{
+ "create": []byte("create_data"),
+ }
+ cli := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()
+ sync, err := NewObjectSyncer(context.Background(), cli, nil, func() error {
+ create.Data = data
+ return nil
+ }, create, SyncTypeCreateOrUpdate).Sync(context.Background())
+ if err != nil {
+ return
+ }
+ if sync.Operation != controllerutil.OperationResultCreated || create.ResourceVersion == "" {
+ t.Errorf("should make a create call")
+ }
+ if !reflect.DeepEqual(create.Data, data) {
+ t.Errorf("create failed")
+ }
+ t.Log(sync.EventReason)
+}
+
+type wrapK8sClientCanceledTest struct {
+ client.Client
+}
+
+func (w *wrapK8sClientCanceledTest) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
+ return context.Canceled
+}
+
+func Test_NewObjectSyncer_CreateOrUpdate_CreateCanceled(t *testing.T) {
+ // test create
+ create := &v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-secret",
+ Namespace: "test-namespace",
+ },
+ }
+ data := map[string][]byte{
+ "create": []byte("create_data"),
+ }
+ cli := &wrapK8sClientCanceledTest{fake.NewClientBuilder().WithScheme(scheme.Scheme).Build()}
+ sync, err := NewObjectSyncer(context.Background(), cli, nil, func() error {
+ create.Data = data
+ return nil
+ }, create, SyncTypeCreateOrUpdate).Sync(context.Background())
+ if err == nil {
+ return
+ }
+ if sync.Operation != controllerutil.OperationResultNone || create.ResourceVersion != "" {
+ t.Errorf("shouldn't make a create call")
+ }
+ t.Log(sync.EventReason)
+}