diff --git a/Dockerfile b/Dockerfile index 3eb1d4b..b0d75fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,6 +14,7 @@ RUN go mod download # Copy the go source COPY cmd/ cmd/ COPY internal/ internal/ +COPY pkg/ pkg/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go index 9cef006..9b3c6af 100644 --- a/cmd/controller/manager.go +++ b/cmd/controller/manager.go @@ -16,7 +16,9 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + cliflag "k8s.io/component-base/cli/flag" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/certwatcher" "sigs.k8s.io/controller-runtime/pkg/healthz" @@ -29,8 +31,12 @@ import ( // +kubebuilder:scaffold:imports "go.datum.net/datum/internal/config" resourcemanagercontroller "go.datum.net/datum/internal/controller/resourcemanager" + "go.datum.net/datum/pkg/features" iamv1alpha1 "go.miloapis.com/milo/pkg/apis/iam/v1alpha1" resourcemanagerv1alpha1 "go.miloapis.com/milo/pkg/apis/resourcemanager/v1alpha1" + + // Register Datum feature gates via init(). + _ "go.datum.net/datum/pkg/features" ) var ( @@ -134,6 +140,12 @@ func NewControllerManagerCommand() *cobra.Command { cmd.Flags().AddGoFlagSet(flag.CommandLine) opts.BindFlags(flag.CommandLine) + namedFlagSets := cliflag.NamedFlagSets{} + utilfeature.DefaultMutableFeatureGate.AddFlag(namedFlagSets.FlagSet("feature gates")) + for _, fs := range namedFlagSets.FlagSets { + cmd.Flags().AddFlagSet(fs) + } + return cmd } @@ -286,16 +298,6 @@ func runControllerManager( return err } - if err = (&resourcemanagercontroller.PersonalOrganizationController{ - Client: mgr.GetClient(), - Config: serverConfig.PersonalOrganizationController, - Scheme: mgr.GetScheme(), - RestConfig: mgr.GetConfig(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "PersonalOrganization") - return err - } - // +kubebuilder:scaffold:builder if metricsCertWatcher != nil { @@ -323,6 +325,20 @@ func runControllerManager( return err } + if !utilfeature.DefaultFeatureGate.Enabled(features.UnifiedOrganizations) { + if err = (&resourcemanagercontroller.PersonalOrganizationController{ + Client: mgr.GetClient(), + Config: serverConfig.PersonalOrganizationController, + Scheme: mgr.GetScheme(), + RestConfig: mgr.GetConfig(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PersonalOrganization") + return err + } + } else { + setupLog.Info("PersonalOrganization controller disabled by UnifiedOrganizations feature gate") + } + setupLog.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { setupLog.Error(err, "problem running manager") diff --git a/config/overlays/unified-organizations/README.md b/config/overlays/unified-organizations/README.md new file mode 100644 index 0000000..4aabaaa --- /dev/null +++ b/config/overlays/unified-organizations/README.md @@ -0,0 +1,21 @@ +# Unified organizations overlay + +Apply this kustomize component when `UnifiedOrganizations=true` on milo and datum +controller-managers. It replaces legacy personal/standard project quota grant +policies with a single 10-project policy for all organizations and removes the +personal organization display-name validation policy. + +The default datum service configuration keeps legacy behavior so releases do not +require coordinated cutover across environments. Only environments that opt in +(staging first) should apply this overlay together with the feature gate. + +## Usage + +Include as a component in your environment kustomization: + +```yaml +components: + - ../path/to/datum/config/overlays/unified-organizations +``` + +Ensure milo and datum controller-managers run with `UnifiedOrganizations=true`. diff --git a/config/overlays/unified-organizations/grant-policies/organization-project-quota-policy.yaml b/config/overlays/unified-organizations/grant-policies/organization-project-quota-policy.yaml new file mode 100644 index 0000000..829a609 --- /dev/null +++ b/config/overlays/unified-organizations/grant-policies/organization-project-quota-policy.yaml @@ -0,0 +1,31 @@ +apiVersion: quota.miloapis.com/v1alpha1 +kind: GrantCreationPolicy +metadata: + name: organization-project-quota-policy + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/component: quota-system +spec: + trigger: + resource: + apiVersion: resourcemanager.miloapis.com/v1alpha1 + kind: Organization + target: + resourceGrantTemplate: + metadata: + name: "default-project-quota" + namespace: "organization-{{ trigger.metadata.name }}" + labels: + quota.miloapis.com/auto-created: "true" + quota.miloapis.com/policy: "organization-project-quota-policy" + annotations: + kubernetes.io/description: "Project quota allocation for organization" + spec: + consumerRef: + apiGroup: resourcemanager.miloapis.com + kind: Organization + name: "{{ trigger.metadata.name }}" + allowances: + - resourceType: resourcemanager.miloapis.com/projects + buckets: + - amount: 10 diff --git a/config/overlays/unified-organizations/kustomization.yaml b/config/overlays/unified-organizations/kustomization.yaml new file mode 100644 index 0000000..e38db35 --- /dev/null +++ b/config/overlays/unified-organizations/kustomization.yaml @@ -0,0 +1,23 @@ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +resources: + - grant-policies/organization-project-quota-policy.yaml + +patches: + - path: remove-personal-grant-policy.yaml + target: + kind: GrantCreationPolicy + name: personal-organization-project-quota-policy + - path: remove-standard-grant-policy.yaml + target: + kind: GrantCreationPolicy + name: standard-organization-project-quota-policy + - path: remove-personal-org-name-vap.yaml + target: + kind: ValidatingAdmissionPolicy + name: disallow-personal-org-name-change + - path: remove-personal-org-name-vap-binding.yaml + target: + kind: ValidatingAdmissionPolicyBinding + name: disallow-personal-org-name-change diff --git a/config/overlays/unified-organizations/remove-personal-grant-policy.yaml b/config/overlays/unified-organizations/remove-personal-grant-policy.yaml new file mode 100644 index 0000000..ca2d10c --- /dev/null +++ b/config/overlays/unified-organizations/remove-personal-grant-policy.yaml @@ -0,0 +1,5 @@ +$patch: delete +apiVersion: quota.miloapis.com/v1alpha1 +kind: GrantCreationPolicy +metadata: + name: personal-organization-project-quota-policy diff --git a/config/overlays/unified-organizations/remove-personal-org-name-vap-binding.yaml b/config/overlays/unified-organizations/remove-personal-org-name-vap-binding.yaml new file mode 100644 index 0000000..2b401c1 --- /dev/null +++ b/config/overlays/unified-organizations/remove-personal-org-name-vap-binding.yaml @@ -0,0 +1,5 @@ +$patch: delete +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: disallow-personal-org-name-change diff --git a/config/overlays/unified-organizations/remove-personal-org-name-vap.yaml b/config/overlays/unified-organizations/remove-personal-org-name-vap.yaml new file mode 100644 index 0000000..6dce8e9 --- /dev/null +++ b/config/overlays/unified-organizations/remove-personal-org-name-vap.yaml @@ -0,0 +1,5 @@ +$patch: delete +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: disallow-personal-org-name-change diff --git a/config/overlays/unified-organizations/remove-standard-grant-policy.yaml b/config/overlays/unified-organizations/remove-standard-grant-policy.yaml new file mode 100644 index 0000000..f76b507 --- /dev/null +++ b/config/overlays/unified-organizations/remove-standard-grant-policy.yaml @@ -0,0 +1,5 @@ +$patch: delete +apiVersion: quota.miloapis.com/v1alpha1 +kind: GrantCreationPolicy +metadata: + name: standard-organization-project-quota-policy diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index b65963f..45aa3ec 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -18,3 +18,14 @@ rules: - organizations verbs: - create +- apiGroups: + - resourcemanager.datumapis.com + resources: + - projects + verbs: + - create + - get + - list + - patch + - update + - watch diff --git a/config/services/dns.networking.miloapis.com/quota/grant-policies/personal-org-grant-policy.yaml b/config/services/dns.networking.miloapis.com/quota/grant-policies/personal-org-grant-policy.yaml index 647eafc..a33aea9 100644 --- a/config/services/dns.networking.miloapis.com/quota/grant-policies/personal-org-grant-policy.yaml +++ b/config/services/dns.networking.miloapis.com/quota/grant-policies/personal-org-grant-policy.yaml @@ -20,7 +20,7 @@ spec: name: "default-dns-quota-{{ trigger.metadata.name }}" namespace: milo-system annotations: - kubernetes.io/description: "DNS quota allocation for Personal org project" + kubernetes.io/description: "DNS quota allocation for organization project" spec: consumerRef: apiGroup: resourcemanager.miloapis.com diff --git a/config/services/resourcemanager.miloapis.com/README.md b/config/services/resourcemanager.miloapis.com/README.md index d36b297..18bc81c 100644 --- a/config/services/resourcemanager.miloapis.com/README.md +++ b/config/services/resourcemanager.miloapis.com/README.md @@ -21,16 +21,21 @@ customize it for Datum Cloud. ## Quota Limits +The default configuration ships legacy personal/standard quota grant policies: + - **Personal Organizations**: 2 project maximum - **Standard Organizations**: 10 projects maximum +When enabling unified organizations, apply `config/overlays/unified-organizations` +with `UnifiedOrganizations=true` on milo and datum controller-managers for a +single 10-project quota policy on all organizations. + ## Deployment ```bash -# Deploy entire service +# Deploy entire service (legacy quota policies by default) kubectl apply -k config/services/resourcemanager.miloapis.com -# Deploy specific components -kubectl apply -k config/services/resourcemanager.miloapis.com/quota -kubectl apply -k config/services/resourcemanager.miloapis.com/validation +# Unified environments: also apply unified overlay and enable feature gate +kubectl apply -k config/overlays/unified-organizations ``` diff --git a/go.mod b/go.mod index 299236b..fe0e7f8 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,9 @@ require ( go.miloapis.com/milo v0.25.1 k8s.io/api v0.32.3 k8s.io/apimachinery v0.33.2 + k8s.io/apiserver v0.32.3 k8s.io/client-go v0.32.3 + k8s.io/component-base v0.32.3 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.20.4 ) @@ -95,8 +97,6 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.32.3 // indirect - k8s.io/apiserver v0.32.3 // indirect - k8s.io/component-base v0.32.3 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect diff --git a/go.sum b/go.sum index 72e1167..4c2db49 100644 --- a/go.sum +++ b/go.sum @@ -31,6 +31,12 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -46,6 +52,8 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -62,8 +70,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -74,6 +80,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -88,6 +96,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -95,12 +107,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= -github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -119,13 +127,8 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= @@ -139,16 +142,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.miloapis.com/milo v0.8.0 h1:3tjZpJ2Xn1MPnAHAuoPZHnffdewvBBNctJLdgM33+GY= -go.miloapis.com/milo v0.8.0/go.mod h1:rmI6r0kL/pRaAkSlDbOVj/LQB3QgEAmDG5dwA1i7rP4= -go.miloapis.com/milo v0.18.0 h1:+6HRLlRO2genWFnIw9803A1er9/vmRfP1EOC3ZlEF/U= -go.miloapis.com/milo v0.18.0/go.mod h1:xOFYvUsvSZV3z6eow5YdB5C/qRQf2s/5/arcfJs5XPg= -go.miloapis.com/milo v0.24.3 h1:bPldWO5Tldj4D2LMJnCP26yLMg8Vn6dgnQMcYHceQ/s= -go.miloapis.com/milo v0.24.3/go.mod h1:xOFYvUsvSZV3z6eow5YdB5C/qRQf2s/5/arcfJs5XPg= go.miloapis.com/milo v0.25.1 h1:8FLOvS3BcNN+KO755NxKrLK/Bu9cceHsg5J27XK7XP4= go.miloapis.com/milo v0.25.1/go.mod h1:xOFYvUsvSZV3z6eow5YdB5C/qRQf2s/5/arcfJs5XPg= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -179,8 +184,6 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= -go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= -go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -196,8 +199,6 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= @@ -205,25 +206,17 @@ golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= @@ -232,8 +225,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -248,8 +239,6 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/config.go b/internal/config/config.go index 1d474f0..2fa1c8b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,7 +29,7 @@ type DatumControllerManager struct { MetricsServer MetricsServerConfig `json:"metricsServer"` // PersonalOrganizationController is the configuration for the personal - // organization controller. + // organization controller. Only active when UnifiedOrganizations is disabled. PersonalOrganizationController resourcemanagercontroller.PersonalOrganizationControllerConfig `json:"personalOrganizationController"` } diff --git a/internal/controller/resourcemanager/personal_organization_controller_test.go b/internal/controller/resourcemanager/personal_organization_controller_test.go index 156023a..06395f6 100644 --- a/internal/controller/resourcemanager/personal_organization_controller_test.go +++ b/internal/controller/resourcemanager/personal_organization_controller_test.go @@ -2,17 +2,18 @@ package resourcemanager -import ( - . "github.com/onsi/ginkgo/v2" -) +import "testing" -var _ = Describe("User Controller", func() { - Context("When reconciling a resource", func() { - - It("should successfully reconcile the resource", func() { - - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) - }) -}) +func TestHashPersonalOrgName(t *testing.T) { + first := hashPersonalOrgName("uid-123") + second := hashPersonalOrgName("uid-123") + if first != second { + t.Fatalf("hashPersonalOrgName() not stable: %q vs %q", first, second) + } + if first == "" { + t.Fatal("hashPersonalOrgName() returned empty string") + } + if hashPersonalOrgName("uid-456") == first { + t.Fatal("hashPersonalOrgName() returned same value for different inputs") + } +} diff --git a/internal/controller/resourcemanager/suite_test.go b/internal/controller/resourcemanager/suite_test.go deleted file mode 100644 index 8d90568..0000000 --- a/internal/controller/resourcemanager/suite_test.go +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only - -package resourcemanager - -import ( - "context" - "os" - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - iamdatumapiscomv1alpha1 "go.miloapis.com/milo/pkg/apis/iam/v1alpha1" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - // +kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - ctx context.Context - cancel context.CancelFunc - testEnv *envtest.Environment - cfg *rest.Config - k8sClient client.Client -) - -func TestControllers(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - ctx, cancel = context.WithCancel(context.TODO()) - - var err error - err = iamdatumapiscomv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:scheme - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: false, - } - - // Retrieve the first found binary directory to allow running tests from IDEs - if getFirstFoundEnvTestBinaryDir() != "" { - testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() - } - - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - cancel() - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) - -// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. -// ENVTEST-based tests depend on specific binaries, usually located in paths set by -// controller-runtime. When running tests directly (e.g., via an IDE) without using -// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. -// -// This function streamlines the process by finding the required binaries, similar to -// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are -// properly set up, run 'make setup-envtest' beforehand. -func getFirstFoundEnvTestBinaryDir() string { - basePath := filepath.Join("..", "..", "bin", "k8s") - entries, err := os.ReadDir(basePath) - if err != nil { - logf.Log.Error(err, "Failed to read directory", "path", basePath) - return "" - } - for _, entry := range entries { - if entry.IsDir() { - return filepath.Join(basePath, entry.Name()) - } - } - return "" -} diff --git a/pkg/features/features.go b/pkg/features/features.go new file mode 100644 index 0000000..3e9500f --- /dev/null +++ b/pkg/features/features.go @@ -0,0 +1,30 @@ +// Package features defines feature gates for the Datum controller manager. +package features + +import ( + "k8s.io/apimachinery/pkg/util/runtime" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/component-base/featuregate" +) + +const ( + // UnifiedOrganizations enables unified organization behavior in Datum. + // When enabled, the PersonalOrganizationController is disabled and infra + // should apply unified quota grant policies instead of legacy personal/standard + // policies. + // + // owner: @datum-cloud/platform + // alpha: v0.1.0 + UnifiedOrganizations featuregate.Feature = "UnifiedOrganizations" +) + +func init() { + runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultFeatureGates)) +} + +var defaultFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + UnifiedOrganizations: { + Default: false, + PreRelease: featuregate.Alpha, + }, +} diff --git a/pkg/features/features_test.go b/pkg/features/features_test.go new file mode 100644 index 0000000..67051da --- /dev/null +++ b/pkg/features/features_test.go @@ -0,0 +1,17 @@ +package features_test + +import ( + "testing" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + + "go.datum.net/datum/pkg/features" + + _ "go.datum.net/datum/pkg/features" +) + +func TestUnifiedOrganizationsDefaultDisabled(t *testing.T) { + if utilfeature.DefaultFeatureGate.Enabled(features.UnifiedOrganizations) { + t.Fatal("UnifiedOrganizations should default to false") + } +}