From 54e9644703708c01e4b1a3dbd49d82b7e26f5176 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 08:08:35 -0500 Subject: [PATCH 01/35] chore: remove old foundation This foundation as moved to `datum-cloud/milo` now to serve as the control plane foundation for service providers. This repo will be updated with components that extend the Milo platform to add Datum Cloud specific capabilities and functionality. --- .devcontainer/devcontainer.json | 44 - .github/ISSUE_TEMPLATE/bug_report.yaml | 74 -- .../ISSUE_TEMPLATE/usability_feedback.yaml | 57 -- .github/workflows/build.yaml | 69 -- .github/workflows/publish.yaml | 25 - .github/workflows/test.yaml | 37 - cmd/datum-apiserver/Dockerfile | 26 - cmd/datum-apiserver/app/admission.go | 27 - cmd/datum-apiserver/app/config.go | 240 ------ cmd/datum-apiserver/app/server.go | 220 ----- cmd/datum-apiserver/app/serviceaccounts.go | 51 -- cmd/datum-apiserver/main.go | 16 - cmd/datum-authorization-webhook/Dockerfile | 21 - .../iam/core_control_plane_authorizer.go | 94 --- .../iam/project_control_plane_authorizer.go | 84 -- .../app/internal/webhook/http.go | 147 ---- .../app/internal/webhook/response.go | 31 - .../app/internal/webhook/webhook.go | 97 --- .../app/internal/webhook/webhook_test.go | 123 --- cmd/datum-authorization-webhook/app/serve.go | 135 ---- .../app/webhook.go | 33 - cmd/datum-authorization-webhook/webhook.go | 15 - cmd/datum-controller-manager/Dockerfile | 25 - .../app/controllermanager.go | 765 ------------------ cmd/datum-controller-manager/app/core.go | 155 ---- cmd/datum-controller-manager/main.go | 15 - config/apiserver/deployment.yaml | 68 -- config/apiserver/httpproxy.yaml | 20 - config/apiserver/kustomization.yaml | 6 - config/apiserver/service.yaml | 15 - config/controller-manager/deployment.yaml | 52 -- config/controller-manager/kustomization.yaml | 4 - docs/README.md | 75 -- go.mod | 140 ---- go.sum | 414 ---------- pkg/cmd/logging.go | 58 -- pkg/server/filters/organizations.go | 181 ----- pkg/server/filters/organizations_test.go | 121 --- 38 files changed, 3780 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yaml delete mode 100644 .github/ISSUE_TEMPLATE/usability_feedback.yaml delete mode 100644 .github/workflows/build.yaml delete mode 100644 .github/workflows/publish.yaml delete mode 100644 .github/workflows/test.yaml delete mode 100644 cmd/datum-apiserver/Dockerfile delete mode 100644 cmd/datum-apiserver/app/admission.go delete mode 100644 cmd/datum-apiserver/app/config.go delete mode 100644 cmd/datum-apiserver/app/server.go delete mode 100644 cmd/datum-apiserver/app/serviceaccounts.go delete mode 100644 cmd/datum-apiserver/main.go delete mode 100644 cmd/datum-authorization-webhook/Dockerfile delete mode 100644 cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go delete mode 100644 cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go delete mode 100644 cmd/datum-authorization-webhook/app/internal/webhook/http.go delete mode 100644 cmd/datum-authorization-webhook/app/internal/webhook/response.go delete mode 100644 cmd/datum-authorization-webhook/app/internal/webhook/webhook.go delete mode 100644 cmd/datum-authorization-webhook/app/internal/webhook/webhook_test.go delete mode 100644 cmd/datum-authorization-webhook/app/serve.go delete mode 100644 cmd/datum-authorization-webhook/app/webhook.go delete mode 100644 cmd/datum-authorization-webhook/webhook.go delete mode 100644 cmd/datum-controller-manager/Dockerfile delete mode 100644 cmd/datum-controller-manager/app/controllermanager.go delete mode 100644 cmd/datum-controller-manager/app/core.go delete mode 100644 cmd/datum-controller-manager/main.go delete mode 100644 config/apiserver/deployment.yaml delete mode 100644 config/apiserver/httpproxy.yaml delete mode 100644 config/apiserver/kustomization.yaml delete mode 100644 config/apiserver/service.yaml delete mode 100644 config/controller-manager/deployment.yaml delete mode 100644 config/controller-manager/kustomization.yaml delete mode 100644 docs/README.md delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 pkg/cmd/logging.go delete mode 100644 pkg/server/filters/organizations.go delete mode 100644 pkg/server/filters/organizations_test.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 16eb98f..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,44 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/go -{ - "name": "Go", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/go:1-1.23-bookworm", - "features": { - "ghcr.io/devcontainers/features/common-utils": { - "installOhMyZsh": true, - "configureZshAsDefaultShell": true, - "installOhMyZshConfig": true, - "installZsh": true, - "upgradePackages": true - }, - "ghcr.io/devcontainers/features/docker-in-docker": {}, - "ghcr.io/dhoeric/features/act": {}, - }, - "customizations": { - "vscode": { - "extensions": [ - "patbenatar.advanced-new-file", - "stkb.rewrap", - "github.vscode-github-actions", - "yzhang.markdown-all-in-one" - ], - "settings": { - "rewrap.autoWrap.enabled": true - }, - "editor.tabSize": 2 - } - }, - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "go version", - - // Configure tool-specific properties. - // "customizations": {}, -} diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml deleted file mode 100644 index fd91c0c..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: Bug Report -description: Provide supporting details for a bug in the Datum Cloud Platform. -labels: -- bug -type: Bug -body: - - type: textarea - attributes: - label: "Overview" - description: "Provide a concise summary of the bug." - placeholder: "Include a short explanation of the gap or need." - validations: - required: true - - type: textarea - attributes: - label: "Steps to Reproduce" - description: "Please provide detailed steps to reproduce the bug. Number each step." - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - - type: textarea - attributes: - label: "Actual Behavior" - description: "What actually happened when you followed the steps?" - placeholder: "Describe the problem you encountered." - validations: - required: true - - type: textarea - attributes: - label: "Expected Behavior" - description: "What did you expect to happen?" - placeholder: "Describe what you thought should occur." - validations: - required: true - - type: textarea - attributes: - label: "Environment Details" - description: "Please provide details about the environment where you experienced the bug." - placeholder: | - - Operating System (e.g., Windows 10, macOS Sonoma 14.2.1): - - Browser (e.g., Chrome 120.0.6099.129, Safari 17.2.1): - - Device (e.g., Laptop, iPhone 15 Pro): - - Any relevant versions (e.g., App version, specific library versions): - validations: - required: false - - type: dropdown - attributes: - label: "Impact / Severity" - description: "How significantly is this bug impacting your ability to use the platform?" - options: - - "Critical: Prevents me from using essential features or a complete outage." - - "High: Significantly impairs my ability to use important features." - - "Medium: Causes inconvenience but I can still use the platform with workarounds." - - "Low: Minor issue or cosmetic problem with minimal impact." - validations: - required: false - - type: textarea - attributes: - label: "Additional Context" - description: "Add any links, references, or context you think is relevant. This could include error messages, logs, or specific data you were using." - placeholder: "Provide URLs, doc references, error messages, or any other useful details." - validations: - required: false - - type: textarea - attributes: - label: "Attachments / Screenshots" - description: "If applicable, please provide screenshots, logs, or any other files that could help us understand the issue. You can drag and drop files here or paste links." - placeholder: "Drag and drop files or paste links to attachments." - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/usability_feedback.yaml b/.github/ISSUE_TEMPLATE/usability_feedback.yaml deleted file mode 100644 index 9510cb4..0000000 --- a/.github/ISSUE_TEMPLATE/usability_feedback.yaml +++ /dev/null @@ -1,57 +0,0 @@ -name: Usability Feedback -description: Report a usability issue or provide feedback on the user experience. -labels: - - usability - - ux - - feedback -type: "Feedback" -body: - - type: textarea - attributes: - label: "What task were you trying to complete?" - description: "Please describe the goal you were trying to achieve when you encountered the usability issue." - placeholder: "e.g., I was trying to invite a new member to my team." - validations: - required: true - - type: textarea - attributes: - label: "Describe the usability issue" - description: "Please detail the difficulty, confusion, or frustration you experienced. What happened, and where in the application did it occur?" - placeholder: "e.g., I couldn't find the option to change my notification settings on the profile page." - validations: - required: true - - type: textarea - attributes: - label: "Why was this confusing or difficult for you?" - description: "Help us understand your thought process. What made this part of the experience challenging from your perspective?" - placeholder: "e.g., The labels were unclear, or I expected the setting to be in a different menu." - validations: - required: true - - type: textarea - attributes: - label: "What did you expect to happen instead? (Optional)" - description: "If you had a different expectation for how this should function or look, please describe it." - placeholder: "e.g., I expected a clear button labeled 'Notification Settings'." - validations: - required: false - - type: textarea - attributes: - label: "Do you have any suggestions for improvement? (Optional)" - description: "If you have ideas on how we could make this better, we'd love to hear them." - placeholder: "e.g., Perhaps group all settings under a single 'Settings' menu." - validations: - required: false - - type: input - attributes: - label: "Relevant URL or Section of the Application (Optional)" - description: "If applicable, please provide the URL or describe the specific part of the application." - placeholder: "e.g., /app/settings/notifications or 'the user profile dropdown'" - validations: - required: false - - type: textarea - attributes: - label: "Attachments / Screenshots (Optional)" - description: "You can drag and drop images or paste links to screenshots/recordings that illustrate the issue." - placeholder: "Drag and drop or paste links here." - validations: - required: false diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 45c4848..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: Build and Publish Docker Image - -on: - push: - branches: - - main - pull_request: - release: - types: ["published"] - -jobs: - build-and-push: - permissions: - contents: read - packages: write - attestations: write - id-token: write - - runs-on: ubuntu-latest - - # Define the services that should be built. - strategy: - matrix: - service: - - datum-authorization-webhook - - datum-apiserver - - datum-controller-manager - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3.3.0 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5.7.0 - with: - images: ghcr.io/datum-cloud/${{ matrix.service }} - tags: | - type=ref,event=pr - type=ref,event=branch - type=ref,event=branch,suffix=-{{commit_date 'YYYYMMDD-HHmmss'}} - type=semver,pattern=v{{version}} - type=semver,pattern=v{{major}}.{{minor}} - type=semver,pattern=v{{major}} - type=sha - - - name: Build ${{ matrix.service }} - id: push - uses: docker/build-push-action@v6.15.0 - with: - context: . - file: cmd/${{ matrix.service }}/Dockerfile - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - - - name: Generate artifact attestation - uses: actions/attest-build-provenance@v1 - with: - subject-name: ghcr.io/datum-cloud/${{ matrix.service }} - subject-digest: ${{ steps.push.outputs.digest }} - push-to-registry: true diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml deleted file mode 100644 index 0b3db76..0000000 --- a/.github/workflows/publish.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: Publish Artifacts - -on: - push: - release: - types: ['published'] - -jobs: - publish-kustomize-bundles: - permissions: - id-token: write - contents: read - packages: write - strategy: - matrix: - bundles: - - name: ghcr.io/datum-cloud/apiserver - path: config/apiserver - - name: ghcr.io/datum-cloud/controller-manager - path: config/controller-manager - uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.2.0 - with: - bundle-name: ${{ matrix.bundles.name }} - bundle-path: ${{ matrix.bundles.path }} - secrets: inherit \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml deleted file mode 100644 index 8483ab7..0000000 --- a/.github/workflows/test.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: Go Tests - -on: - push: - branches: - - main - pull_request: - -jobs: - test: - name: Run Go Tests - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '1.23' - - - name: Cache Go modules - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - - - name: Install dependencies - run: go mod tidy - - - name: Run Tests - run: go test -v ./... diff --git a/cmd/datum-apiserver/Dockerfile b/cmd/datum-apiserver/Dockerfile deleted file mode 100644 index 69daa32..0000000 --- a/cmd/datum-apiserver/Dockerfile +++ /dev/null @@ -1,26 +0,0 @@ -FROM golang:1.23 AS build-stage - -WORKDIR /workspace - -COPY go.mod go.mod -COPY go.sum go.sum -RUN go mod download - -COPY cmd/datum-apiserver/ cmd/datum-apiserver/ -COPY pkg pkg/ - -RUN --mount=type=cache,target=/go/pkg/mod/ \ - --mount=type=cache,target="/root/.cache/go-build" \ - CGO_ENABLED=0 GOOS=linux \ - go build \ - -ldflags="-X 'k8s.io/component-base/version/verflag.programName=Datum'" \ - -o /datum-apiserver cmd/datum-apiserver/main.go - -FROM gcr.io/distroless/base-debian11 -ARG cmd - -WORKDIR / - -COPY --from=build-stage /datum-apiserver /datum-apiserver - -ENTRYPOINT ["/datum-apiserver"] diff --git a/cmd/datum-apiserver/app/admission.go b/cmd/datum-apiserver/app/admission.go deleted file mode 100644 index 25524c6..0000000 --- a/cmd/datum-apiserver/app/admission.go +++ /dev/null @@ -1,27 +0,0 @@ -package app - -import ( - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle" - mutatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating" - validatingwebhook "k8s.io/apiserver/pkg/admission/plugin/webhook/validating" - "k8s.io/kubernetes/pkg/kubeapiserver/options" -) - -// DefaultOffAdmissionPlugins get admission plugins off by default for kube-apiserver. -func DefaultOffAdmissionPlugins() sets.Set[string] { - defaultOnPlugins := sets.New[string]( - lifecycle.PluginName, // NamespaceLifecycle - // defaulttolerationseconds.PluginName, // DefaultTolerationSeconds - mutatingwebhook.PluginName, // MutatingAdmissionWebhook - validatingwebhook.PluginName, // ValidatingAdmissionWebhook - // resourcequota.PluginName, // ResourceQuota - // certapproval.PluginName, // CertificateApproval - // certsigning.PluginName, // CertificateSigning - // ctbattest.PluginName, // ClusterTrustBundleAttest - // certsubjectrestriction.PluginName, // CertificateSubjectRestriction - // validatingadmissionpolicy.PluginName, // ValidatingAdmissionPolicy, only active when feature gate ValidatingAdmissionPolicy is enabled - ) - - return sets.New(options.AllOrderedPlugins...).Difference(defaultOnPlugins) -} diff --git a/cmd/datum-apiserver/app/config.go b/cmd/datum-apiserver/app/config.go deleted file mode 100644 index f4bc9b0..0000000 --- a/cmd/datum-apiserver/app/config.go +++ /dev/null @@ -1,240 +0,0 @@ -package app - -import ( - "net/http" - - corev1 "k8s.io/api/core/v1" - apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/endpoints/filterlatency" - genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" - genericfeatures "k8s.io/apiserver/pkg/features" - "k8s.io/apiserver/pkg/server" - genericfilters "k8s.io/apiserver/pkg/server/filters" - "k8s.io/apiserver/pkg/server/routine" - flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request" - "k8s.io/apiserver/pkg/util/webhook" - "k8s.io/client-go/discovery" - utilversion "k8s.io/component-base/version" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" - aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" - "k8s.io/kubernetes/pkg/api/legacyscheme" - "k8s.io/kubernetes/pkg/controlplane" - controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver" - "k8s.io/kubernetes/pkg/controlplane/apiserver/options" - generatedopenapi "k8s.io/kubernetes/pkg/generated/openapi" - admissionregistrationrest "k8s.io/kubernetes/pkg/registry/admissionregistration/rest" - apiserverinternalrest "k8s.io/kubernetes/pkg/registry/apiserverinternal/rest" - authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest" - authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest" - coordinationrest "k8s.io/kubernetes/pkg/registry/coordination/rest" - discoveryrest "k8s.io/kubernetes/pkg/registry/discovery/rest" - eventsrest "k8s.io/kubernetes/pkg/registry/events/rest" - flowcontrolrest "k8s.io/kubernetes/pkg/registry/flowcontrol/rest" - rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest" - svmrest "k8s.io/kubernetes/pkg/registry/storagemigration/rest" - - datumfilters "go.datumapis.com/datum/pkg/server/filters" -) - -type Config struct { - Options options.CompletedOptions - - Aggregator *aggregatorapiserver.Config - ControlPlane *controlplaneapiserver.Config - APIExtensions *apiextensionsapiserver.Config - - ExtraConfig -} - -type ExtraConfig struct { -} - -type completedConfig struct { - Options options.CompletedOptions - - Aggregator aggregatorapiserver.CompletedConfig - ControlPlane controlplaneapiserver.CompletedConfig - APIExtensions apiextensionsapiserver.CompletedConfig - - ExtraConfig -} - -type CompletedConfig struct { - // Embed a private pointer that cannot be instantiated outside of this package. - *completedConfig -} - -func (c *CompletedConfig) GenericStorageProviders(discovery discovery.DiscoveryInterface) ([]controlplaneapiserver.RESTStorageProvider, error) { - return []controlplaneapiserver.RESTStorageProvider{ - c.ControlPlane.NewCoreGenericConfig(), - apiserverinternalrest.StorageProvider{}, - authenticationrest.RESTStorageProvider{Authenticator: c.ControlPlane.Generic.Authentication.Authenticator, APIAudiences: c.ControlPlane.Generic.Authentication.APIAudiences}, - authorizationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, RuleResolver: c.ControlPlane.Generic.RuleResolver}, - coordinationrest.RESTStorageProvider{}, - rbacrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer}, - svmrest.RESTStorageProvider{}, - flowcontrolrest.RESTStorageProvider{InformerFactory: c.ControlPlane.Generic.SharedInformerFactory}, - admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: discovery}, - eventsrest.RESTStorageProvider{TTL: c.ControlPlane.EventTTL}, - discoveryrest.StorageProvider{}, - }, nil -} - -func (c *Config) Complete() (CompletedConfig, error) { - return CompletedConfig{&completedConfig{ - Options: c.Options, - - Aggregator: c.Aggregator.Complete(), - ControlPlane: c.ControlPlane.Complete(), - APIExtensions: c.APIExtensions.Complete(), - - ExtraConfig: c.ExtraConfig, - }}, nil -} - -func NewConfig(opts options.CompletedOptions) (*Config, error) { - c := &Config{ - Options: opts, - } - - apiResourceConfigSource := controlplane.DefaultAPIResourceConfigSource() - apiResourceConfigSource.DisableResources(corev1.SchemeGroupVersion.WithResource("serviceaccounts")) - - genericConfig, versionedInformers, storageFactory, err := controlplaneapiserver.BuildGenericConfig( - opts, - []*runtime.Scheme{legacyscheme.Scheme, apiextensionsapiserver.Scheme, aggregatorscheme.Scheme}, - apiResourceConfigSource, - generatedopenapi.GetOpenAPIDefinitions, - ) - if err != nil { - return nil, err - } - - genericConfig.BuildHandlerChainFunc = DefaultBuildHandlerChain - - serviceResolver := webhook.NewDefaultServiceResolver() - kubeAPIs, pluginInitializer, err := controlplaneapiserver.CreateConfig(opts, genericConfig, versionedInformers, storageFactory, serviceResolver, nil) - if err != nil { - return nil, err - } - c.ControlPlane = kubeAPIs - c.ControlPlane.Generic.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion() - - authInfoResolver := webhook.NewDefaultAuthenticationInfoResolverWrapper(kubeAPIs.ProxyTransport, kubeAPIs.Generic.EgressSelector, kubeAPIs.Generic.LoopbackClientConfig, kubeAPIs.Generic.TracerProvider) - apiExtensions, err := controlplaneapiserver.CreateAPIExtensionsConfig(*kubeAPIs.Generic, kubeAPIs.VersionedInformers, pluginInitializer, opts, 3, serviceResolver, authInfoResolver) - if err != nil { - return nil, err - } - c.APIExtensions = apiExtensions - - // TODO(jreese) create an admission plugin that will prohibit the creation of - // a Secret with a type of `kubernetes.io/service-account-token` - c.APIExtensions.GenericConfig.DisabledPostStartHooks.Insert("start-legacy-token-tracking-controller") - - aggregator, err := controlplaneapiserver.CreateAggregatorConfig(*kubeAPIs.Generic, opts, kubeAPIs.VersionedInformers, serviceResolver, kubeAPIs.ProxyTransport, kubeAPIs.Extra.PeerProxy, pluginInitializer) - if err != nil { - return nil, err - } - c.Aggregator = aggregator - c.Aggregator.ExtraConfig.DisableRemoteAvailableConditionController = true - // TODO(jreese) better version handling - c.Aggregator.GenericConfig.EffectiveVersion = utilversion.DefaultKubeEffectiveVersion() - - return c, nil -} - -// Taken from https://github.com/kubernetes/kubernetes/blob/50fc400f178d2078d0ca46aee955ee26375fc437/staging/src/k8s.io/apiserver/pkg/server/config.go#L1004 -// -// Modified to inject the following filters at the necessary locations: -// - datumfilters.OrganizationContextAuthorizationDecorator -// - datumfilters.ProjectListOrganizationConstraintDecorator -// - datumfilters.OrganizationContextHandler -// -// This is done to improve the UX that customers will experience while -// interacting with the Datum API server. -// -// Some handlers have not been added as a result of not having access to -// lifecycleSignals in server.Config. TODO(jreese) need to look into this more -func DefaultBuildHandlerChain(apiHandler http.Handler, c *server.Config) http.Handler { - handler := apiHandler - - handler = filterlatency.TrackCompleted(handler) - handler = genericapifilters.WithAuthorization(handler, c.Authorization.Authorizer, c.Serializer) - handler = datumfilters.OrganizationContextAuthorizationDecorator(handler) - handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authorization") - - if c.FlowControl != nil { - workEstimatorCfg := flowcontrolrequest.DefaultWorkEstimatorConfig() - requestWorkEstimator := flowcontrolrequest.NewWorkEstimator( - c.StorageObjectCountTracker.Get, c.FlowControl.GetInterestedWatchCount, workEstimatorCfg, c.FlowControl.GetMaxSeats) - handler = filterlatency.TrackCompleted(handler) - handler = genericfilters.WithPriorityAndFairness(handler, c.LongRunningFunc, c.FlowControl, requestWorkEstimator, c.RequestTimeout/4) - handler = filterlatency.TrackStarted(handler, c.TracerProvider, "priorityandfairness") - } else { - handler = genericfilters.WithMaxInFlightLimit(handler, c.MaxRequestsInFlight, c.MaxMutatingRequestsInFlight, c.LongRunningFunc) - } - - handler = filterlatency.TrackCompleted(handler) - handler = genericapifilters.WithImpersonation(handler, c.Authorization.Authorizer, c.Serializer) - handler = filterlatency.TrackStarted(handler, c.TracerProvider, "impersonation") - - handler = filterlatency.TrackCompleted(handler) - handler = genericapifilters.WithAudit(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator, c.LongRunningFunc) - handler = filterlatency.TrackStarted(handler, c.TracerProvider, "audit") - - failedHandler := genericapifilters.Unauthorized(c.Serializer) - failedHandler = genericapifilters.WithFailedAuthenticationAudit(failedHandler, c.AuditBackend, c.AuditPolicyRuleEvaluator) - - failedHandler = filterlatency.TrackCompleted(failedHandler) - handler = filterlatency.TrackCompleted(handler) - handler = genericapifilters.WithAuthentication(handler, c.Authentication.Authenticator, failedHandler, c.Authentication.APIAudiences, c.Authentication.RequestHeaderConfig) - handler = filterlatency.TrackStarted(handler, c.TracerProvider, "authentication") - - handler = genericfilters.WithCORS(handler, c.CorsAllowedOriginList, nil, nil, nil, "true") - - // WithWarningRecorder must be wrapped by the timeout handler - // to make the addition of warning headers threadsafe - handler = genericapifilters.WithWarningRecorder(handler) - - // WithTimeoutForNonLongRunningRequests will call the rest of the request handling in a go-routine with the - // context with deadline. The go-routine can keep running, while the timeout logic will return a timeout to the client. - handler = genericfilters.WithTimeoutForNonLongRunningRequests(handler, c.LongRunningFunc) - - handler = genericapifilters.WithRequestDeadline(handler, c.AuditBackend, c.AuditPolicyRuleEvaluator, - c.LongRunningFunc, c.Serializer, c.RequestTimeout) - handler = genericfilters.WithWaitGroup(handler, c.LongRunningFunc, c.NonLongRunningRequestWaitGroup) - // if c.ShutdownWatchTerminationGracePeriod > 0 { - // handler = genericfilters.WithWatchTerminationDuringShutdown(handler, c.lifecycleSignals, c.WatchRequestWaitGroup) - // } - if c.SecureServing != nil && !c.SecureServing.DisableHTTP2 && c.GoawayChance > 0 { - handler = genericfilters.WithProbabilisticGoaway(handler, c.GoawayChance) - } - handler = genericapifilters.WithCacheControl(handler) - handler = genericfilters.WithHSTS(handler, c.HSTSDirectives) - // if c.ShutdownSendRetryAfter { - // handler = genericfilters.WithRetryAfter(handler, c.lifecycleSignals.NotAcceptingNewRequest.Signaled()) - // } - handler = genericfilters.WithHTTPLogging(handler) - if c.FeatureGate.Enabled(genericfeatures.APIServerTracing) { - handler = genericapifilters.WithTracing(handler, c.TracerProvider) - } - handler = genericapifilters.WithLatencyTrackers(handler) - // WithRoutine will execute future handlers in a separate goroutine and serving - // handler in current goroutine to minimize the stack memory usage. It must be - // after WithPanicRecover() to be protected from panics. - if c.FeatureGate.Enabled(genericfeatures.APIServingWithRoutine) { - handler = routine.WithRoutine(handler, c.LongRunningFunc) - } - - handler = datumfilters.OrganizationProjectListConstraintDecorator(handler) - handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver) - handler = genericapifilters.WithRequestReceivedTimestamp(handler) - // handler = genericapifilters.WithMuxAndDiscoveryComplete(handler, c.lifecycleSignals.MuxAndDiscoveryComplete.Signaled()) - handler = genericfilters.WithPanicRecovery(handler, c.RequestInfoResolver) - handler = genericapifilters.WithAuditInit(handler) - - handler = datumfilters.OrganizationContextHandler(handler, c.Serializer) - - return handler -} diff --git a/cmd/datum-apiserver/app/server.go b/cmd/datum-apiserver/app/server.go deleted file mode 100644 index 6545a40..0000000 --- a/cmd/datum-apiserver/app/server.go +++ /dev/null @@ -1,220 +0,0 @@ -package app - -import ( - "context" - "fmt" - "net" - "os" - "path/filepath" - - "github.com/spf13/cobra" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - _ "k8s.io/apiserver/pkg/admission" - genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" - genericapiserver "k8s.io/apiserver/pkg/server" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/apiserver/pkg/util/notfoundhandler" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - cliflag "k8s.io/component-base/cli/flag" - "k8s.io/component-base/cli/globalflag" - "k8s.io/component-base/featuregate" - "k8s.io/component-base/logs" - logsapi "k8s.io/component-base/logs/api/v1" - _ "k8s.io/component-base/metrics/prometheus/workqueue" - "k8s.io/component-base/term" - "k8s.io/component-base/version" - "k8s.io/component-base/version/verflag" - "k8s.io/klog/v2" - aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver" - controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver" - "k8s.io/kubernetes/pkg/controlplane/apiserver/options" -) - -func init() { - utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) -} - -// NewCommand creates a *cobra.Command object with default parameters -func NewCommand() *cobra.Command { - s := NewOptions() - var namedFlagSets cliflag.NamedFlagSets - - cmd := &cobra.Command{ - Use: "datum-apiserver", - Long: `The Datum API server serves Datum Cloud Infrastructure related APIs -which are compatible with Kubernetes clients.`, - - // stop printing usage when the command errors - SilenceUsage: true, - PersistentPreRunE: func(*cobra.Command, []string) error { - // silence client-go warnings. - // kube-apiserver loopback clients should not log self-issued warnings. - rest.SetDefaultWarningHandler(rest.NoWarnings{}) - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - verflag.PrintAndExitIfRequested() - fs := cmd.Flags() - - // Activate logging as soon as possible, after that - // show flags with the final logging configuration. - if err := logsapi.ValidateAndApply(s.Logs, utilfeature.DefaultFeatureGate); err != nil { - return err - } - cliflag.PrintFlags(fs) - s.SystemNamespaces = []string{metav1.NamespaceSystem, metav1.NamespaceDefault, "datum-system"} - - completedOptions, err := s.Complete(cmd.Context(), namedFlagSets, []string{}, []net.IP{}) - if err != nil { - return err - } - - // utilfeature.DefaultMutableFeatureGate.Set("ExternalServiceAccountTokenSigner=true") - - if errs := completedOptions.Validate(); len(errs) != 0 { - return utilerrors.NewAggregate(errs) - } - - // add feature enablement metrics - utilfeature.DefaultMutableFeatureGate.AddMetrics() - - ctx := genericapiserver.SetupSignalContext() - return Run(ctx, completedOptions) - }, - Args: func(cmd *cobra.Command, args []string) error { - for _, arg := range args { - if len(arg) > 0 { - return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args) - } - } - return nil - }, - } - - // Override the ComponentGlobalsRegistry to avoid k8s feature flags from being added. - // Add our own feature gates or expose native k8s ones if necessary - s.GenericServerRunOptions.ComponentGlobalsRegistry = featuregate.NewComponentGlobalsRegistry() - s.GenericServerRunOptions.AddUniversalFlags(namedFlagSets.FlagSet("generic")) - s.Etcd.AddFlags(namedFlagSets.FlagSet("etcd")) - s.SecureServing.AddFlags(namedFlagSets.FlagSet("secure serving")) - s.Audit.AddFlags(namedFlagSets.FlagSet("auditing")) - s.Features.AddFlags(namedFlagSets.FlagSet("features")) - s.Authentication.AddFlags(namedFlagSets.FlagSet("authentication")) - s.Authorization.AddFlags(namedFlagSets.FlagSet("authorization")) - // Add our own Admission flags - s.Metrics.AddFlags(namedFlagSets.FlagSet("metrics")) - logsapi.AddFlags(s.Logs, namedFlagSets.FlagSet("logs")) - s.Traces.AddFlags(namedFlagSets.FlagSet("traces")) - // Add misc flags for event ttl - - verflag.AddFlags(namedFlagSets.FlagSet("global")) - globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags()) - - fs := cmd.Flags() - for _, f := range namedFlagSets.FlagSets { - fs.AddFlagSet(f) - } - - fs.StringVar(&s.ServiceAccountSigningKeyFile, "service-account-signing-key-file", s.ServiceAccountSigningKeyFile, ""+ - "Path to the file that contains the current private key of the service account token issuer. The issuer will sign issued ID tokens with this private key.") - - fs.StringVar(&s.ServiceAccountSigningEndpoint, "service-account-signing-endpoint", s.ServiceAccountSigningEndpoint, ""+ - "Path to socket where a external JWT signer is listening. This flag is mutually exclusive with --service-account-signing-key-file and --service-account-key-file. Requires enabling feature gate (ExternalServiceAccountTokenSigner)") - - cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) - cliflag.SetUsageAndHelpFunc(cmd, namedFlagSets, cols) - - return cmd -} - -func NewOptions() *options.Options { - s := options.NewOptions() - s.Admission.GenericAdmission.DefaultOffPlugins = DefaultOffAdmissionPlugins() - - wd, _ := os.Getwd() - s.SecureServing.ServerCert.CertDirectory = filepath.Join(wd, ".sample-minimal-controlplane") - - // Wire ServiceAccount authentication without relying on pods and nodes. - s.Authentication.ServiceAccounts.OptionalTokenGetter = genericTokenGetter - s.ServiceAccountIssuer = &jwtTokenGenerator{} - - return s -} - -// Run runs the specified APIServer. This should never exit. -func Run(ctx context.Context, opts options.CompletedOptions) error { - // To help debugging, immediately log version - klog.Infof("Version: %+v", version.Get()) - - klog.InfoS("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK")) - - config, err := NewConfig(opts) - if err != nil { - return err - } - - completed, err := config.Complete() - if err != nil { - return err - } - - server, err := CreateServerChain(completed) - if err != nil { - return err - } - - prepared, err := server.PrepareRun() - if err != nil { - return err - } - - return prepared.Run(ctx) -} - -// CreateServerChain creates the apiservers connected via delegation. -func CreateServerChain(config CompletedConfig) (*aggregatorapiserver.APIAggregator, error) { - // 1. CRDs - notFoundHandler := notfoundhandler.New(config.ControlPlane.Generic.Serializer, genericapifilters.NoMuxAndDiscoveryIncompleteKey) - apiExtensionsServer, err := config.APIExtensions.New(genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler)) - if err != nil { - return nil, fmt.Errorf("failed to create apiextensions-apiserver: %w", err) - } - crdAPIEnabled := config.APIExtensions.GenericConfig.MergedResourceConfig.ResourceEnabled(apiextensionsv1.SchemeGroupVersion.WithResource("customresourcedefinitions")) - - // 2. Natively implemented resources - nativeAPIs, err := config.ControlPlane.New("datum-apiserver", apiExtensionsServer.GenericAPIServer) - if err != nil { - return nil, fmt.Errorf("failed to create datum controlplane apiserver: %w", err) - } - client, err := kubernetes.NewForConfig(config.ControlPlane.Generic.LoopbackClientConfig) - if err != nil { - return nil, err - } - storageProviders, err := config.GenericStorageProviders(client.Discovery()) - if err != nil { - return nil, fmt.Errorf("failed to create storage providers: %w", err) - } - - if err := nativeAPIs.InstallAPIs(storageProviders...); err != nil { - return nil, fmt.Errorf("failed to install APIs: %w", err) - } - - // 3. Aggregator for APIServices, discovery and OpenAPI - aggregatorServer, err := controlplaneapiserver.CreateAggregatorServer( - config.Aggregator, - nativeAPIs.GenericAPIServer, - apiExtensionsServer.Informers.Apiextensions().V1().CustomResourceDefinitions(), - crdAPIEnabled, - controlplaneapiserver.DefaultGenericAPIServicePriorities(), - ) - if err != nil { - // we don't need special handling for innerStopCh because the aggregator server doesn't create any go routines - return nil, fmt.Errorf("failed to create kube-aggregator: %w", err) - } - - return aggregatorServer, nil -} diff --git a/cmd/datum-apiserver/app/serviceaccounts.go b/cmd/datum-apiserver/app/serviceaccounts.go deleted file mode 100644 index a446ef8..0000000 --- a/cmd/datum-apiserver/app/serviceaccounts.go +++ /dev/null @@ -1,51 +0,0 @@ -package app - -import ( - "context" - - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" - v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/informers" - v1listers "k8s.io/client-go/listers/core/v1" - "k8s.io/kubernetes/pkg/serviceaccount" -) - -// clientGetter implements ServiceAccountTokenGetter using a factory function -type clientGetter struct { - secretLister v1listers.SecretLister - serviceAccountLister v1listers.ServiceAccountLister -} - -// genericTokenGetter returns a ServiceAccountTokenGetter that does not depend -// on pods and nodes. -func genericTokenGetter(factory informers.SharedInformerFactory) serviceaccount.ServiceAccountTokenGetter { - return clientGetter{} - // return clientGetter{secretLister: factory.Core().V1().Secrets().Lister(), serviceAccountLister: factory.Core().V1().ServiceAccounts().Lister()} -} - -func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) { - return c.serviceAccountLister.ServiceAccounts(namespace).Get(name) -} - -func (c clientGetter) GetPod(namespace, name string) (*v1.Pod, error) { - return nil, apierrors.NewNotFound(v1.Resource("pods"), name) -} - -func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) { - return c.secretLister.Secrets(namespace).Get(name) -} - -func (c clientGetter) GetNode(name string) (*v1.Node, error) { - return nil, apierrors.NewNotFound(v1.Resource("nodes"), name) -} - -type jwtTokenGenerator struct { - iss string - signer jose.Signer -} - -func (j *jwtTokenGenerator) GenerateToken(ctx context.Context, claims *jwt.Claims, privateClaims interface{}) (string, error) { - return "", nil -} diff --git a/cmd/datum-apiserver/main.go b/cmd/datum-apiserver/main.go deleted file mode 100644 index 3a352c5..0000000 --- a/cmd/datum-apiserver/main.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "os" - - "k8s.io/component-base/cli" - - "go.datumapis.com/datum/cmd/datum-apiserver/app" -) - -// Implementation approach follows work done in https://github.com/kubernetes/kubernetes/pull/126260 -func main() { - command := app.NewCommand() - code := cli.Run(command) - os.Exit(code) -} diff --git a/cmd/datum-authorization-webhook/Dockerfile b/cmd/datum-authorization-webhook/Dockerfile deleted file mode 100644 index 93fb1b0..0000000 --- a/cmd/datum-authorization-webhook/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -# Use the official Go image as a build stage -FROM golang:1.24 AS builder - -# Set the working directory inside the container -WORKDIR /app - -# Copy go.mod and go.sum files and download dependencies -COPY go.mod go.sum ./ -RUN go mod download - -# Copy the rest of the application source code -COPY . . - -# Build the application -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o datum-authorization-webhook ./cmd/datum-authorization-webhook - -# Use a minimal image for the final container -FROM gcr.io/distroless/static -WORKDIR /app -COPY --from=builder /app/datum-authorization-webhook . -ENTRYPOINT ["/app/datum-authorization-webhook"] diff --git a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go deleted file mode 100644 index 8574784..0000000 --- a/cmd/datum-authorization-webhook/app/internal/iam/core_control_plane_authorizer.go +++ /dev/null @@ -1,94 +0,0 @@ -package iam - -import ( - "context" - "fmt" - "log/slog" - "slices" - - "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" - iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" - "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -var _ authorizer.Authorizer = &CoreControlPlaneAuthorizer{} - -type CoreControlPlaneAuthorizer struct { - IAMClient iamv1alphagrpc.AccessCheckClient -} - -// Authorize implements authorizer.Authorizer. -func (o *CoreControlPlaneAuthorizer) Authorize(ctx context.Context, attributes authorizer.Attributes) (authorizer.Decision, string, error) { - ctx, span := otel.Tracer("go.datum.net/k8s-authz-webhook").Start(ctx, "datum.core-control-plane.Authorize", trace.WithAttributes( - attribute.String("api_group", attributes.GetAPIGroup()), - attribute.String("resource_kind", attributes.GetResource()), - attribute.String("subject", attributes.GetUser().GetName()), - )) - defer span.End() - - if attributes.GetAPIGroup() != "resourcemanager.datumapis.com" { - slog.DebugContext(ctx, "No opinion on auth webhook request since API Group is not managed by webhook", slog.String("api_group", attributes.GetAPIGroup())) - return authorizer.DecisionNoOpinion, "", nil - } - - var organizationID string - if orgIDs, set := attributes.GetUser().GetExtra()[webhook.OrganizationIDExtraKey]; !set { - return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' is required by core control plane authorizer", webhook.OrganizationIDExtraKey) - } else if len(orgIDs) > 1 { - return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", webhook.OrganizationIDExtraKey, orgIDs) - } else { - organizationID = orgIDs[0] - } - - req := getCheckAccessRequest(attributes, organizationID) - - span.SetAttributes( - attribute.String("subject", req.Subject), - attribute.String("resource", req.Resource), - attribute.String("permission", req.Permission), - ) - - resp, err := o.IAMClient.CheckAccess(ctx, req) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - slog.ErrorContext(ctx, "failed to check subject access in IAM system", slog.String("error", err.Error())) - return authorizer.DecisionNoOpinion, "", err - } - span.SetAttributes(attribute.Bool("allowed", resp.GetAllowed())) - - if resp.GetAllowed() { - slog.DebugContext(ctx, "subject was granted access through IAM service") - return authorizer.DecisionAllow, "", nil - } - - return authorizer.DecisionDeny, "", nil -} - -func getCheckAccessRequest(attributes authorizer.Attributes, organizationID string) *iampb.CheckAccessRequest { - req := &iampb.CheckAccessRequest{ - Subject: "user:" + attributes.GetUser().GetName(), - Permission: fmt.Sprintf("%s/%s.%s", attributes.GetAPIGroup(), attributes.GetResource(), attributes.GetVerb()), - } - - // Use the organization resource URL when acting on resource collections. - if slices.Contains([]string{"list", "create", "watch"}, attributes.GetVerb()) { - req.Resource = "resourcemanager.datumapis.com/organizations/" + organizationID - } else { - req.Resource = fmt.Sprintf("resourcemanager.datumapis.com/%s/%s", attributes.GetResource(), attributes.GetName()) - req.Context = []*iampb.CheckContext{{ - ContextType: &iampb.CheckContext_ParentRelationship{ - ParentRelationship: &iampb.ParentRelationship{ - ParentResource: "resourcemanager.datumapis.com/organizations/" + organizationID, - ChildResource: req.Resource, - }, - }, - }} - } - - return req -} diff --git a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go b/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go deleted file mode 100644 index a5ac29d..0000000 --- a/cmd/datum-authorization-webhook/app/internal/iam/project_control_plane_authorizer.go +++ /dev/null @@ -1,84 +0,0 @@ -package iam - -import ( - "context" - "fmt" - "log/slog" - - "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" - iampb "buf.build/gen/go/datum-cloud/iam/protocolbuffers/go/datum/iam/v1alpha" - "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - "go.opentelemetry.io/otel/trace" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -var _ authorizer.Authorizer = &ProjectControlPlaneAuthorizer{} - -type ProjectControlPlaneAuthorizer struct { - IAMClient iamv1alphagrpc.AccessCheckClient -} - -// Contains a mapping of Kubernetes APIGroups to the service name that should be -// used by the webhook to perform authorization checks. -var serviceNameMapping = map[string]string{ - // An empty APIGroup is used for the core/v1 Kubernetes API Group. - "": "core.datumapis.com", -} - -// Authorize implements authorizer.Authorizer. -func (o *ProjectControlPlaneAuthorizer) Authorize( - ctx context.Context, attributes authorizer.Attributes, -) (authorizer.Decision, string, error) { - - ctx, span := otel.Tracer("go.datum.net/datum/cmd/datum-authorization-webhook").Start(ctx, "datum.authz-webhook.Authorize", trace.WithAttributes( - attribute.String("subject", attributes.GetUser().GetName()), - )) - defer span.End() - - var projectName string - if projectNames, set := attributes.GetUser().GetExtra()[webhook.ProjectExtraKey]; !set { - span.SetStatus(codes.Error, "no project ID present in webhook request") - return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' is required by core control plane authorizer", webhook.ProjectExtraKey) - } else if len(projectNames) > 1 { - span.SetStatus(codes.Error, "multiple project IDs present in webhook request") - return authorizer.DecisionDeny, "", fmt.Errorf("extra '%s' only supports one value, but multiple were provided: %v", webhook.ProjectExtraKey, projectNames) - } else { - projectName = projectNames[0] - } - - serviceName := attributes.GetAPIGroup() - if override, set := serviceNameMapping[serviceName]; set { - serviceName = override - } - - resourceURL := "resourcemanager.datumapis.com/" + projectName - permissionName := fmt.Sprintf("%s/%s.%s", serviceName, attributes.GetResource(), attributes.GetVerb()) - - span.SetAttributes( - attribute.String("resource", resourceURL), - attribute.String("permission", permissionName), - ) - - resp, err := o.IAMClient.CheckAccess(ctx, &iampb.CheckAccessRequest{ - Resource: resourceURL, - Subject: "user:" + attributes.GetUser().GetName(), - Permission: permissionName, - }) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - slog.ErrorContext(ctx, "failed to check subject access in IAM system", slog.String("error", err.Error())) - return authorizer.DecisionNoOpinion, "", err - } - span.SetAttributes(attribute.Bool("allowed", resp.GetAllowed())) - - if resp.GetAllowed() { - slog.DebugContext(ctx, "subject was granted access through IAM service") - return authorizer.DecisionAllow, "", nil - } - - return authorizer.DecisionDeny, "", nil -} diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/http.go b/cmd/datum-authorization-webhook/app/internal/webhook/http.go deleted file mode 100644 index 77a64be..0000000 --- a/cmd/datum-authorization-webhook/app/internal/webhook/http.go +++ /dev/null @@ -1,147 +0,0 @@ -package webhook - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "log/slog" - "net/http" - - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/codes" - authorizationv1 "k8s.io/api/authorization/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" -) - -var authorizationScheme = runtime.NewScheme() -var authorizationCodecs = serializer.NewCodecFactory(authorizationScheme) - -func init() { - utilruntime.Must(authorizationv1.AddToScheme(authorizationScheme)) -} - -// Request defines the input for an authorization handler. -// It contains information to identify the object in -// question (group, version, kind, resource, subresource, -// name, namespace), as well as the operation in question -// (e.g. Get, Create, etc), and the object itself. -type Request struct { - authorizationv1.SubjectAccessReview -} - -// Response is the output of an authorization handler. -// It contains a response indicating if a given -// operation is allowed. -type Response struct { - authorizationv1.SubjectAccessReview -} - -// HandlerFunc implements Handler interface using a single function. -type HandlerFunc func(context.Context, Request) Response - -// Handler can handle an TokenReview. -type Handler interface { - // Handle yields a response to an TokenReview. - // - // The supplied context is extracted from the received http.Request, allowing wrapping - // http.Handlers to inject values into and control cancelation of downstream request processing. - Handle(context.Context, Request) Response -} - -var _ Handler = HandlerFunc(nil) - -// Handle process the SubjectAccessReview by invoking the underlying function. -func (f HandlerFunc) Handle(ctx context.Context, req Request) Response { - return f(ctx, req) -} - -var _ http.Handler = &Webhook{} - -// ServeHTTP implements http.Handler. -func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { - var body []byte - var err error - - ctx, span := otel.Tracer("go.datum.net/datum/cmd/datum-authorization-webhook").Start(r.Context(), "datum.authz-webhook.HandleRequest") - defer span.End() - - if wh.WithContextFunc != nil { - ctx = wh.WithContextFunc(ctx, r) - } - - var reviewResponse Response - if r.Body == nil { - err = errors.New("request body is empty") - reviewResponse = Errored(err) - wh.writeResponse(w, nil, reviewResponse) - return - } - defer r.Body.Close() - - if body, err = io.ReadAll(r.Body); err != nil { - span.SetStatus(codes.Error, err.Error()) - reviewResponse = Errored(err) - wh.writeResponse(w, nil, reviewResponse) - return - } - - // verify the content type is accurate - if contentType := r.Header.Get("Content-Type"); contentType != "application/json" { - reviewResponse = Errored(fmt.Errorf("contentType=%s, expected application/json", contentType)) - span.SetStatus(codes.Error, "invalid content-type header provided") - wh.writeResponse(w, nil, reviewResponse) - return - } - - req := Request{} - _, _, err = authorizationCodecs.UniversalDeserializer().Decode(body, nil, &req.SubjectAccessReview) - if err != nil { - span.SetStatus(codes.Error, err.Error()) - reviewResponse = Errored(err) - wh.writeResponse(w, &req, reviewResponse) - return - } - - if projectID := r.PathValue("project"); projectID != "" { - projectName := "projects/" + projectID - span.SetAttributes(attribute.String("project", projectName)) - if req.Spec.Extra == nil { - req.Spec.Extra = map[string]authorizationv1.ExtraValue{} - } - req.Spec.Extra[ProjectExtraKey] = []string{projectName} - } - - reviewResponse = wh.Handle(ctx, req) - if reviewResponse.Status.EvaluationError != "" { - span.RecordError(errors.New(reviewResponse.Status.EvaluationError)) - span.SetStatus(codes.Error, reviewResponse.Status.EvaluationError) - } - - span.SetAttributes( - attribute.Bool("denied", reviewResponse.Status.Denied), - attribute.Bool("allowed", reviewResponse.Status.Allowed), - ) - - slog.InfoContext( - ctx, - "handled SubjectAccessReview webhook request", - ) - wh.writeResponse(w, &req, reviewResponse) -} - -// writeTokenResponse writes response resp to w. req is optional (can be nil) and adds -// context for the logger. -func (wh *Webhook) writeResponse(w io.Writer, req *Request, resp Response) { - _ = req - - resp.SetGroupVersionKind(authorizationv1.SchemeGroupVersion.WithKind("SubjectAccessReview")) - - if err := json.NewEncoder(w).Encode(resp.SubjectAccessReview); err != nil { - panic(err) - } -} diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/response.go b/cmd/datum-authorization-webhook/app/internal/webhook/response.go deleted file mode 100644 index 37bfc9d..0000000 --- a/cmd/datum-authorization-webhook/app/internal/webhook/response.go +++ /dev/null @@ -1,31 +0,0 @@ -// This file is inspired by https://github.com/kubernetes-sigs/controller-runtime/blob/v0.16.3/pkg/webhook/authentication/response.go -package webhook - -import ( - authorizationv1 "k8s.io/api/authorization/v1" -) - -// Denied constructs a response indicating that the given user is denied -// to perform the given action. The reason parameter is optional. -func Denied(reason string) Response { - return AuthorizationResponse(false, true, reason, "") -} - -// Errored creates a new Response for error-handling a request. -func Errored(err error) Response { - return AuthorizationResponse(false, false, "", err.Error()) -} - -// AuthorizationResponse returns a response an authorization request. -func AuthorizationResponse(allowed, denied bool, reason, evaluationError string) Response { - return Response{ - authorizationv1.SubjectAccessReview{ - Status: authorizationv1.SubjectAccessReviewStatus{ - Allowed: allowed, - Denied: denied, - Reason: reason, - EvaluationError: evaluationError, - }, - }, - } -} diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go deleted file mode 100644 index 0ed2519..0000000 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook.go +++ /dev/null @@ -1,97 +0,0 @@ -package webhook - -import ( - "context" - "log/slog" - "net/http" - - authorizationv1 "k8s.io/api/authorization/v1" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -const OrganizationIDExtraKey = "resourcemanager.datumapis.com/organization-id" -const ProjectExtraKey = "resourcemanager.datumapis.com/project-name" - -func NewAuthorizerWebhook(authzer authorizer.Authorizer) *Webhook { - return &Webhook{ - Handler: HandlerFunc(func(ctx context.Context, r Request) Response { - if r.Spec.ResourceAttributes != nil && r.Spec.NonResourceAttributes != nil { - return Denied("must specify oneof resource or non-resource attributes, not both") - } - - extra := map[string][]string{} - for key, val := range r.Spec.Extra { - extra[key] = []string(val) - } - - attrs := authorizer.AttributesRecord{ - User: &user.DefaultInfo{ - Name: r.Spec.User, - UID: r.Spec.UID, - Groups: r.Spec.Groups, - Extra: extra, - }, - } - - if resourceAttrs := r.Spec.ResourceAttributes; resourceAttrs != nil { - attrs.Verb = resourceAttrs.Verb - attrs.Namespace = resourceAttrs.Namespace - attrs.APIGroup = resourceAttrs.Group - attrs.APIVersion = resourceAttrs.Version - attrs.Resource = resourceAttrs.Resource - attrs.Subresource = resourceAttrs.Subresource - attrs.Name = resourceAttrs.Name - attrs.ResourceRequest = true - } - - if nonResourceAttrs := r.Spec.NonResourceAttributes; nonResourceAttrs != nil { - attrs.Verb = nonResourceAttrs.Verb - attrs.Path = nonResourceAttrs.Path - attrs.ResourceRequest = false - } - - decision, reason, err := authzer.Authorize( - ctx, - attrs, - ) - if err != nil { - slog.ErrorContext(ctx, "error authorizing request", slog.String("error", err.Error())) - return Errored(err) - } - - status := authorizationv1.SubjectAccessReviewStatus{ - Reason: reason, - } - - switch decision { - case authorizer.DecisionAllow: - status.Allowed = true - case authorizer.DecisionDeny: - status.Denied = true - } - - return Response{ - SubjectAccessReview: authorizationv1.SubjectAccessReview{ - Status: status, - }, - } - }), - } -} - -// Webhook represents each individual webhook. -type Webhook struct { - // Handler actually processes an authorization request returning whether it was authorized - Handler Handler - - // WithContextFunc will allow you to take the http.Request.Context() and - // add any additional information such as passing the request path or - // headers thus allowing you to read them from within the handler - WithContextFunc func(context.Context, *http.Request) context.Context -} - -// Handle processes SubjectAccessReview. -func (wh *Webhook) Handle(ctx context.Context, req Request) Response { - return wh.Handler.Handle(ctx, req) -} diff --git a/cmd/datum-authorization-webhook/app/internal/webhook/webhook_test.go b/cmd/datum-authorization-webhook/app/internal/webhook/webhook_test.go deleted file mode 100644 index 6e3da7d..0000000 --- a/cmd/datum-authorization-webhook/app/internal/webhook/webhook_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package webhook_test - -import ( - "context" - "encoding/json" - "errors" - "net/http" - "net/url" - "strings" - "testing" - - "github.com/google/go-cmp/cmp" - "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" - v1 "k8s.io/api/authorization/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apiserver/pkg/authorization/authorizer" -) - -func TestWebhook(t *testing.T) { - mux := http.NewServeMux() - // Register the project specific webhook mux - mux.Handle("/project/v1alpha/projects/{project}/webhook", webhook.NewAuthorizerWebhook(authorizer.AuthorizerFunc(func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { - return authorizer.DecisionAllow, "", nil - }))) - - srv := &http.Server{ - Addr: "127.0.0.1:11000", - Handler: mux, - } - - go func() { - err := srv.ListenAndServe() - if err != nil && !errors.Is(err, http.ErrServerClosed) { - t.Errorf("failed to start webhook server: %s", err) - } - }() - - defer srv.Shutdown(context.Background()) - - client := &http.Client{} - - // Define test cases to execute against the webhook - testCases := []struct { - desc string - url url.URL - request webhook.Request - expectedResponse webhook.Response - expectedStatusCode int - }{ - { - desc: "Validate server returns correct authorization response when extra data is empty", - url: url.URL{ - Path: "/project/v1alpha/projects/my-personal-project/webhook", - }, - request: webhook.Request{ - SubjectAccessReview: v1.SubjectAccessReview{ - Spec: v1.SubjectAccessReviewSpec{ - User: "user:test-user@datum.net", - UID: "525c3cc0-6960-4950-8999-f50af1bc050d", - ResourceAttributes: &v1.ResourceAttributes{ - Namespace: "default", - Verb: "use", - Group: "networking.datumapis.com", - Version: "v1alpha", - Resource: "networks", - Name: "default", - }, - }, - }, - }, - expectedStatusCode: http.StatusOK, - expectedResponse: webhook.Response{ - SubjectAccessReview: v1.SubjectAccessReview{ - TypeMeta: metav1.TypeMeta{ - Kind: "SubjectAccessReview", - APIVersion: "authorization.k8s.io/v1", - }, - Status: v1.SubjectAccessReviewStatus{ - Allowed: true, - Denied: false, - }, - }, - }, - }, - } - - for _, testCase := range testCases { - // Configure to what the server is listening on - testCase.url.Host = "localhost:11000" - testCase.url.Scheme = "http" - - t.Run(testCase.desc, func(t *testing.T) { - body, err := json.Marshal(testCase.request) - if err != nil { - t.Errorf("failed to marshall authorization request: %s", err) - return - } - - resp, err := client.Post(testCase.url.String(), "application/json", strings.NewReader(string(body))) - if err != nil { - t.Errorf("failed to call webhook endpoint: %s", err) - return - } - - if resp.StatusCode != testCase.expectedStatusCode { - t.Errorf("got status code %d but expected %d", resp.StatusCode, &testCase.expectedStatusCode) - return - } - - webhookResponse := webhook.Response{} - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&webhookResponse); err != nil { - t.Errorf("failed to decode webhook response: %s", err) - return - } - - if !cmp.Equal(webhookResponse, testCase.expectedResponse) { - t.Error("webhook response did not match expected response, see logs for details") - t.Log("response diff: ", cmp.Diff(webhookResponse, testCase.expectedResponse)) - } - }) - } -} diff --git a/cmd/datum-authorization-webhook/app/serve.go b/cmd/datum-authorization-webhook/app/serve.go deleted file mode 100644 index 39fe49c..0000000 --- a/cmd/datum-authorization-webhook/app/serve.go +++ /dev/null @@ -1,135 +0,0 @@ -package app - -import ( - "context" - "fmt" - - "buf.build/gen/go/datum-cloud/iam/grpc/go/datum/iam/v1alpha/iamv1alphagrpc" - "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/iam" - authwebhook "go.datumapis.com/datum/cmd/datum-authorization-webhook/app/internal/webhook" - - "github.com/spf13/cobra" - "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace" - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" - "go.opentelemetry.io/otel/propagation" - "go.opentelemetry.io/otel/sdk/resource" - "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.20.0" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "k8s.io/api/authentication/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -func serveCommand() *cobra.Command { - var iamEndpoint, certDir, certFile, keyFile string - var iamInsecure bool - - var serveCmd = &cobra.Command{ - Use: "serve", - Short: "Run the Authorization Webhook API server", - RunE: func(cmd *cobra.Command, args []string) error { - if iamEndpoint == "" { - return fmt.Errorf("`--iam-endpoint` is required") - } - - exporter, err := otlptrace.New(cmd.Context(), otlptracegrpc.NewClient()) - if err != nil { - return err - } - - otel.SetTracerProvider(trace.NewTracerProvider( - trace.WithSampler(trace.AlwaysSample()), - trace.WithResource(resource.NewWithAttributes( - semconv.SchemaURL, - semconv.ServiceName("authorization-webhook.datumapis.com"), - )), - trace.WithBatcher(exporter), - )) - otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( - propagation.TraceContext{}, - propagation.Baggage{}, - )) - - dialOptions := []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler()), - } - - if iamInsecure { - dialOptions = append(dialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) - } else { - dialOptions = append(dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(nil))) - } - - iamConnection, err := grpc.NewClient(iamEndpoint, dialOptions...) - if err != nil { - return fmt.Errorf("failed to create new IAM client: %w", err) - } - defer iamConnection.Close() - - log.SetLogger(zap.New(zap.JSONEncoder())) - - iamClient := iamv1alphagrpc.NewAccessCheckClient(iamConnection) - - entryLog := log.Log.WithName("entrypoint") - - restConfig, err := config.GetConfig() - if err != nil { - return fmt.Errorf("failed to get rest config: %s", err) - } - - runtimeScheme := runtime.NewScheme() - v1beta1.AddToScheme(runtimeScheme) - - // Setup a Manager - entryLog.Info("setting up manager") - mgr, err := manager.New(restConfig, manager.Options{ - Scheme: runtimeScheme, - Metrics: server.Options{ - BindAddress: ":8999", - }, - WebhookServer: webhook.NewServer(webhook.Options{ - CertDir: certDir, - CertName: certFile, - KeyName: keyFile, - }), - }) - if err != nil { - return fmt.Errorf("failed to setup manager: %s", err) - } - - // Setup webhooks - entryLog.Info("setting up webhook server") - hookServer := mgr.GetWebhookServer() - - entryLog.Info("registering webhooks to the webhook server") - - hookServer.Register("/project/v1alpha/projects/{project}/webhook", authwebhook.NewAuthorizerWebhook(&iam.ProjectControlPlaneAuthorizer{ - IAMClient: iamClient, - })) - hookServer.Register("/core/v1alpha/webhook", authwebhook.NewAuthorizerWebhook(&iam.CoreControlPlaneAuthorizer{ - IAMClient: iamClient, - })) - - return mgr.Start(context.Background()) - }, - } - - serveCmd.Flags().StringVar(&iamEndpoint, "iam-endpoint", "", "Endpoint to use for connecting to the datum gRPC API endpoint") - serveCmd.Flags().BoolVar(&iamInsecure, "iam-endpoint-insecure", false, "Whether the use an insecure connection when export") - - serveCmd.Flags().StringVar(&certDir, "cert-dir", "", "Directory that contains the TLS certs to use for serving the webhook") - serveCmd.Flags().StringVar(&certFile, "cert-file", "", "Filename in the directory that contains the TLS cert") - serveCmd.Flags().StringVar(&keyFile, "key-file", "", "Filename in the directory that contains the TLS private key") - - return serveCmd -} diff --git a/cmd/datum-authorization-webhook/app/webhook.go b/cmd/datum-authorization-webhook/app/webhook.go deleted file mode 100644 index c25fac2..0000000 --- a/cmd/datum-authorization-webhook/app/webhook.go +++ /dev/null @@ -1,33 +0,0 @@ -package app - -import ( - "fmt" - "log/slog" - - "github.com/spf13/cobra" - "go.datumapis.com/datum/pkg/cmd" -) - -func NewWebhook() *cobra.Command { - webhook := &cobra.Command{ - Use: "datum-authorization-webhook", - Short: "An authorization webhook backed by the Datum IAM service", - PersistentPreRunE: func(webhook *cobra.Command, args []string) error { - logger, err := cmd.SetupLogging(webhook) - if err != nil { - return fmt.Errorf("failed to configure logging: %w", err) - } - - slog.SetDefault(logger) - return nil - }, - } - - cmd.AddLoggingFlags(webhook.PersistentFlags()) - - webhook.AddCommand( - serveCommand(), - ) - - return webhook -} diff --git a/cmd/datum-authorization-webhook/webhook.go b/cmd/datum-authorization-webhook/webhook.go deleted file mode 100644 index 4f068b0..0000000 --- a/cmd/datum-authorization-webhook/webhook.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "go.datumapis.com/datum/cmd/datum-authorization-webhook/app" -) - -func main() { - if err := app.NewWebhook().Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/cmd/datum-controller-manager/Dockerfile b/cmd/datum-controller-manager/Dockerfile deleted file mode 100644 index 974e7bf..0000000 --- a/cmd/datum-controller-manager/Dockerfile +++ /dev/null @@ -1,25 +0,0 @@ -FROM golang:1.23 AS build-stage - -WORKDIR /workspace - -COPY go.mod go.mod -COPY go.sum go.sum -RUN go mod download - -COPY cmd/datum-controller-manager/ cmd/datum-controller-manager/ - -RUN --mount=type=cache,target=/go/pkg/mod/ \ - --mount=type=cache,target="/root/.cache/go-build" \ - CGO_ENABLED=0 GOOS=linux \ - go build \ - -ldflags="-X 'k8s.io/component-base/version/verflag.programName=Datum'" \ - -o /datum-controller-manager cmd/datum-controller-manager/main.go - -FROM gcr.io/distroless/base-debian11 -ARG cmd - -WORKDIR / - -COPY --from=build-stage /datum-controller-manager /datum-controller-manager - -ENTRYPOINT ["/datum-controller-manager"] diff --git a/cmd/datum-controller-manager/app/controllermanager.go b/cmd/datum-controller-manager/app/controllermanager.go deleted file mode 100644 index 93ca1a3..0000000 --- a/cmd/datum-controller-manager/app/controllermanager.go +++ /dev/null @@ -1,765 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/v1.31.3/cmd/kube-controller-manager/app/controllermanager.go -// Provenance-includes-license: Apache-2.0 -// Provenance-includes-copyright: The Kubernetes Authors. -package app - -import ( - "context" - "fmt" - "math/rand" - "net/http" - "os" - "sort" - "time" - - "github.com/blang/semver/v4" - "github.com/spf13/cobra" - coordinationv1 "k8s.io/api/coordination/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/uuid" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/apiserver/pkg/server/healthz" - "k8s.io/apiserver/pkg/server/mux" - utilfeature "k8s.io/apiserver/pkg/util/feature" - cacheddiscovery "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/informers" - v1core "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/metadata" - "k8s.io/client-go/metadata/metadatainformer" - restclient "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - "k8s.io/client-go/tools/leaderelection" - "k8s.io/client-go/tools/leaderelection/resourcelock" - certutil "k8s.io/client-go/util/cert" - cliflag "k8s.io/component-base/cli/flag" - "k8s.io/component-base/cli/globalflag" - "k8s.io/component-base/configz" - "k8s.io/component-base/featuregate" - "k8s.io/component-base/logs" - logsapi "k8s.io/component-base/logs/api/v1" - metricsfeatures "k8s.io/component-base/metrics/features" - controllersmetrics "k8s.io/component-base/metrics/prometheus/controllers" - "k8s.io/component-base/metrics/prometheus/slis" - "k8s.io/component-base/term" - "k8s.io/component-base/version" - utilversion "k8s.io/component-base/version" - "k8s.io/component-base/version/verflag" - genericcontrollermanager "k8s.io/controller-manager/app" - "k8s.io/controller-manager/controller" - "k8s.io/controller-manager/pkg/clientbuilder" - controllerhealthz "k8s.io/controller-manager/pkg/healthz" - "k8s.io/controller-manager/pkg/informerfactory" - "k8s.io/controller-manager/pkg/leadermigration" - "k8s.io/klog/v2" - "k8s.io/kubernetes/cmd/kube-controller-manager/app/config" - "k8s.io/kubernetes/cmd/kube-controller-manager/app/options" - "k8s.io/kubernetes/cmd/kube-controller-manager/names" - kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config" - garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector" - kubefeatures "k8s.io/kubernetes/pkg/features" -) - -func init() { - utilruntime.Must(logsapi.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) - utilruntime.Must(metricsfeatures.AddFeatureGates(utilfeature.DefaultMutableFeatureGate)) -} - -const ( - // ControllerStartJitter is the Jitter used when starting controller managers - ControllerStartJitter = 1.0 - // ConfigzName is the name used for registering datum-controller-manager /configz. - ConfigzName = "datumcontrollermanager.config.k8s.io" -) - -// ControllerLoopMode is the datum-controller-manager's mode of running controller loops that are cloud provider dependent -type ControllerLoopMode int - -const ( - // IncludeCloudLoops means the datum-controller-manager include the controller loops that are cloud provider dependent - IncludeCloudLoops ControllerLoopMode = iota - // ExternalLoops means the datum-controller-manager exclude the controller loops that are cloud provider dependent - ExternalLoops -) - -// NewCommand creates a *cobra.Command object with default parameters -func NewCommand() *cobra.Command { - _, _ = featuregate.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister( - featuregate.DefaultKubeComponent, utilversion.DefaultBuildEffectiveVersion(), utilfeature.DefaultMutableFeatureGate) - - s, err := options.NewKubeControllerManagerOptions() - if err != nil { - klog.Background().Error(err, "Unable to initialize command options") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - s.Generic.LeaderElection.ResourceName = "datum-controller-manager" - s.Generic.LeaderElection.ResourceNamespace = "datum-system" - - cmd := &cobra.Command{ - Use: "datum-controller-manager", - Long: `TODO`, - PersistentPreRunE: func(*cobra.Command, []string) error { - // silence client-go warnings. - // datum-controller-manager generically watches APIs (including deprecated ones), - // and CI ensures it works properly against matching kube-apiserver versions. - restclient.SetDefaultWarningHandler(restclient.NoWarnings{}) - // makes sure feature gates are set before RunE. - return s.ComponentGlobalsRegistry.Set() - }, - RunE: func(cmd *cobra.Command, args []string) error { - verflag.PrintAndExitIfRequested() - - // Activate logging as soon as possible, after that - // show flags with the final logging configuration. - if err := logsapi.ValidateAndApply(s.Logs, utilfeature.DefaultFeatureGate); err != nil { - return err - } - cliflag.PrintFlags(cmd.Flags()) - - c, err := s.Config(KnownControllers(), nil, ControllerAliases()) - if err != nil { - return err - } - - // add feature enablement metrics - fg := s.ComponentGlobalsRegistry.FeatureGateFor(featuregate.DefaultKubeComponent) - fg.(featuregate.MutableFeatureGate).AddMetrics() - return Run(context.Background(), c.Complete()) - }, - Args: func(cmd *cobra.Command, args []string) error { - for _, arg := range args { - if len(arg) > 0 { - return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args) - } - } - return nil - }, - } - - fs := cmd.Flags() - var namedFlagSets cliflag.NamedFlagSets - s.Generic.AddFlags(&namedFlagSets, KnownControllers(), nil, ControllerAliases()) - s.SecureServing.AddFlags(namedFlagSets.FlagSet("secure serving")) - - // Are these needed? - s.Authentication.AddFlags(namedFlagSets.FlagSet("authentication")) - s.Authorization.AddFlags(namedFlagSets.FlagSet("authorization")) - // s.CSRSigningController.AddFlags(fss.FlagSet(names.CertificateSigningRequestSigningController)) - s.GarbageCollectorController.AddFlags(namedFlagSets.FlagSet(names.GarbageCollectorController)) - s.NamespaceController.AddFlags(namedFlagSets.FlagSet(names.NamespaceController)) - s.ResourceQuotaController.AddFlags(namedFlagSets.FlagSet(names.ResourceQuotaController)) - s.ValidatingAdmissionPolicyStatusController.AddFlags(namedFlagSets.FlagSet(names.ValidatingAdmissionPolicyStatusController)) - s.Metrics.AddFlags(namedFlagSets.FlagSet("metrics")) - logsapi.AddFlags(s.Logs, namedFlagSets.FlagSet("logs")) - - // Are these needed? - fs.StringVar(&s.Master, "master", s.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).") - fs.StringVar(&s.Generic.ClientConnection.Kubeconfig, "kubeconfig", s.Generic.ClientConnection.Kubeconfig, "Path to kubeconfig file with authorization and master location information (the master location can be overridden by the master flag).") - - verflag.AddFlags(namedFlagSets.FlagSet("global")) - globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags()) - for _, f := range namedFlagSets.FlagSets { - fs.AddFlagSet(f) - } - - cols, _, _ := term.TerminalSize(cmd.OutOrStdout()) - cliflag.SetUsageAndHelpFunc(cmd, namedFlagSets, cols) - - return cmd -} - -// ResyncPeriod returns a function which generates a duration each time it is -// invoked; this is so that multiple controllers don't get into lock-step and all -// hammer the apiserver with list requests simultaneously. -func ResyncPeriod(c *config.CompletedConfig) func() time.Duration { - return func() time.Duration { - factor := rand.Float64() + 1 - return time.Duration(float64(c.ComponentConfig.Generic.MinResyncPeriod.Nanoseconds()) * factor) - } -} - -// Run runs the KubeControllerManagerOptions. -func Run(ctx context.Context, c *config.CompletedConfig) error { - logger := klog.FromContext(ctx) - stopCh := ctx.Done() - - // To help debugging, immediately log version - logger.Info("Starting", "version", version.Get()) - - logger.Info("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK")) - - // Start events processing pipeline. - c.EventBroadcaster.StartStructuredLogging(0) - c.EventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: c.Client.CoreV1().Events("")}) - defer c.EventBroadcaster.Shutdown() - - if cfgz, err := configz.New(ConfigzName); err == nil { - cfgz.Set(c.ComponentConfig) - } else { - logger.Error(err, "Unable to register configz") - } - - // Setup any healthz checks we will want to use. - var checks []healthz.HealthChecker - var electionChecker *leaderelection.HealthzAdaptor - if c.ComponentConfig.Generic.LeaderElection.LeaderElect { - electionChecker = leaderelection.NewLeaderHealthzAdaptor(time.Second * 20) - checks = append(checks, electionChecker) - } - healthzHandler := controllerhealthz.NewMutableHealthzHandler(checks...) - - // Start the controller manager HTTP server - // unsecuredMux is the handler for these controller *after* authn/authz filters have been applied - var unsecuredMux *mux.PathRecorderMux - if c.SecureServing != nil { - unsecuredMux = genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, healthzHandler) - slis.SLIMetricsWithReset{}.Install(unsecuredMux) - - handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, &c.Authorization, &c.Authentication) - // TODO: handle stoppedCh and listenerStoppedCh returned by c.SecureServing.Serve - if _, _, err := c.SecureServing.Serve(handler, 0, stopCh); err != nil { - return err - } - } - - clientBuilder, rootClientBuilder := createClientBuilders(logger, c) - - run := func(ctx context.Context, controllerDescriptors map[string]*ControllerDescriptor) { - controllerContext, err := CreateControllerContext(ctx, c, rootClientBuilder, clientBuilder) - if err != nil { - logger.Error(err, "Error building controller context") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - - if err := StartControllers(ctx, controllerContext, controllerDescriptors, unsecuredMux, healthzHandler); err != nil { - logger.Error(err, "Error starting controllers") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - - controllerContext.InformerFactory.Start(stopCh) - controllerContext.ObjectOrMetadataInformerFactory.Start(stopCh) - close(controllerContext.InformersStarted) - - <-ctx.Done() - } - - // No leader election, run directly - if !c.ComponentConfig.Generic.LeaderElection.LeaderElect { - controllerDescriptors := NewControllerDescriptors() - run(ctx, controllerDescriptors) - return nil - } - - id, err := os.Hostname() - if err != nil { - return err - } - - // add a uniquifier so that two processes on the same host don't accidentally both become active - id = id + "_" + string(uuid.NewUUID()) - - // leaderMigrator will be non-nil if and only if Leader Migration is enabled. - var leaderMigrator *leadermigration.LeaderMigrator = nil - - // If leader migration is enabled, create the LeaderMigrator and prepare for migration - if leadermigration.Enabled(&c.ComponentConfig.Generic) { - logger.Info("starting leader migration") - - leaderMigrator = leadermigration.NewLeaderMigrator(&c.ComponentConfig.Generic.LeaderMigration, - "datum-controller-manager") - } - - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection) { - binaryVersion, err := semver.ParseTolerant(featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent).BinaryVersion().String()) - if err != nil { - return err - } - emulationVersion, err := semver.ParseTolerant(featuregate.DefaultComponentGlobalsRegistry.EffectiveVersionFor(featuregate.DefaultKubeComponent).EmulationVersion().String()) - if err != nil { - return err - } - - // Start lease candidate controller for coordinated leader election - leaseCandidate, waitForSync, err := leaderelection.NewCandidate( - c.Client, - "datum-system", - id, - "datum-controller-manager", - binaryVersion.FinalizeVersion(), - emulationVersion.FinalizeVersion(), - coordinationv1.OldestEmulationVersion, - ) - if err != nil { - return err - } - healthzHandler.AddHealthChecker(healthz.NewInformerSyncHealthz(waitForSync)) - - go leaseCandidate.Run(ctx) - } - - // Start the main lock - go leaderElectAndRun(ctx, c, id, electionChecker, - c.ComponentConfig.Generic.LeaderElection.ResourceLock, - c.ComponentConfig.Generic.LeaderElection.ResourceName, - leaderelection.LeaderCallbacks{ - OnStartedLeading: func(ctx context.Context) { - controllerDescriptors := NewControllerDescriptors() - if leaderMigrator != nil { - // If leader migration is enabled, we should start only non-migrated controllers - // for the main lock. - controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerNonMigrated) - logger.Info("leader migration: starting main controllers.") - } - run(ctx, controllerDescriptors) - }, - OnStoppedLeading: func() { - logger.Error(nil, "leaderelection lost") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - }, - }) - - // If Leader Migration is enabled, proceed to attempt the migration lock. - if leaderMigrator != nil { - // Wait for Service Account Token Controller to start before acquiring the migration lock. - // At this point, the main lock must have already been acquired, or the KCM process already exited. - // We wait for the main lock before acquiring the migration lock to prevent the situation - // where KCM instance A holds the main lock while KCM instance B holds the migration lock. - <-leaderMigrator.MigrationReady - - // Start the migration lock. - go leaderElectAndRun(ctx, c, id, electionChecker, - c.ComponentConfig.Generic.LeaderMigration.ResourceLock, - c.ComponentConfig.Generic.LeaderMigration.LeaderName, - leaderelection.LeaderCallbacks{ - OnStartedLeading: func(ctx context.Context) { - logger.Info("leader migration: starting migrated controllers.") - controllerDescriptors := NewControllerDescriptors() - controllerDescriptors = filteredControllerDescriptors(controllerDescriptors, leaderMigrator.FilterFunc, leadermigration.ControllerMigrated) - // DO NOT start saTokenController under migration lock - delete(controllerDescriptors, names.ServiceAccountTokenController) - run(ctx, controllerDescriptors) - }, - OnStoppedLeading: func() { - logger.Error(nil, "migration leaderelection lost") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - }, - }) - } - - <-stopCh - return nil -} - -// ControllerContext defines the context object for controller -type ControllerContext struct { - // ClientBuilder will provide a client for this controller to use - ClientBuilder clientbuilder.ControllerClientBuilder - - // InformerFactory gives access to informers for the controller. - InformerFactory informers.SharedInformerFactory - - // ObjectOrMetadataInformerFactory gives access to informers for typed resources - // and dynamic resources by their metadata. All generic controllers currently use - // object metadata - if a future controller needs access to the full object this - // would become GenericInformerFactory and take a dynamic client. - ObjectOrMetadataInformerFactory informerfactory.InformerFactory - - // ComponentConfig provides access to init options for a given controller - ComponentConfig kubectrlmgrconfig.KubeControllerManagerConfiguration - - // DeferredDiscoveryRESTMapper is a RESTMapper that will defer - // initialization of the RESTMapper until the first mapping is - // requested. - RESTMapper *restmapper.DeferredDiscoveryRESTMapper - - // InformersStarted is closed after all of the controllers have been initialized and are running. After this point it is safe, - // for an individual controller to start the shared informers. Before it is closed, they should not. - InformersStarted chan struct{} - - // ResyncPeriod generates a duration each time it is invoked; this is so that - // multiple controllers don't get into lock-step and all hammer the apiserver - // with list requests simultaneously. - ResyncPeriod func() time.Duration - - // ControllerManagerMetrics provides a proxy to set controller manager specific metrics. - ControllerManagerMetrics *controllersmetrics.ControllerManagerMetrics - - // GraphBuilder gives an access to dependencyGraphBuilder which keeps tracks of resources in the cluster - GraphBuilder *garbagecollector.GraphBuilder -} - -// IsControllerEnabled checks if the context's controllers enabled or not -func (c ControllerContext) IsControllerEnabled(controllerDescriptor *ControllerDescriptor) bool { - controllersDisabledByDefault := sets.NewString() - if controllerDescriptor.IsDisabledByDefault() { - controllersDisabledByDefault.Insert(controllerDescriptor.Name()) - } - return genericcontrollermanager.IsControllerEnabled(controllerDescriptor.Name(), controllersDisabledByDefault, c.ComponentConfig.Generic.Controllers) -} - -// InitFunc is used to launch a particular controller. It returns a controller -// that can optionally implement other interfaces so that the controller manager -// can support the requested features. -// The returned controller may be nil, which will be considered an anonymous controller -// that requests no additional features from the controller manager. -// Any error returned will cause the controller process to `Fatal` -// The bool indicates whether the controller was enabled. -type InitFunc func(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller controller.Interface, enabled bool, err error) - -type ControllerDescriptor struct { - name string - initFunc InitFunc - requiredFeatureGates []featuregate.Feature - aliases []string - isDisabledByDefault bool - isCloudProviderController bool - requiresSpecialHandling bool -} - -func (r *ControllerDescriptor) Name() string { - return r.name -} - -func (r *ControllerDescriptor) GetInitFunc() InitFunc { - return r.initFunc -} - -func (r *ControllerDescriptor) GetRequiredFeatureGates() []featuregate.Feature { - return append([]featuregate.Feature(nil), r.requiredFeatureGates...) -} - -// GetAliases returns aliases to ensure backwards compatibility and should never be removed! -// Only addition of new aliases is allowed, and only when a canonical name is changed (please see CHANGE POLICY of controller names) -func (r *ControllerDescriptor) GetAliases() []string { - return append([]string(nil), r.aliases...) -} - -func (r *ControllerDescriptor) IsDisabledByDefault() bool { - return r.isDisabledByDefault -} - -// KnownControllers returns all known controllers's name -func KnownControllers() []string { - return sets.StringKeySet(NewControllerDescriptors()).List() -} - -// ControllerAliases returns a mapping of aliases to canonical controller names -func ControllerAliases() map[string]string { - aliases := map[string]string{} - for name, c := range NewControllerDescriptors() { - for _, alias := range c.GetAliases() { - aliases[alias] = name - } - } - return aliases -} - -func ControllersDisabledByDefault() []string { - var controllersDisabledByDefault []string - - for name, c := range NewControllerDescriptors() { - if c.IsDisabledByDefault() { - controllersDisabledByDefault = append(controllersDisabledByDefault, name) - } - } - - sort.Strings(controllersDisabledByDefault) - - return controllersDisabledByDefault -} - -// NewControllerDescriptors is a public map of named controller groups (you can start more than one in an init func) -// paired to their ControllerDescriptor wrapper object that includes InitFunc. -// This allows for structured downstream composition and subdivision. -func NewControllerDescriptors() map[string]*ControllerDescriptor { - controllers := map[string]*ControllerDescriptor{} - aliases := sets.NewString() - - // All the controllers must fulfil common constraints, or else we will explode. - register := func(controllerDesc *ControllerDescriptor) { - if controllerDesc == nil { - panic("received nil controller for a registration") - } - name := controllerDesc.Name() - if len(name) == 0 { - panic("received controller without a name for a registration") - } - if _, found := controllers[name]; found { - panic(fmt.Sprintf("controller name %q was registered twice", name)) - } - if controllerDesc.GetInitFunc() == nil { - panic(fmt.Sprintf("controller %q does not have an init function", name)) - } - - for _, alias := range controllerDesc.GetAliases() { - if aliases.Has(alias) { - panic(fmt.Sprintf("controller %q has a duplicate alias %q", name, alias)) - } - aliases.Insert(alias) - } - - controllers[name] = controllerDesc - } - - register(newNamespaceControllerDescriptor()) - register(newGarbageCollectorControllerDescriptor()) - register(newResourceQuotaControllerDescriptor()) - // register(newCertificateSigningRequestSigningControllerDescriptor()) - // register(newCertificateSigningRequestApprovingControllerDescriptor()) - // register(newCertificateSigningRequestCleanerControllerDescriptor()) - - for _, alias := range aliases.UnsortedList() { - if _, ok := controllers[alias]; ok { - panic(fmt.Sprintf("alias %q conflicts with a controller name", alias)) - } - } - - return controllers -} - -// CreateControllerContext creates a context struct containing references to resources needed by the -// controllers such as the cloud provider and clientBuilder. rootClientBuilder is only used for -// the shared-informers client and token controller. -func CreateControllerContext(ctx context.Context, s *config.CompletedConfig, rootClientBuilder, clientBuilder clientbuilder.ControllerClientBuilder) (ControllerContext, error) { - // Informer transform to trim ManagedFields for memory efficiency. - trim := func(obj interface{}) (interface{}, error) { - if accessor, err := meta.Accessor(obj); err == nil { - if accessor.GetManagedFields() != nil { - accessor.SetManagedFields(nil) - } - } - return obj, nil - } - - versionedClient := rootClientBuilder.ClientOrDie("shared-informers") - sharedInformers := informers.NewSharedInformerFactoryWithOptions(versionedClient, ResyncPeriod(s)(), informers.WithTransform(trim)) - - metadataClient := metadata.NewForConfigOrDie(rootClientBuilder.ConfigOrDie("metadata-informers")) - metadataInformers := metadatainformer.NewSharedInformerFactoryWithOptions(metadataClient, ResyncPeriod(s)(), metadatainformer.WithTransform(trim)) - - // If apiserver is not running we should wait for some time and fail only then. This is particularly - // important when we start apiserver and controller manager at the same time. - if err := genericcontrollermanager.WaitForAPIServer(versionedClient, 10*time.Second); err != nil { - return ControllerContext{}, fmt.Errorf("failed to wait for apiserver being healthy: %v", err) - } - - // Use a discovery client capable of being refreshed. - discoveryClient := rootClientBuilder.DiscoveryClientOrDie("controller-discovery") - cachedClient := cacheddiscovery.NewMemCacheClient(discoveryClient) - restMapper := restmapper.NewDeferredDiscoveryRESTMapper(cachedClient) - go wait.Until(func() { - restMapper.Reset() - }, 30*time.Second, ctx.Done()) - - controllerContext := ControllerContext{ - ClientBuilder: clientBuilder, - InformerFactory: sharedInformers, - ObjectOrMetadataInformerFactory: informerfactory.NewInformerFactory(sharedInformers, metadataInformers), - ComponentConfig: s.ComponentConfig, - RESTMapper: restMapper, - InformersStarted: make(chan struct{}), - ResyncPeriod: ResyncPeriod(s), - ControllerManagerMetrics: controllersmetrics.NewControllerManagerMetrics("datum-controller-manager"), - } - - if controllerContext.ComponentConfig.GarbageCollectorController.EnableGarbageCollector && - controllerContext.IsControllerEnabled(NewControllerDescriptors()[names.GarbageCollectorController]) { - ignoredResources := make(map[schema.GroupResource]struct{}) - for _, r := range controllerContext.ComponentConfig.GarbageCollectorController.GCIgnoredResources { - ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct{}{} - } - - controllerContext.GraphBuilder = garbagecollector.NewDependencyGraphBuilder( - ctx, - metadataClient, - controllerContext.RESTMapper, - ignoredResources, - controllerContext.ObjectOrMetadataInformerFactory, - controllerContext.InformersStarted, - ) - } - - controllersmetrics.Register() - return controllerContext, nil -} - -// StartControllers starts a set of controllers with a specified ControllerContext -func StartControllers(ctx context.Context, controllerCtx ControllerContext, controllerDescriptors map[string]*ControllerDescriptor, - unsecuredMux *mux.PathRecorderMux, healthzHandler *controllerhealthz.MutableHealthzHandler) error { - var controllerChecks []healthz.HealthChecker - - // Always start the SA token controller first using a full-power client, since it needs to mint tokens for the rest - // If this fails, just return here and fail since other controllers won't be able to get credentials. - if serviceAccountTokenControllerDescriptor, ok := controllerDescriptors[names.ServiceAccountTokenController]; ok { - check, err := StartController(ctx, controllerCtx, serviceAccountTokenControllerDescriptor, unsecuredMux) - if err != nil { - return err - } - if check != nil { - // HealthChecker should be present when controller has started - controllerChecks = append(controllerChecks, check) - } - } - - // Each controller is passed a context where the logger has the name of - // the controller set through WithName. That name then becomes the prefix of - // of all log messages emitted by that controller. - // - // In StartController, an explicit "controller" key is used instead, for two reasons: - // - while contextual logging is alpha, klog.LoggerWithName is still a no-op, - // so we cannot rely on it yet to add the name - // - it allows distinguishing between log entries emitted by the controller - // and those emitted for it - this is a bit debatable and could be revised. - for _, controllerDesc := range controllerDescriptors { - - check, err := StartController(ctx, controllerCtx, controllerDesc, unsecuredMux) - if err != nil { - return err - } - if check != nil { - // HealthChecker should be present when controller has started - controllerChecks = append(controllerChecks, check) - } - } - - healthzHandler.AddHealthChecker(controllerChecks...) - - return nil -} - -// StartController starts a controller with a specified ControllerContext -// and performs required pre- and post- checks/actions -func StartController(ctx context.Context, controllerCtx ControllerContext, controllerDescriptor *ControllerDescriptor, - unsecuredMux *mux.PathRecorderMux) (healthz.HealthChecker, error) { - logger := klog.FromContext(ctx) - controllerName := controllerDescriptor.Name() - - for _, featureGate := range controllerDescriptor.GetRequiredFeatureGates() { - if !utilfeature.DefaultFeatureGate.Enabled(featureGate) { - logger.Info("Controller is disabled by a feature gate", "controller", controllerName, "requiredFeatureGates", controllerDescriptor.GetRequiredFeatureGates()) - return nil, nil - } - } - - if !controllerCtx.IsControllerEnabled(controllerDescriptor) { - logger.Info("Warning: controller is disabled", "controller", controllerName) - return nil, nil - } - - time.Sleep(wait.Jitter(controllerCtx.ComponentConfig.Generic.ControllerStartInterval.Duration, ControllerStartJitter)) - - logger.V(1).Info("Starting controller", "controller", controllerName) - - initFunc := controllerDescriptor.GetInitFunc() - ctrl, started, err := initFunc(klog.NewContext(ctx, klog.LoggerWithName(logger, controllerName)), controllerCtx, controllerName) - if err != nil { - logger.Error(err, "Error starting controller", "controller", controllerName) - return nil, err - } - if !started { - logger.Info("Warning: skipping controller", "controller", controllerName) - return nil, nil - } - - check := controllerhealthz.NamedPingChecker(controllerName) - if ctrl != nil { - // check if the controller supports and requests a debugHandler - // and it needs the unsecuredMux to mount the handler onto. - if debuggable, ok := ctrl.(controller.Debuggable); ok && unsecuredMux != nil { - if debugHandler := debuggable.DebuggingHandler(); debugHandler != nil { - basePath := "/debug/controllers/" + controllerName - unsecuredMux.UnlistedHandle(basePath, http.StripPrefix(basePath, debugHandler)) - unsecuredMux.UnlistedHandlePrefix(basePath+"/", http.StripPrefix(basePath, debugHandler)) - } - } - if healthCheckable, ok := ctrl.(controller.HealthCheckable); ok { - if realCheck := healthCheckable.HealthChecker(); realCheck != nil { - check = controllerhealthz.NamedHealthChecker(controllerName, realCheck) - } - } - } - - logger.Info("Started controller", "controller", controllerName) - return check, nil -} - -func readCA(file string) ([]byte, error) { - rootCA, err := os.ReadFile(file) - if err != nil { - return nil, err - } - if _, err := certutil.ParseCertsPEM(rootCA); err != nil { - return nil, err - } - - return rootCA, err -} - -// createClientBuilders creates clientBuilder and rootClientBuilder from the given configuration -func createClientBuilders(logger klog.Logger, c *config.CompletedConfig) (clientBuilder clientbuilder.ControllerClientBuilder, rootClientBuilder clientbuilder.ControllerClientBuilder) { - rootClientBuilder = clientbuilder.SimpleControllerClientBuilder{ - ClientConfig: c.Kubeconfig, - } - if c.ComponentConfig.KubeCloudShared.UseServiceAccountCredentials { - if len(c.ComponentConfig.SAController.ServiceAccountKeyFile) == 0 { - // It's possible another controller process is creating the tokens for us. - // If one isn't, we'll timeout and exit when our client builder is unable to create the tokens. - logger.Info("Warning: --use-service-account-credentials was specified without providing a --service-account-private-key-file") - } - - clientBuilder = clientbuilder.NewDynamicClientBuilder( - restclient.AnonymousClientConfig(c.Kubeconfig), - c.Client.CoreV1(), - metav1.NamespaceSystem) - } else { - clientBuilder = rootClientBuilder - } - return -} - -// leaderElectAndRun runs the leader election, and runs the callbacks once the leader lease is acquired. -// TODO: extract this function into staging/controller-manager -func leaderElectAndRun(ctx context.Context, c *config.CompletedConfig, lockIdentity string, electionChecker *leaderelection.HealthzAdaptor, resourceLock string, leaseName string, callbacks leaderelection.LeaderCallbacks) { - logger := klog.FromContext(ctx) - rl, err := resourcelock.NewFromKubeconfig(resourceLock, - c.ComponentConfig.Generic.LeaderElection.ResourceNamespace, - leaseName, - resourcelock.ResourceLockConfig{ - Identity: lockIdentity, - EventRecorder: c.EventRecorder, - }, - c.Kubeconfig, - c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration) - if err != nil { - logger.Error(err, "Error creating lock") - klog.FlushAndExit(klog.ExitFlushTimeout, 1) - } - - leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ - Lock: rl, - LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration, - RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration, - RetryPeriod: c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration, - Callbacks: callbacks, - WatchDog: electionChecker, - Name: leaseName, - Coordinated: utilfeature.DefaultFeatureGate.Enabled(kubefeatures.CoordinatedLeaderElection), - }) - - panic("unreachable") -} - -// filteredControllerDescriptors returns all controllerDescriptors after filtering through filterFunc. -func filteredControllerDescriptors(controllerDescriptors map[string]*ControllerDescriptor, filterFunc leadermigration.FilterFunc, expected leadermigration.FilterResult) map[string]*ControllerDescriptor { - resultControllers := make(map[string]*ControllerDescriptor) - for name, controllerDesc := range controllerDescriptors { - if filterFunc(name) == expected { - resultControllers[name] = controllerDesc - } - } - return resultControllers -} diff --git a/cmd/datum-controller-manager/app/core.go b/cmd/datum-controller-manager/app/core.go deleted file mode 100644 index 8e4556d..0000000 --- a/cmd/datum-controller-manager/app/core.go +++ /dev/null @@ -1,155 +0,0 @@ -package app - -import ( - "context" - "fmt" - "time" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/quota/v1/generic" - clientset "k8s.io/client-go/kubernetes" - "k8s.io/client-go/metadata" - restclient "k8s.io/client-go/rest" - "k8s.io/controller-manager/controller" - "k8s.io/kubernetes/cmd/kube-controller-manager/names" - pkgcontroller "k8s.io/kubernetes/pkg/controller" - garbagecollector "k8s.io/kubernetes/pkg/controller/garbagecollector" - namespacecontroller "k8s.io/kubernetes/pkg/controller/namespace" - resourcequotacontroller "k8s.io/kubernetes/pkg/controller/resourcequota" - quotainstall "k8s.io/kubernetes/pkg/quota/v1/install" -) - -func newNamespaceControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.NamespaceController, - aliases: []string{"namespace"}, - initFunc: startNamespaceController, - } -} - -func startNamespaceController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) { - // the namespace cleanup controller is very chatty. It makes lots of discovery calls and then it makes lots of delete calls - // the ratelimiter negatively affects its speed. Deleting 100 total items in a namespace (that's only a few of each resource - // including events), takes ~10 seconds by default. - nsKubeconfig := controllerContext.ClientBuilder.ConfigOrDie("namespace-controller") - nsKubeconfig.QPS *= 20 - nsKubeconfig.Burst *= 100 - namespaceKubeClient := clientset.NewForConfigOrDie(nsKubeconfig) - return startModifiedNamespaceController(ctx, controllerContext, namespaceKubeClient, nsKubeconfig) -} - -func startModifiedNamespaceController(ctx context.Context, controllerContext ControllerContext, namespaceKubeClient clientset.Interface, nsKubeconfig *restclient.Config) (controller.Interface, bool, error) { - - metadataClient, err := metadata.NewForConfig(nsKubeconfig) - if err != nil { - return nil, true, err - } - - discoverResourcesFn := namespaceKubeClient.Discovery().ServerPreferredNamespacedResources - - namespaceController := namespacecontroller.NewNamespaceController( - ctx, - namespaceKubeClient, - metadataClient, - discoverResourcesFn, - controllerContext.InformerFactory.Core().V1().Namespaces(), - controllerContext.ComponentConfig.NamespaceController.NamespaceSyncPeriod.Duration, - v1.FinalizerKubernetes, - ) - go namespaceController.Run(ctx, int(controllerContext.ComponentConfig.NamespaceController.ConcurrentNamespaceSyncs)) - - return nil, true, nil -} - -func newGarbageCollectorControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.GarbageCollectorController, - aliases: []string{"garbagecollector"}, - initFunc: startGarbageCollectorController, - } -} - -func startGarbageCollectorController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) { - if !controllerContext.ComponentConfig.GarbageCollectorController.EnableGarbageCollector { - return nil, false, nil - } - - gcClientset := controllerContext.ClientBuilder.ClientOrDie("generic-garbage-collector") - discoveryClient := controllerContext.ClientBuilder.DiscoveryClientOrDie("generic-garbage-collector") - - config := controllerContext.ClientBuilder.ConfigOrDie("generic-garbage-collector") - // Increase garbage collector controller's throughput: each object deletion takes two API calls, - // so to get |config.QPS| deletion rate we need to allow 2x more requests for this controller. - config.QPS *= 2 - metadataClient, err := metadata.NewForConfig(config) - if err != nil { - return nil, true, err - } - - ignoredResources := make(map[schema.GroupResource]struct{}) - for _, r := range controllerContext.ComponentConfig.GarbageCollectorController.GCIgnoredResources { - ignoredResources[schema.GroupResource{Group: r.Group, Resource: r.Resource}] = struct{}{} - } - - garbageCollector, err := garbagecollector.NewComposedGarbageCollector( - ctx, - gcClientset, - metadataClient, - controllerContext.RESTMapper, - controllerContext.GraphBuilder, - ) - if err != nil { - return nil, true, fmt.Errorf("failed to start the generic garbage collector: %w", err) - } - - // Start the garbage collector. - workers := int(controllerContext.ComponentConfig.GarbageCollectorController.ConcurrentGCSyncs) - const syncPeriod = 30 * time.Second - go garbageCollector.Run(ctx, workers, syncPeriod) - - // Periodically refresh the RESTMapper with new discovery information and sync - // the garbage collector. - go garbageCollector.Sync(ctx, discoveryClient, 30*time.Second) - - return garbageCollector, true, nil -} - -func newResourceQuotaControllerDescriptor() *ControllerDescriptor { - return &ControllerDescriptor{ - name: names.ResourceQuotaController, - aliases: []string{"resourcequota"}, - initFunc: startResourceQuotaController, - } -} - -func startResourceQuotaController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) { - resourceQuotaControllerClient := controllerContext.ClientBuilder.ClientOrDie("resourcequota-controller") - resourceQuotaControllerDiscoveryClient := controllerContext.ClientBuilder.DiscoveryClientOrDie("resourcequota-controller") - discoveryFunc := resourceQuotaControllerDiscoveryClient.ServerPreferredNamespacedResources - listerFuncForResource := generic.ListerFuncForResourceFunc(controllerContext.InformerFactory.ForResource) - quotaConfiguration := quotainstall.NewQuotaConfigurationForControllers(listerFuncForResource) - - resourceQuotaControllerOptions := &resourcequotacontroller.ControllerOptions{ - QuotaClient: resourceQuotaControllerClient.CoreV1(), - ResourceQuotaInformer: controllerContext.InformerFactory.Core().V1().ResourceQuotas(), - ResyncPeriod: pkgcontroller.StaticResyncPeriodFunc(controllerContext.ComponentConfig.ResourceQuotaController.ResourceQuotaSyncPeriod.Duration), - InformerFactory: controllerContext.ObjectOrMetadataInformerFactory, - ReplenishmentResyncPeriod: controllerContext.ResyncPeriod, - DiscoveryFunc: discoveryFunc, - IgnoredResourcesFunc: quotaConfiguration.IgnoredResources, - InformersStarted: controllerContext.InformersStarted, - Registry: generic.NewRegistry(quotaConfiguration.Evaluators()), - UpdateFilter: quotainstall.DefaultUpdateFilter(), - } - resourceQuotaController, err := resourcequotacontroller.NewController(ctx, resourceQuotaControllerOptions) - if err != nil { - return nil, false, err - } - go resourceQuotaController.Run(ctx, int(controllerContext.ComponentConfig.ResourceQuotaController.ConcurrentResourceQuotaSyncs)) - - // Periodically the quota controller to detect new resource types - go resourceQuotaController.Sync(ctx, discoveryFunc, 30*time.Second) - - return nil, true, nil -} diff --git a/cmd/datum-controller-manager/main.go b/cmd/datum-controller-manager/main.go deleted file mode 100644 index f63d60a..0000000 --- a/cmd/datum-controller-manager/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "os" - - "k8s.io/component-base/cli" - - "go.datumapis.com/datum/cmd/datum-controller-manager/app" -) - -func main() { - command := app.NewCommand() - code := cli.Run(command) - os.Exit(code) -} diff --git a/config/apiserver/deployment.yaml b/config/apiserver/deployment.yaml deleted file mode 100644 index b423584..0000000 --- a/config/apiserver/deployment.yaml +++ /dev/null @@ -1,68 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: datum-apiserver -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: datum-apiserver - app.kubernetes.io/part-of: datum-control-plane - strategy: - rollingUpdate: - maxSurge: 25% - maxUnavailable: 25% - type: RollingUpdate - template: - metadata: - labels: - app.kubernetes.io/name: datum-apiserver - app.kubernetes.io/part-of: datum-control-plane - spec: - automountServiceAccountToken: false - containers: - - image: us-east4-docker.pkg.dev/datum-cloud-prod/datum-internal-images/datum-apiserver:v0.0.1-v1alpha12-amd64 - imagePullPolicy: IfNotPresent - livenessProbe: - failureThreshold: 3 - httpGet: - path: /livez - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - name: datum-apiserver - ports: - - containerPort: 6443 - name: https - protocol: TCP - readinessProbe: - failureThreshold: 3 - httpGet: - path: /readyz - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - resources: {} - startupProbe: - failureThreshold: 3 - httpGet: - path: /livez - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 15 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - dnsPolicy: ClusterFirst - restartPolicy: Always - schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/config/apiserver/httpproxy.yaml b/config/apiserver/httpproxy.yaml deleted file mode 100644 index 055f70e..0000000 --- a/config/apiserver/httpproxy.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: projectcontour.io/v1 -kind: HTTPProxy -metadata: - name: datum-apiserver - labels: - app: datum-apiserver -spec: - ingressClassName: contour - routes: - - conditions: - - prefix: /apis/resourcemanager.datumapis.com/v1alpha/projects/ - pathRewritePolicy: - replacePrefix: - - replacement: / - services: - - name: datum-apiserver - port: 6443 - protocol: tls - timeoutPolicy: - response: infinity \ No newline at end of file diff --git a/config/apiserver/kustomization.yaml b/config/apiserver/kustomization.yaml deleted file mode 100644 index d7e333c..0000000 --- a/config/apiserver/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - deployment.yaml - - service.yaml - - httpproxy.yaml \ No newline at end of file diff --git a/config/apiserver/service.yaml b/config/apiserver/service.yaml deleted file mode 100644 index 1edb115..0000000 --- a/config/apiserver/service.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: datum-apiserver - labels: - app: datum-apiserver -spec: - type: ClusterIP - selector: - app.kubernetes.io/name: datum-apiserver - ports: - - name: https - port: 6443 - protocol: TCP - targetPort: https diff --git a/config/controller-manager/deployment.yaml b/config/controller-manager/deployment.yaml deleted file mode 100644 index 07f857b..0000000 --- a/config/controller-manager/deployment.yaml +++ /dev/null @@ -1,52 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: datum-controller-manager -spec: - replicas: 1 - selector: - matchLabels: - app.kubernetes.io/name: datum-controller-manager - app.kubernetes.io/part-of: datum-control-plane - strategy: - rollingUpdate: - maxSurge: 25% - maxUnavailable: 25% - type: RollingUpdate - template: - metadata: - labels: - app.kubernetes.io/name: datum-controller-manager - app.kubernetes.io/part-of: datum-control-plane - spec: - automountServiceAccountToken: false - containers: - - name: datum-controller-manager - image: us-east4-docker.pkg.dev/datum-cloud-prod/datum-internal-images/datum-controller-manager:v0.0.1-v1alpha2-amd64 - imagePullPolicy: IfNotPresent - ports: - - containerPort: 6443 - name: https - protocol: TCP - livenessProbe: - httpGet: - path: /healthz - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /healthz - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - startupProbe: - httpGet: - path: /healthz - port: https - scheme: HTTPS - initialDelaySeconds: 10 - periodSeconds: 10 - restartPolicy: Always diff --git a/config/controller-manager/kustomization.yaml b/config/controller-manager/kustomization.yaml deleted file mode 100644 index ceae37d..0000000 --- a/config/controller-manager/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - deployment.yaml \ No newline at end of file diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 62c7bcd..0000000 --- a/docs/README.md +++ /dev/null @@ -1,75 +0,0 @@ -# Kustomize Manifests for Datum APIServer and Controller Manager - -## Overview -This repository provides Kustomize manifests to deploy the Datum APIServer and Datum Controller Manager components. These manifests are structured for ease of use by the community and integration with FluxCD pipelines. - -## Repository Structure -``` -config -├── api-server -│ ├── deployment.yaml -│ ├── httpproxy.yaml -│ ├── kustomization.yaml -│ ├── service.yaml -├── controller-manager -│ ├── deployment.yaml -│ ├── kustomization.yaml -docs -``` - -### API Server -The `api-server` folder contains the Kustomize manifests required to deploy the Datum APIServer, including: -- **deployment.yaml**: Defines the Kubernetes Deployment for the API Server. -- **httpproxy.yaml**: Configuration for HTTP routing (if applicable). -- **kustomization.yaml**: Kustomize configuration for managing API Server resources. -- **service.yaml**: Defines the Kubernetes Service for the API Server. - -### Controller Manager -The `controller-manager` folder contains the Kustomize manifests required to deploy the Datum Controller Manager, including: -- **deployment.yaml**: Defines the Kubernetes Deployment for the Controller Manager. -- **kustomization.yaml**: Kustomize configuration for managing Controller Manager resources. - -## Pushing Manifests using Flux CLI -We utilize `flux push artifact` to publish Kustomize manifests to an OCI repository. - -### Example Workflow -To push the manifests to an OCI registry, use the following command: -```sh -flux push artifact oci://ghcr.io/your-org/datum-kustomize:latest \ - --path=./config --source=your-repository-url -``` - -## GitHub Actions Integration -A GitHub Action is set up to automatically push these manifests upon changes. The workflow is defined as follows: - -```yaml -name: Publish Kustomize Manifests - -on: - push: - branches: - - main - release: - types: [published] - -jobs: - push-kustomize: - runs-on: ubuntu-latest - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Install Flux CLI - run: | - curl -s https://fluxcd.io/install.sh | sudo bash - - - name: Push Manifests - run: | - flux push artifact oci://ghcr.io/your-org/datum-kustomize:latest \ - --path=./config --source=\$(git remote get-url origin) -``` - -This ensures that any updates to the `config` directory are automatically pushed to the OCI registry. - -This setup enables both community users and internal automation (e.g., FluxCD) to deploy the Datum APIServer and Controller Manager efficiently. - diff --git a/go.mod b/go.mod deleted file mode 100644 index 0e2c316..0000000 --- a/go.mod +++ /dev/null @@ -1,140 +0,0 @@ -module go.datumapis.com/datum - -go 1.23.1 - -require ( - buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20250214190629-505d1ed65f22.2 - buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.36.1-20250214190629-505d1ed65f22.1 - github.com/blang/semver/v4 v4.0.0 - github.com/google/go-cmp v0.7.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.6 - github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 - go.opentelemetry.io/otel v1.35.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/trace v1.35.0 - google.golang.org/grpc v1.71.0 - gopkg.in/square/go-jose.v2 v2.6.0 - k8s.io/api v0.32.0 - k8s.io/apiextensions-apiserver v0.32.0 - k8s.io/apimachinery v0.32.0 - k8s.io/apiserver v0.32.0 - k8s.io/client-go v0.32.0 - k8s.io/component-base v0.32.0 - k8s.io/controller-manager v0.32.0 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-aggregator v0.32.0 - k8s.io/kubernetes v1.32.0 - sigs.k8s.io/controller-runtime v0.19.3 -) - -require ( - buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go v1.36.1-20250214190629-249284998450.1 // indirect - cel.dev/expr v0.19.1 // indirect - cloud.google.com/go/longrunning v0.6.3 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/coreos/go-oidc v2.2.1+incompatible // indirect - github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/cel-go v0.22.0 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/moby/spdystream v0.5.0 // indirect - github.com/moby/sys/mountinfo v0.7.2 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/selinux v1.11.1 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pquerna/cachecontrol v0.1.0 // indirect - github.com/prometheus/client_golang v1.19.1 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - go.etcd.io/etcd/api/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.16 // indirect - go.etcd.io/etcd/client/v3 v3.5.16 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.26.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/cloud-provider v0.32.0 // indirect - k8s.io/cluster-bootstrap v0.32.0 // indirect - k8s.io/component-helpers v0.32.0 // indirect - k8s.io/cri-client v0.32.0 // indirect - k8s.io/csi-translation-lib v0.32.0 // indirect - k8s.io/dynamic-resource-allocation v0.32.0 // indirect - k8s.io/externaljwt v0.32.0 // indirect - k8s.io/kms v0.32.0 // indirect - k8s.io/kube-controller-manager v0.32.0 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/kubelet v0.32.0 // indirect - k8s.io/mount-utils v0.32.0 // indirect - k8s.io/pod-security-admission v0.32.0 // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 522a77e..0000000 --- a/go.sum +++ /dev/null @@ -1,414 +0,0 @@ -buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go v1.36.1-20250214190629-249284998450.1 h1:+MS/KDeUD04gqRJtbQ73sqTrMy8E8tsda51mkg90SHY= -buf.build/gen/go/datum-cloud/datum-os/protocolbuffers/go v1.36.1-20250214190629-249284998450.1/go.mod h1:oJZ1dK0lOxvkFEYPFqF6fiead/Sm7EXOaajXAgu8L+A= -buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20250214190629-505d1ed65f22.2 h1:suOMS2oFYVUmt7GL1Kf+0Y0QfCZDAN/2DU56G/Qoa5g= -buf.build/gen/go/datum-cloud/iam/grpc/go v1.5.1-20250214190629-505d1ed65f22.2/go.mod h1:kc1ou2fMU2fGegrXTX4Knhiy3VjRVkXVIPedtrSM12I= -buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.36.1-20250214190629-505d1ed65f22.1 h1:JzhUIsVcoNCVtFNC2M0nW0lfk59lvUikfM9u+z1FUUk= -buf.build/gen/go/datum-cloud/iam/protocolbuffers/go v1.36.1-20250214190629-505d1ed65f22.1/go.mod h1:FIvO376l+H+t7E0WYyELtU4QwvxmST7L9gfh+qv5RBQ= -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= -cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= -cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= -cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= -github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= -github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= -github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= -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/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= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= -github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= -github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -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= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= -github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= -github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= -github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= -github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= -github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= -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/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= -go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= -go.etcd.io/etcd/api/v3 v3.5.16 h1:WvmyJVbjWqK4R1E+B12RRHz3bRGy9XVfh++MgbN+6n0= -go.etcd.io/etcd/api/v3 v3.5.16/go.mod h1:1P4SlIP/VwkDmGo3OlOD7faPeP8KDIFhqvciH5EfN28= -go.etcd.io/etcd/client/pkg/v3 v3.5.16 h1:ZgY48uH6UvB+/7R9Yf4x574uCO3jIx0TRDyetSfId3Q= -go.etcd.io/etcd/client/pkg/v3 v3.5.16/go.mod h1:V8acl8pcEK0Y2g19YlOV9m9ssUe6MgiDSobSoaBAM0E= -go.etcd.io/etcd/client/v2 v2.305.16 h1:kQrn9o5czVNaukf2A2At43cE9ZtWauOtf9vRZuiKXow= -go.etcd.io/etcd/client/v2 v2.305.16/go.mod h1:h9YxWCzcdvZENbfzBTFCnoNumr2ax3F19sKMqHFmXHE= -go.etcd.io/etcd/client/v3 v3.5.16 h1:sSmVYOAHeC9doqi0gv7v86oY/BTld0SEFGaxsU9eRhE= -go.etcd.io/etcd/client/v3 v3.5.16/go.mod h1:X+rExSGkyqxvu276cr2OwPLBaeqFu1cIl4vmRjAD/50= -go.etcd.io/etcd/pkg/v3 v3.5.16 h1:cnavs5WSPWeK4TYwPYfmcr3Joz9BH+TZ6qoUtz6/+mc= -go.etcd.io/etcd/pkg/v3 v3.5.16/go.mod h1:+lutCZHG5MBBFI/U4eYT5yL7sJfnexsoM20Y0t2uNuY= -go.etcd.io/etcd/raft/v3 v3.5.16 h1:zBXA3ZUpYs1AwiLGPafYAKKl/CORn/uaxYDwlNwndAk= -go.etcd.io/etcd/raft/v3 v3.5.16/go.mod h1:P4UP14AxofMJ/54boWilabqqWoW9eLodl6I5GdGzazI= -go.etcd.io/etcd/server/v3 v3.5.16 h1:d0/SAdJ3vVsZvF8IFVb1k8zqMZ+heGcNfft71ul9GWE= -go.etcd.io/etcd/server/v3 v3.5.16/go.mod h1:ynhyZZpdDp1Gq49jkUg5mfkDWZwXnn3eIqCqtJnrD/s= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -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.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= -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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53 h1:Df6WuGvthPzc+JiQ/G+m+sNX24kc0aTBqoDN/0yyykE= -google.golang.org/genproto v0.0.0-20241015192408-796eee8c2d53/go.mod h1:fheguH3Am2dGp1LfXkrvwqC/KlFq8F0nLq3LryOMrrE= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= -gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= -k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.0 h1:VJ89ZvQZ8p1sLeiWdRJpRD6oLozNZD2+qVSLi+ft5Qs= -k8s.io/apiserver v0.32.0/go.mod h1:HFh+dM1/BE/Hm4bS4nTXHVfN6Z6tFIZPi649n83b4Ag= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/cloud-provider v0.32.0 h1:QXYJGmwME2q2rprymbmw2GroMChQYc/MWN6l/I4Kgp8= -k8s.io/cloud-provider v0.32.0/go.mod h1:cz3gVodkhgwi2ugj/JUPglIruLSdDaThxawuDyCHfr8= -k8s.io/cluster-bootstrap v0.32.0 h1:xfaL0a5MfX3CfZlbUDN1EaSkDAOYIshOiWGIo7n/2Dc= -k8s.io/cluster-bootstrap v0.32.0/go.mod h1:DgKZNB6DhBsEf8bp7RqMqGFGR4jq2JGcmyQgssAb5/I= -k8s.io/component-base v0.32.0 h1:d6cWHZkCiiep41ObYQS6IcgzOUQUNpywm39KVYaUqzU= -k8s.io/component-base v0.32.0/go.mod h1:JLG2W5TUxUu5uDyKiH2R/7NnxJo1HlPoRIIbVLkK5eM= -k8s.io/component-helpers v0.32.0 h1:pQEEBmRt3pDJJX98cQvZshDgJFeKRM4YtYkMmfOlczw= -k8s.io/component-helpers v0.32.0/go.mod h1:9RuClQatbClcokXOcDWSzFKQm1huIf0FzQlPRpizlMc= -k8s.io/controller-manager v0.32.0 h1:tpQl1rvH4huFB6Avl1nhowZHtZoCNWqn6OYdZPl7Ybc= -k8s.io/controller-manager v0.32.0/go.mod h1:JRuYnYCkKj3NgBTy+KNQKIUm/lJRoDAvGbfdEmk9LhY= -k8s.io/cri-api v0.32.0 h1:pzXJfyG7Tm4acrEt5HPqAq3r4cN5guLeapAN/NM2b70= -k8s.io/cri-api v0.32.0/go.mod h1:DCzMuTh2padoinefWME0G678Mc3QFbLMF2vEweGzBAI= -k8s.io/cri-client v0.32.0 h1:K6aTYDyS2AS8O4h79eI5r26562xstdybprtaaszjn+E= -k8s.io/cri-client v0.32.0/go.mod h1:FB8qZNj8KrwIFfVIR2zBGb+l6KUhrp+k8YFsVp3D+kw= -k8s.io/csi-translation-lib v0.32.0 h1:RAn9RGgYXHJQtDSb6qQ7zvq6QObOejzmsXDARI+f4OQ= -k8s.io/csi-translation-lib v0.32.0/go.mod h1:TjCJzkTNstdOESAXNnEImrYOMIEzP14aqM7H+vkehqw= -k8s.io/dynamic-resource-allocation v0.32.0 h1:0ZLSCKzlLZLVwKHxg6vafpd2U8b7jPMO3k8bbMFodis= -k8s.io/dynamic-resource-allocation v0.32.0/go.mod h1:MfoAUi0vCJtchNirAVk7c3IYfGGB3n+zbZ9GuyX4eeo= -k8s.io/externaljwt v0.32.0 h1:XblxKdGy937DArBgW/G2QCI55P2e69UxR/aLffOa+wY= -k8s.io/externaljwt v0.32.0/go.mod h1:P9TZ/u+o3CG//KNc/2HJmKgnuvawWS75IosS9dlGlxI= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.32.0 h1:jwOfunHIrcdYl5FRcA+uUKKtg6qiqoPCwmS2T3XTYL4= -k8s.io/kms v0.32.0/go.mod h1:Bk2evz/Yvk0oVrvm4MvZbgq8BD34Ksxs2SRHn4/UiOM= -k8s.io/kube-aggregator v0.32.0 h1:5ZyMW3QwAbmkasQrROcpa5we3et938DQuyUYHeXSPao= -k8s.io/kube-aggregator v0.32.0/go.mod h1:6OKivf6Ypx44qu2v1ZUMrxH8kRp/8LKFKeJU72J18lU= -k8s.io/kube-controller-manager v0.32.0 h1:GXQz5GtyvtyFRFpIZmPUgdLPyrvVKNA72JHAW5jLIL0= -k8s.io/kube-controller-manager v0.32.0/go.mod h1:9Z9qDFMJJv5RikzQdGSjq4J+qMm8Kg1bR+kEs/+COQA= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/kubelet v0.32.0 h1:uLyiKlz195Wo4an/K2tyge8o3QHx0ZkhVN3pevvp59A= -k8s.io/kubelet v0.32.0/go.mod h1:lAwuVZT/Hm7EdLn0jW2D+WdrJoorjJL2rVSdhOFnegw= -k8s.io/kubernetes v1.32.0 h1:4BDBWSolqPrv8GC3YfZw0CJvh5kA1TPnoX0FxDVd+qc= -k8s.io/kubernetes v1.32.0/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I= -k8s.io/mount-utils v0.32.0 h1:KOQAhPzJICATXnc6XCkWoexKbkOexRnMCUW8APFfwg4= -k8s.io/mount-utils v0.32.0/go.mod h1:Kun5c2svjAPx0nnvJKYQWhfeNW+O0EpzHgRhDcYoSY0= -k8s.io/pod-security-admission v0.32.0 h1:I+Og0uZIiMpIgTgPrTbW4jlwRI5RWazi8y+jrx1v10w= -k8s.io/pod-security-admission v0.32.0/go.mod h1:RvrcY0+5UAoCIJ7BscgDF3nbmXprgfnjTW+byCyXDvA= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.19.3 h1:XO2GvC9OPftRst6xWCpTgBZO04S2cbp0Qqkj8bX1sPw= -sigs.k8s.io/controller-runtime v0.19.3/go.mod h1:j4j87DqtsThvwTv5/Tc5NFRyyF/RF0ip4+62tbTSIUM= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/cmd/logging.go b/pkg/cmd/logging.go deleted file mode 100644 index 3060c07..0000000 --- a/pkg/cmd/logging.go +++ /dev/null @@ -1,58 +0,0 @@ -package cmd - -import ( - "fmt" - "log/slog" - "os" - - "github.com/spf13/cobra" - "github.com/spf13/pflag" -) - -func AddLoggingFlags(cmd *pflag.FlagSet) { - - cmd.String("log-level", "INFO", "The level of logs that should be emitted from the service. Supports: 'ERROR', 'WARN', 'INFO', 'DEBUG'") - cmd.String("log-format", "json", "The format of logs that should be emitted from the service. Supports: 'json' and 'text'") -} - -func SetupLogging(cmd *cobra.Command) (*slog.Logger, error) { - level, err := cmd.Flags().GetString("log-level") - if err != nil { - return nil, fmt.Errorf("failed to get `--log-level` flag: %w", err) - } - - format, err := cmd.Flags().GetString("log-format") - if err != nil { - return nil, fmt.Errorf("failed to get `--log-format` flag: %w", err) - } - - var handler slog.Handler - - handlerOpts := &slog.HandlerOptions{ - AddSource: true, - } - - switch level { - case "ERROR": - handlerOpts.Level = slog.LevelError - case "WARN": - handlerOpts.Level = slog.LevelWarn - case "INFO": - handlerOpts.Level = slog.LevelInfo - case "DEBUG": - handlerOpts.Level = slog.LevelDebug - default: - return nil, fmt.Errorf("log level '%s' is invalid. Supported options are: 'ERROR', 'WARN', 'INFO', 'DEBUG'", level) - } - - switch format { - case "json": - handler = slog.NewJSONHandler(os.Stderr, handlerOpts) - case "text": - handler = slog.NewTextHandler(os.Stderr, handlerOpts) - default: - return nil, fmt.Errorf("log format '%s' is invalid. Supported options are 'json', 'text'", format) - } - - return slog.New(handler), nil -} diff --git a/pkg/server/filters/organizations.go b/pkg/server/filters/organizations.go deleted file mode 100644 index 88a7434..0000000 --- a/pkg/server/filters/organizations.go +++ /dev/null @@ -1,181 +0,0 @@ -package filters - -import ( - "context" - "fmt" - "net/http" - "net/url" - "strings" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/selection" - "k8s.io/apimachinery/pkg/util/validation" - "k8s.io/apiserver/pkg/authentication/user" - "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" - "k8s.io/apiserver/pkg/endpoints/request" -) - -type key int - -const orgId key = iota - -const organizationIdKey = "resourcemanager.datumapis.com/organization-id" - -// OrganizationContextHandler will react to requests sent to a pseudo API path -// of `/apis/resourcemanager.datumapis.com/v1alpha/organizations/` and injects -// the provided organization ID into a request context value. This value will -// then be used by `organizationContextAuthorizationDecorator` to inject the -// org ID into the authenticated user's Extra field. It will then rewrite the -// request path to strip the prefix of `/apis/resourcemanager.datumapis.com/v1alpha/organizations/{organization}/control-plane`, -// which will result in the next set of handlers seeing a typical API request. -func OrganizationContextHandler(handler http.Handler, s runtime.NegotiatedSerializer) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - const prefix = "/apis/resourcemanager.datumapis.com/v1alpha/organizations/" - if strings.HasPrefix(req.URL.Path, prefix) { - // Extract the organization ID and the remaining path - rest := strings.TrimPrefix(req.URL.Path, prefix) - parts := strings.SplitN(rest, "/", 2) - - gv := schema.GroupVersion{Group: "resourcemanager.datumapis.com", Version: "v1alpha"} - if len(parts) != 2 { - responsewriters.ErrorNegotiated(apierrors.NewBadRequest( - "invalid request", - ), s, gv, w, req) - return - } - - organizationID := parts[0] - - if errs := validation.IsValidLabelValue(organizationID); len(errs) > 0 { - - // Return a text/plain response for discovery so that kubectl - // prints a useful error. If a structured response is given, it will - // swallow all useful error information. - if strings.HasSuffix(req.URL.Path, "control-plane/api") { - w.Header().Add("Content-Type", "text/plain") - w.WriteHeader(http.StatusForbidden) - if _, err := w.Write([]byte(fmt.Sprintf("invalid organization ID %q", organizationID))); err != nil { - responsewriters.InternalError(w, req, fmt.Errorf("failed to write response: %w", err)) - } - } else { - responsewriters.ErrorNegotiated(apierrors.NewBadRequest( - fmt.Sprintf("invalid organization ID %q", organizationID), - ), s, gv, w, req) - } - return - } - - ctx := context.WithValue(req.Context(), orgId, organizationID) - req = req.WithContext(ctx) - - remainingPath := strings.TrimPrefix(parts[1], "control-plane") - - req.URL.Path = strings.SplitN(remainingPath, "?", 2)[0] - - } - handler.ServeHTTP(w, req) - }) -} - -// OrganizationContextAuthorizationDecorator needs to run after authentication, -// but prior to authorization. -// -// This handler injects organization information into the authenticated user's -// Extra information that's made available in the request context by -// the `organizationContextHandler` handler. -func OrganizationContextAuthorizationDecorator(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx := req.Context() - orgId, ok := ctx.Value(orgId).(string) - if !ok { - // Not an org scoped request - handler.ServeHTTP(w, req) - return - } - - reqUser, ok := request.UserFrom(ctx) - if !ok { - // error handling - responsewriters.InternalError(w, req, fmt.Errorf("failed to extract user info from context")) - return - } - - u, ok := reqUser.(*user.DefaultInfo) - if !ok { - responsewriters.InternalError(w, req, fmt.Errorf("unexpected user.Info type. Expected *user.DefaultInfo, got %T", reqUser)) - return - } - - if u.Extra == nil { - u.Extra = map[string][]string{} - } - - u.Extra[organizationIdKey] = []string{orgId} - - handler.ServeHTTP(w, req) - }) -} - -// OrganizationProjectListConstraintDecorator intercepts requests to list -// projects, which are a cluster scoped resource, and injects a label selector -// to limit projects to the organization provided in the request context. -// -// This is done so that end users can execute `kubectl get projects` and not -// need to provide a label selector. -func OrganizationProjectListConstraintDecorator(handler http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx := req.Context() - info, ok := request.RequestInfoFrom(ctx) - if !ok { - responsewriters.InternalError(w, req, fmt.Errorf("failed to get RequestInfo from context")) - return - } - - if info.APIGroup == "resourcemanager.datumapis.com" && info.Resource == "projects" && info.Verb == "list" { - organizationID, ok := ctx.Value(orgId).(string) - if ok { - requirements, err := labels.ParseToRequirements(info.LabelSelector) - if err != nil { - responsewriters.InternalError(w, req, fmt.Errorf("failed to parse label selector: %w", err)) - return - } - - orgConstraint, err := labels.NewRequirement(organizationIdKey, selection.Equals, []string{organizationID}) - if err != nil { - responsewriters.InternalError(w, req, fmt.Errorf("failed to parse label selector: %w", err)) - return - } - - // Build new selector, filtering out any organization-uid constraint that - // may have been provided in the request - selector := labels.NewSelector() - selector = selector.Add(*orgConstraint) - for _, r := range requirements { - if r.Key() == organizationIdKey { - continue - } - selector = selector.Add(r) - } - - info.LabelSelector = selector.String() - - // Inject the new selector into the request - query, err := url.ParseQuery(req.URL.RawQuery) - if err != nil { - responsewriters.InternalError(w, req, fmt.Errorf("failed to parse url query: %w", err)) - } - query.Del("labelSelector") - query.Add("labelSelector", info.LabelSelector) - - req.URL.RawQuery = query.Encode() - } - } - - req = req.WithContext(request.WithRequestInfo(ctx, info)) - - handler.ServeHTTP(w, req) - }) -} diff --git a/pkg/server/filters/organizations_test.go b/pkg/server/filters/organizations_test.go deleted file mode 100644 index a30f9db..0000000 --- a/pkg/server/filters/organizations_test.go +++ /dev/null @@ -1,121 +0,0 @@ -package filters - -import ( - "net/http" - "net/http/httptest" - "testing" - - "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apiserver/pkg/authentication/user" - genericapifilters "k8s.io/apiserver/pkg/endpoints/filters" - "k8s.io/apiserver/pkg/endpoints/request" - k8sapiserver "k8s.io/apiserver/pkg/server" - - "github.com/stretchr/testify/assert" -) - -func TestOrganizationContextHandler(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - tests := map[string]struct { - path string - reqUser *user.DefaultInfo - expectedCode int - // Custom handler to assert expectatations on request state in the end of - // the request chain. - assertRequest func(*testing.T, *http.Request) - }{ - "bad request: missing org id": { - path: "/apis/resourcemanager.datumapis.com/v1alpha/organizations/", - expectedCode: http.StatusBadRequest, - }, - "internal error: org request with no user": { - path: "/apis/resourcemanager.datumapis.com/v1alpha/organizations/some-org/control-plane", - assertRequest: func(t *testing.T, req *http.Request) { - assert.Equal(t, "", req.URL.Path) - }, - expectedCode: http.StatusInternalServerError, - }, - "org request succeeds": { - path: "/apis/resourcemanager.datumapis.com/v1alpha/organizations/some-org/control-plane", - reqUser: &user.DefaultInfo{}, - expectedCode: http.StatusOK, - assertRequest: func(t *testing.T, req *http.Request) { - assert.Equal(t, "", req.URL.Path) - reqUser, ok := request.UserFrom(req.Context()) - assert.True(t, ok, "user not found in request context") - - u, ok := reqUser.(*user.DefaultInfo) - assert.True(t, ok, "user in request context is not *user.DefaultInfo") - - assert.Contains(t, u.Extra, organizationIdKey) - assert.Equal(t, "some-org", u.Extra[organizationIdKey][0]) - }, - }, - "org project list label selector injected": { - path: "/apis/resourcemanager.datumapis.com/v1alpha/organizations/some-org/control-plane/apis/resourcemanager.datumapis.com/v1alpha/projects?labelSelector=resourcemanager.datumapis.com/organization-id=notvalid,other=value", - reqUser: &user.DefaultInfo{}, - expectedCode: http.StatusOK, - assertRequest: func(t *testing.T, req *http.Request) { - info, ok := request.RequestInfoFrom(req.Context()) - assert.True(t, ok, "request info not found in request context") - if ok { - assert.NotEmpty(t, info.LabelSelector, "label selector not found in request") - - selector, err := labels.Parse(info.LabelSelector) - assert.NoError(t, err, "unexpected error parsing request label selectors") - - // Ensure that the org constraint exists and has the value in the URL - // instead of the value provided in the query parameter. - v, ok := selector.RequiresExactMatch(organizationIdKey) - assert.True(t, ok, "organization-id constraint not found") - assert.Equal(t, "some-org", v) - - // Ensure other constraints still exist - v, ok = selector.RequiresExactMatch("other") - assert.True(t, ok, `constraint "other" not found`) - assert.Equal(t, "value", v) - } - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - req, err := http.NewRequest("GET", tt.path, nil) - assert.NoError(t, err) - - rr := httptest.NewRecorder() - - handler := OrganizationContextHandler( - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if tt.reqUser != nil { - req = req.WithContext(request.WithUser(req.Context(), tt.reqUser)) - } - - genericapifilters.WithRequestInfo( - OrganizationProjectListConstraintDecorator( - OrganizationContextAuthorizationDecorator(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - if tt.assertRequest != nil { - tt.assertRequest(t, req) - } - })), - ), - k8sapiserver.NewRequestInfoResolver(&k8sapiserver.Config{}), - ).ServeHTTP(w, req) - - }), - serializer.NewCodecFactory(scheme), - ) - - handler.ServeHTTP(rr, req) - - assert.Equal(t, tt.expectedCode, rr.Code) - }) - } - -} From 4e2e468ae42a27e76fbe4b5c96590c837ffcf5e4 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 12:48:41 -0500 Subject: [PATCH 02/35] chore: init repo with kubebuilder --- .devcontainer/devcontainer.json | 25 ++ .devcontainer/post-install.sh | 23 ++ .dockerignore | 3 + .github/workflows/lint.yml | 23 ++ .github/workflows/test-e2e.yml | 35 ++ .github/workflows/test.yml | 23 ++ .gitignore | 27 ++ .golangci.yml | 47 +++ Dockerfile | 33 ++ Makefile | 225 ++++++++++++ PROJECT | 10 + cmd/main.go | 233 +++++++++++++ .../default/cert_metrics_manager_patch.yaml | 30 ++ config/default/kustomization.yaml | 234 +++++++++++++ config/default/manager_metrics_patch.yaml | 4 + config/default/metrics_service.yaml | 18 + config/manager/kustomization.yaml | 2 + config/manager/manager.yaml | 98 ++++++ .../network-policy/allow-metrics-traffic.yaml | 27 ++ config/network-policy/kustomization.yaml | 2 + config/prometheus/kustomization.yaml | 11 + config/prometheus/monitor.yaml | 27 ++ config/prometheus/monitor_tls_patch.yaml | 19 + config/rbac/kustomization.yaml | 20 ++ config/rbac/leader_election_role.yaml | 40 +++ config/rbac/leader_election_role_binding.yaml | 15 + config/rbac/metrics_auth_role.yaml | 17 + config/rbac/metrics_auth_role_binding.yaml | 12 + config/rbac/metrics_reader_role.yaml | 9 + config/rbac/role.yaml | 11 + config/rbac/role_binding.yaml | 15 + config/rbac/service_account.yaml | 8 + go.mod | 100 ++++++ go.sum | 247 +++++++++++++ hack/boilerplate.go.txt | 15 + test/e2e/e2e_suite_test.go | 89 +++++ test/e2e/e2e_test.go | 329 ++++++++++++++++++ test/utils/utils.go | 251 +++++++++++++ 38 files changed, 2357 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/post-install.sh create mode 100644 .dockerignore create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/test-e2e.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 PROJECT create mode 100644 cmd/main.go create mode 100644 config/default/cert_metrics_manager_patch.yaml create mode 100644 config/default/kustomization.yaml create mode 100644 config/default/manager_metrics_patch.yaml create mode 100644 config/default/metrics_service.yaml create mode 100644 config/manager/kustomization.yaml create mode 100644 config/manager/manager.yaml create mode 100644 config/network-policy/allow-metrics-traffic.yaml create mode 100644 config/network-policy/kustomization.yaml create mode 100644 config/prometheus/kustomization.yaml create mode 100644 config/prometheus/monitor.yaml create mode 100644 config/prometheus/monitor_tls_patch.yaml create mode 100644 config/rbac/kustomization.yaml create mode 100644 config/rbac/leader_election_role.yaml create mode 100644 config/rbac/leader_election_role_binding.yaml create mode 100644 config/rbac/metrics_auth_role.yaml create mode 100644 config/rbac/metrics_auth_role_binding.yaml create mode 100644 config/rbac/metrics_reader_role.yaml create mode 100644 config/rbac/role.yaml create mode 100644 config/rbac/role_binding.yaml create mode 100644 config/rbac/service_account.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/boilerplate.go.txt create mode 100644 test/e2e/e2e_suite_test.go create mode 100644 test/e2e/e2e_test.go create mode 100644 test/utils/utils.go diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..0e0eed2 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,25 @@ +{ + "name": "Kubebuilder DevContainer", + "image": "docker.io/golang:1.23", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/git:1": {} + }, + + "runArgs": ["--network=host"], + + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "extensions": [ + "ms-kubernetes-tools.vscode-kubernetes-tools", + "ms-azuretools.vscode-docker" + ] + } + }, + + "onCreateCommand": "bash .devcontainer/post-install.sh" +} + diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh new file mode 100644 index 0000000..265c43e --- /dev/null +++ b/.devcontainer/post-install.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -x + +curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 +chmod +x ./kind +mv ./kind /usr/local/bin/kind + +curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/amd64 +chmod +x kubebuilder +mv kubebuilder /usr/local/bin/ + +KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) +curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" +chmod +x kubectl +mv kubectl /usr/local/bin/kubectl + +docker network create -d=bridge --subnet=172.19.0.0/24 kind + +kind version +kubebuilder version +docker --version +go version +kubectl version --client diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a3aab7a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..4951e33 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: Lint + +on: + push: + pull_request: + +jobs: + lint: + name: Run on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Run linter + uses: golangci/golangci-lint-action@v6 + with: + version: v1.63.4 diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml new file mode 100644 index 0000000..b2eda8c --- /dev/null +++ b/.github/workflows/test-e2e.yml @@ -0,0 +1,35 @@ +name: E2E Tests + +on: + push: + pull_request: + +jobs: + test-e2e: + name: Run on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Install the latest version of kind + run: | + curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 + chmod +x ./kind + sudo mv ./kind /usr/local/bin/kind + + - name: Verify kind installation + run: kind version + + - name: Create kind cluster + run: kind create cluster + + - name: Running Test e2e + run: | + go mod tidy + make test-e2e diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..fc2e80d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,23 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + name: Run on Ubuntu + runs-on: ubuntu-latest + steps: + - name: Clone the code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Running Tests + run: | + go mod tidy + make test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ada68ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/* +Dockerfile.cross + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Go workspace file +go.work + +# Kubernetes Generated files - skip generated files, except for vendored files +!vendor/**/zz_generated.* + +# editor and IDE paraphernalia +.idea +.vscode +*.swp +*.swo +*~ diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6b29746 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,47 @@ +run: + timeout: 5m + allow-parallel-runners: true + +issues: + # don't skip warning about doc comments + # don't exclude the default set of lint + exclude-use-default: false + # restore some of the defaults + # (fill in the rest as needed) + exclude-rules: + - path: "api/*" + linters: + - lll + - path: "internal/*" + linters: + - dupl + - lll +linters: + disable-all: true + enable: + - dupl + - errcheck + - copyloopvar + - ginkgolinter + - goconst + - gocyclo + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - typecheck + - unconvert + - unparam + - unused + +linters-settings: + revive: + rules: + - name: comment-spacings diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..348b837 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,33 @@ +# Build the manager binary +FROM docker.io/golang:1.23 AS builder +ARG TARGETOS +ARG TARGETARCH + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY cmd/main.go cmd/main.go +COPY api/ api/ +COPY internal/ internal/ + +# Build +# the GOARCH has not a default value to allow the binary be built according to the host where the command +# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO +# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, +# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b491df7 --- /dev/null +++ b/Makefile @@ -0,0 +1,225 @@ +# Image URL to use all building/pushing image targets +IMG ?= controller:latest + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif + +# CONTAINER_TOOL defines the container tool to be used for building images. +# Be aware that the target commands are only tested with Docker which is +# scaffolded by default. However, you might want to replace it to use other +# tools. (i.e. podman) +CONTAINER_TOOL ?= docker + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec + +.PHONY: all +all: build + +##@ General + +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk command is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. + go vet ./... + +.PHONY: test +test: manifests generate fmt vet setup-envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out + +# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'. +# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. +# CertManager is installed by default; skip with: +# - CERT_MANAGER_INSTALL_SKIP=true +.PHONY: test-e2e +test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + @command -v $(KIND) >/dev/null 2>&1 || { \ + echo "Kind is not installed. Please install Kind manually."; \ + exit 1; \ + } + @$(KIND) get clusters | grep -q 'kind' || { \ + echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ + exit 1; \ + } + go test ./test/e2e/ -v -ginkgo.v + +.PHONY: lint +lint: golangci-lint ## Run golangci-lint linter + $(GOLANGCI_LINT) run + +.PHONY: lint-fix +lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes + $(GOLANGCI_LINT) run --fix + +.PHONY: lint-config +lint-config: golangci-lint ## Verify golangci-lint linter configuration + $(GOLANGCI_LINT) config verify + +##@ Build + +.PHONY: build +build: manifests generate fmt vet ## Build manager binary. + go build -o bin/manager cmd/main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./cmd/main.go + +# If you wish to build the manager image targeting other platforms you can use the --platform flag. +# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. +# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + $(CONTAINER_TOOL) push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ +# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) +# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: ## Build and push docker image for the manager for cross-platform support + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross + - $(CONTAINER_TOOL) buildx create --name datum-builder + $(CONTAINER_TOOL) buildx use datum-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm datum-builder + rm Dockerfile.cross + +.PHONY: build-installer +build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. + mkdir -p dist + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default > dist/install.yaml + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - + +.PHONY: undeploy +undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - + +##@ Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUBECTL ?= kubectl +KIND ?= kind +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint + +## Tool Versions +KUSTOMIZE_VERSION ?= v5.6.0 +CONTROLLER_TOOLS_VERSION ?= v0.17.2 +#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) +ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') +#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) +ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') +GOLANGCI_LINT_VERSION ?= v1.63.4 + +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. +$(CONTROLLER_GEN): $(LOCALBIN) + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) + +.PHONY: setup-envtest +setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory. + @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..." + @$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \ + echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \ + exit 1; \ + } + +.PHONY: envtest +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. +$(ENVTEST): $(LOCALBIN) + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) + +.PHONY: golangci-lint +golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. +$(GOLANGCI_LINT): $(LOCALBIN) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) +endef diff --git a/PROJECT b/PROJECT new file mode 100644 index 0000000..804d72b --- /dev/null +++ b/PROJECT @@ -0,0 +1,10 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html +domain: datumapis.com +layout: +- go.kubebuilder.io/v4 +projectName: datum-cloud +repo: go.datum.net/datum +version: "3" diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..63179be --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,233 @@ +/* +Copyright 2025. + +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 main + +import ( + "crypto/tls" + "flag" + "os" + "path/filepath" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + // +kubebuilder:scaffold:scheme +} + +// nolint:gocyclo +func main() { + var metricsAddr string + var metricsCertPath, metricsCertName, metricsCertKey string + var webhookCertPath, webhookCertName, webhookCertKey string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var tlsOpts []func(*tls.Config) + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + flag.BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") + flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") + flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") + flag.StringVar(&metricsCertPath, "metrics-cert-path", "", + "The directory that contains the metrics server certificate.") + flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") + flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") + flag.BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + // Create watchers for metrics and webhooks certificates + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher + + // Initial webhook TLS options + webhookTLSOpts := tlsOpts + + if len(webhookCertPath) > 0 { + setupLog.Info("Initializing webhook certificate watcher using provided certificates", + "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) + + var err error + webhookCertWatcher, err = certwatcher.New( + filepath.Join(webhookCertPath, webhookCertName), + filepath.Join(webhookCertPath, webhookCertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize webhook certificate watcher") + os.Exit(1) + } + + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { + config.GetCertificate = webhookCertWatcher.GetCertificate + }) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: webhookTLSOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + // If the certificate is not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + // + // TODO(user): If you enable certManager, uncomment the following lines: + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates + // managed by cert-manager for the metrics server. + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. + if len(metricsCertPath) > 0 { + setupLog.Info("Initializing metrics certificate watcher using provided certificates", + "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) + + var err error + metricsCertWatcher, err = certwatcher.New( + filepath.Join(metricsCertPath, metricsCertName), + filepath.Join(metricsCertPath, metricsCertKey), + ) + if err != nil { + setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) + os.Exit(1) + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = metricsCertWatcher.GetCertificate + }) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "81afa9db.datumapis.com", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + // +kubebuilder:scaffold:builder + + if metricsCertWatcher != nil { + setupLog.Info("Adding metrics certificate watcher to manager") + if err := mgr.Add(metricsCertWatcher); err != nil { + setupLog.Error(err, "unable to add metrics certificate watcher to manager") + os.Exit(1) + } + } + + if webhookCertWatcher != nil { + setupLog.Info("Adding webhook certificate watcher to manager") + if err := mgr.Add(webhookCertWatcher); err != nil { + setupLog.Error(err, "unable to add webhook certificate watcher to manager") + os.Exit(1) + } + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} diff --git a/config/default/cert_metrics_manager_patch.yaml b/config/default/cert_metrics_manager_patch.yaml new file mode 100644 index 0000000..d975015 --- /dev/null +++ b/config/default/cert_metrics_manager_patch.yaml @@ -0,0 +1,30 @@ +# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs. + +# Add the volumeMount for the metrics-server certs +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + mountPath: /tmp/k8s-metrics-server/metrics-certs + name: metrics-certs + readOnly: true + +# Add the --metrics-cert-path argument for the metrics server +- op: add + path: /spec/template/spec/containers/0/args/- + value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs + +# Add the metrics-server certs volume configuration +- op: add + path: /spec/template/spec/volumes/- + value: + name: metrics-certs + secret: + secretName: metrics-server-cert + optional: false + items: + - key: ca.crt + path: ca.crt + - key: tls.crt + path: tls.crt + - key: tls.key + path: tls.key diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 0000000..19db271 --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,234 @@ +# Adds namespace to all resources. +namespace: datum-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: datum- + +# Labels to add to all resources and selectors. +#labels: +#- includeSelectors: true +# pairs: +# someName: someValue + +resources: +#- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus +# [METRICS] Expose the controller manager metrics service. +- metrics_service.yaml +# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. +# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. +# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will +# be able to communicate with the Webhook Server. +#- ../network-policy + +# Uncomment the patches line if you enable Metrics +patches: +# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. +# More info: https://book.kubebuilder.io/reference/metrics +- path: manager_metrics_patch.yaml + target: + kind: Deployment + +# Uncomment the patches line if you enable Metrics and CertManager +# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line. +# This patch will protect the metrics with certManager self-signed certs. +#- path: cert_metrics_manager_patch.yaml +# target: +# kind: Deployment + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- path: manager_webhook_patch.yaml +# target: +# kind: Deployment + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +# Uncomment the following replacements to add the cert-manager CA injection annotations +#replacements: +# - source: # Uncomment the following block to enable certificates for metrics +# kind: Service +# version: v1 +# name: controller-manager-metrics-service +# fieldPath: metadata.name +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: metrics-certs +# fieldPaths: +# - spec.dnsNames.0 +# - spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor +# kind: ServiceMonitor +# group: monitoring.coreos.com +# version: v1 +# name: controller-manager-metrics-monitor +# fieldPaths: +# - spec.endpoints.0.tlsConfig.serverName +# options: +# delimiter: '.' +# index: 0 +# create: true +# +# - source: +# kind: Service +# version: v1 +# name: controller-manager-metrics-service +# fieldPath: metadata.namespace +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: metrics-certs +# fieldPaths: +# - spec.dnsNames.0 +# - spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true +# - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor +# kind: ServiceMonitor +# group: monitoring.coreos.com +# version: v1 +# name: controller-manager-metrics-monitor +# fieldPaths: +# - spec.endpoints.0.tlsConfig.serverName +# options: +# delimiter: '.' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have any webhook +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.name # Name of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 0 +# create: true +# - source: +# kind: Service +# version: v1 +# name: webhook-service +# fieldPath: .metadata.namespace # Namespace of the service +# targets: +# - select: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPaths: +# - .spec.dnsNames.0 +# - .spec.dnsNames.1 +# options: +# delimiter: '.' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # This name should match the one in certificate.yaml +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: +# - select: +# kind: ValidatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 0 +# create: true +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: +# - select: +# kind: MutatingWebhookConfiguration +# fieldPaths: +# - .metadata.annotations.[cert-manager.io/inject-ca-from] +# options: +# delimiter: '/' +# index: 1 +# create: true +# +# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.namespace # Namespace of the certificate CR +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# +kubebuilder:scaffold:crdkustomizecainjectionns +# - source: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert +# fieldPath: .metadata.name +# targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. +# +kubebuilder:scaffold:crdkustomizecainjectionname diff --git a/config/default/manager_metrics_patch.yaml b/config/default/manager_metrics_patch.yaml new file mode 100644 index 0000000..2aaef65 --- /dev/null +++ b/config/default/manager_metrics_patch.yaml @@ -0,0 +1,4 @@ +# This patch adds the args to allow exposing the metrics endpoint using HTTPS +- op: add + path: /spec/template/spec/containers/0/args/0 + value: --metrics-bind-address=:8443 diff --git a/config/default/metrics_service.yaml b/config/default/metrics_service.yaml new file mode 100644 index 0000000..a45b3e2 --- /dev/null +++ b/config/default/metrics_service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: 8443 + selector: + control-plane: controller-manager + app.kubernetes.io/name: datum diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 0000000..5c5f0b8 --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- manager.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 0000000..69f2bd3 --- /dev/null +++ b/config/manager/manager.yaml @@ -0,0 +1,98 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: datum + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + app.kubernetes.io/name: datum + spec: + # TODO(user): Uncomment the following code to configure the nodeAffinity expression + # according to the platforms which are supported by your solution. + # It is considered best practice to support multiple architectures. You can + # build your manager image using the makefile target docker-buildx. + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: kubernetes.io/arch + # operator: In + # values: + # - amd64 + # - arm64 + # - ppc64le + # - s390x + # - key: kubernetes.io/os + # operator: In + # values: + # - linux + securityContext: + # Projects are configured by default to adhere to the "restricted" Pod Security Standards. + # This ensures that deployments meet the highest security requirements for Kubernetes. + # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - command: + - /manager + args: + - --leader-elect + - --health-probe-bind-address=:8081 + image: controller:latest + name: manager + ports: [] + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + volumeMounts: [] + volumes: [] + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/config/network-policy/allow-metrics-traffic.yaml b/config/network-policy/allow-metrics-traffic.yaml new file mode 100644 index 0000000..305338e --- /dev/null +++ b/config/network-policy/allow-metrics-traffic.yaml @@ -0,0 +1,27 @@ +# This NetworkPolicy allows ingress traffic +# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those +# namespaces are able to gather data from the metrics endpoint. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: allow-metrics-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: datum + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label metrics: enabled + - from: + - namespaceSelector: + matchLabels: + metrics: enabled # Only from namespaces with this label + ports: + - port: 8443 + protocol: TCP diff --git a/config/network-policy/kustomization.yaml b/config/network-policy/kustomization.yaml new file mode 100644 index 0000000..ec0fb5e --- /dev/null +++ b/config/network-policy/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- allow-metrics-traffic.yaml diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 0000000..fdc5481 --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,11 @@ +resources: +- monitor.yaml + +# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus +# to securely reference certificates created and managed by cert-manager. +# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml +# to mount the "metrics-server-cert" secret in the Manager Deployment. +#patches: +# - path: monitor_tls_patch.yaml +# target: +# kind: ServiceMonitor diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 0000000..5692943 --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,27 @@ +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https # Ensure this is the name of the port that exposes HTTPS metrics + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables + # certificate verification, exposing the system to potential man-in-the-middle attacks. + # For production environments, it is recommended to use cert-manager for automatic TLS certificate management. + # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml, + # which securely references the certificate from the 'metrics-server-cert' secret. + insecureSkipVerify: true + selector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: datum diff --git a/config/prometheus/monitor_tls_patch.yaml b/config/prometheus/monitor_tls_patch.yaml new file mode 100644 index 0000000..5bf84ce --- /dev/null +++ b/config/prometheus/monitor_tls_patch.yaml @@ -0,0 +1,19 @@ +# Patch for Prometheus ServiceMonitor to enable secure TLS configuration +# using certificates managed by cert-manager +- op: replace + path: /spec/endpoints/0/tlsConfig + value: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc + insecureSkipVerify: false + ca: + secret: + name: metrics-server-cert + key: ca.crt + cert: + secret: + name: metrics-server-cert + key: tls.crt + keySecret: + name: metrics-server-cert + key: tls.key diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 0000000..5619aa0 --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,20 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml +# The following RBAC configurations are used to protect +# the metrics endpoint with authn/authz. These configurations +# ensure that only authorized users and service accounts +# can access the metrics endpoint. Comment the following +# permissions if you want to disable this protection. +# More info: https://book.kubebuilder.io/reference/metrics.html +- metrics_auth_role.yaml +- metrics_auth_role_binding.yaml +- metrics_reader_role.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml new file mode 100644 index 0000000..975800c --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,40 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 0000000..1376a5d --- /dev/null +++ b/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/metrics_auth_role.yaml b/config/rbac/metrics_auth_role.yaml new file mode 100644 index 0000000..32d2e4e --- /dev/null +++ b/config/rbac/metrics_auth_role.yaml @@ -0,0 +1,17 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-auth-role +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create diff --git a/config/rbac/metrics_auth_role_binding.yaml b/config/rbac/metrics_auth_role_binding.yaml new file mode 100644 index 0000000..e775d67 --- /dev/null +++ b/config/rbac/metrics_auth_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: metrics-auth-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metrics-auth-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/metrics_reader_role.yaml b/config/rbac/metrics_reader_role.yaml new file mode 100644 index 0000000..51a75db --- /dev/null +++ b/config/rbac/metrics_reader_role.yaml @@ -0,0 +1,9 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: + - "/metrics" + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..c82dc1c --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: manager-role +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 0000000..38ca43b --- /dev/null +++ b/config/rbac/role_binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: controller-manager + namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml new file mode 100644 index 0000000..0daf2da --- /dev/null +++ b/config/rbac/service_account.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: datum + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9a57008 --- /dev/null +++ b/go.mod @@ -0,0 +1,100 @@ +module go.datum.net/datum + +go 1.24.0 + +godebug default=go1.24 + +require ( + github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/gomega v1.36.1 + k8s.io/apimachinery v0.32.1 + k8s.io/client-go v0.32.1 + sigs.k8s.io/controller-runtime v0.20.4 +) + +require ( + cel.dev/expr v0.18.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.22.0 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.19.1 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/oauth2 v0.23.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/term v0.25.0 // indirect + golang.org/x/text v0.19.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.26.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.32.1 // indirect + k8s.io/apiserver v0.32.1 // indirect + k8s.io/component-base v0.32.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3719e6c --- /dev/null +++ b/go.sum @@ -0,0 +1,247 @@ +cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= +cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= +github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +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/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= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= +github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +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/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= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +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= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +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/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= +golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= +google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= +k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= +k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= +k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= +k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= +k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= +k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= +k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= +k8s.io/client-go v0.32.1/go.mod h1:aTTKZY7MdxUaJ/KiUs8D+GssR9zJZi77ZqtzcGXIiDg= +k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= +k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= +sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 0000000..221dcbe --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2025. + +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. +*/ \ No newline at end of file diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go new file mode 100644 index 0000000..4507a6b --- /dev/null +++ b/test/e2e/e2e_suite_test.go @@ -0,0 +1,89 @@ +/* +Copyright 2025. + +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 e2e + +import ( + "fmt" + "os" + "os/exec" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "go.datum.net/datum/test/utils" +) + +var ( + // Optional Environment Variables: + // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup. + // These variables are useful if CertManager is already installed, avoiding + // re-installation and conflicts. + skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true" + // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster + isCertManagerAlreadyInstalled = false + + // projectImage is the name of the image which will be build and loaded + // with the code source changes to be tested. + projectImage = "example.com/datum:v0.0.1" +) + +// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, +// temporary environment to validate project changes with the purposed to be used in CI jobs. +// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs +// CertManager. +func TestE2E(t *testing.T) { + RegisterFailHandler(Fail) + _, _ = fmt.Fprintf(GinkgoWriter, "Starting datum integration test suite\n") + RunSpecs(t, "e2e suite") +} + +var _ = BeforeSuite(func() { + By("building the manager(Operator) image") + cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage)) + _, err := utils.Run(cmd) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") + + // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is + // built and available before running the tests. Also, remove the following block. + By("loading the manager(Operator) image on Kind") + err = utils.LoadImageToKindClusterWithName(projectImage) + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") + + // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing. + // To prevent errors when tests run in environments with CertManager already installed, + // we check for its presence before execution. + // Setup CertManager before the suite if not skipped and if not already installed + if !skipCertManagerInstall { + By("checking if cert manager is installed already") + isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled() + if !isCertManagerAlreadyInstalled { + _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n") + Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager") + } else { + _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n") + } + } +}) + +var _ = AfterSuite(func() { + // Teardown CertManager after the suite if not skipped and if it was not already installed + if !skipCertManagerInstall && !isCertManagerAlreadyInstalled { + _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n") + utils.UninstallCertManager() + } +}) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go new file mode 100644 index 0000000..d77af78 --- /dev/null +++ b/test/e2e/e2e_test.go @@ -0,0 +1,329 @@ +/* +Copyright 2025. + +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 e2e + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "go.datum.net/datum/test/utils" +) + +// namespace where the project is deployed in +const namespace = "datum-system" + +// serviceAccountName created for the project +const serviceAccountName = "datum-controller-manager" + +// metricsServiceName is the name of the metrics service of the project +const metricsServiceName = "datum-controller-manager-metrics-service" + +// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data +const metricsRoleBindingName = "datum-metrics-binding" + +var _ = Describe("Manager", Ordered, func() { + var controllerPodName string + + // Before running the tests, set up the environment by creating the namespace, + // enforce the restricted security policy to the namespace, installing CRDs, + // and deploying the controller. + BeforeAll(func() { + By("creating manager namespace") + cmd := exec.Command("kubectl", "create", "ns", namespace) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") + + By("labeling the namespace to enforce the restricted security policy") + cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace, + "pod-security.kubernetes.io/enforce=restricted") + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy") + + By("installing CRDs") + cmd = exec.Command("make", "install") + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs") + + By("deploying the controller-manager") + cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage)) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager") + }) + + // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, + // and deleting the namespace. + AfterAll(func() { + By("cleaning up the curl pod for metrics") + cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) + _, _ = utils.Run(cmd) + + By("undeploying the controller-manager") + cmd = exec.Command("make", "undeploy") + _, _ = utils.Run(cmd) + + By("uninstalling CRDs") + cmd = exec.Command("make", "uninstall") + _, _ = utils.Run(cmd) + + By("removing manager namespace") + cmd = exec.Command("kubectl", "delete", "ns", namespace) + _, _ = utils.Run(cmd) + }) + + // After each test, check for failures and collect logs, events, + // and pod descriptions for debugging. + AfterEach(func() { + specReport := CurrentSpecReport() + if specReport.Failed() { + By("Fetching controller manager pod logs") + cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) + controllerLogs, err := utils.Run(cmd) + if err == nil { + _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) + } else { + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) + } + + By("Fetching Kubernetes events") + cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") + eventsOutput, err := utils.Run(cmd) + if err == nil { + _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) + } else { + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) + } + + By("Fetching curl-metrics logs") + cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) + metricsOutput, err := utils.Run(cmd) + if err == nil { + _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) + } else { + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) + } + + By("Fetching controller manager pod description") + cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace) + podDescription, err := utils.Run(cmd) + if err == nil { + fmt.Println("Pod description:\n", podDescription) + } else { + fmt.Println("Failed to describe controller pod") + } + } + }) + + SetDefaultEventuallyTimeout(2 * time.Minute) + SetDefaultEventuallyPollingInterval(time.Second) + + Context("Manager", func() { + It("should run successfully", func() { + By("validating that the controller-manager pod is running as expected") + verifyControllerUp := func(g Gomega) { + // Get the name of the controller-manager pod + cmd := exec.Command("kubectl", "get", + "pods", "-l", "control-plane=controller-manager", + "-o", "go-template={{ range .items }}"+ + "{{ if not .metadata.deletionTimestamp }}"+ + "{{ .metadata.name }}"+ + "{{ \"\\n\" }}{{ end }}{{ end }}", + "-n", namespace, + ) + + podOutput, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information") + podNames := utils.GetNonEmptyLines(podOutput) + g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running") + controllerPodName = podNames[0] + g.Expect(controllerPodName).To(ContainSubstring("controller-manager")) + + // Validate the pod's status + cmd = exec.Command("kubectl", "get", + "pods", controllerPodName, "-o", "jsonpath={.status.phase}", + "-n", namespace, + ) + output, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status") + } + Eventually(verifyControllerUp).Should(Succeed()) + }) + + It("should ensure the metrics endpoint is serving metrics", func() { + By("creating a ClusterRoleBinding for the service account to allow access to metrics") + cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, + "--clusterrole=datum-metrics-reader", + fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), + ) + _, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to create ClusterRoleBinding") + + By("validating that the metrics service is available") + cmd = exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Metrics service should exist") + + By("getting the service account token") + token, err := serviceAccountToken() + Expect(err).NotTo(HaveOccurred()) + Expect(token).NotTo(BeEmpty()) + + By("waiting for the metrics endpoint to be ready") + verifyMetricsEndpointReady := func(g Gomega) { + cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace) + output, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(ContainSubstring("8443"), "Metrics endpoint is not ready") + } + Eventually(verifyMetricsEndpointReady).Should(Succeed()) + + By("verifying that the controller manager is serving the metrics server") + verifyMetricsServerStarted := func(g Gomega) { + cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) + output, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"), + "Metrics server not yet started") + } + Eventually(verifyMetricsServerStarted).Should(Succeed()) + + By("creating the curl-metrics pod to access the metrics endpoint") + cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never", + "--namespace", namespace, + "--image=curlimages/curl:latest", + "--overrides", + fmt.Sprintf(`{ + "spec": { + "containers": [{ + "name": "curl", + "image": "curlimages/curl:latest", + "command": ["/bin/sh", "-c"], + "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": ["ALL"] + }, + "runAsNonRoot": true, + "runAsUser": 1000, + "seccompProfile": { + "type": "RuntimeDefault" + } + } + }], + "serviceAccount": "%s" + } + }`, token, metricsServiceName, namespace, serviceAccountName)) + _, err = utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod") + + By("waiting for the curl-metrics pod to complete.") + verifyCurlUp := func(g Gomega) { + cmd := exec.Command("kubectl", "get", "pods", "curl-metrics", + "-o", "jsonpath={.status.phase}", + "-n", namespace) + output, err := utils.Run(cmd) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status") + } + Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed()) + + By("getting the metrics by checking curl-metrics logs") + metricsOutput := getMetricsOutput() + Expect(metricsOutput).To(ContainSubstring( + "controller_runtime_reconcile_total", + )) + }) + + // +kubebuilder:scaffold:e2e-webhooks-checks + + // TODO: Customize the e2e test suite with scenarios specific to your project. + // Consider applying sample/CR(s) and check their status and/or verifying + // the reconciliation by using the metrics, i.e.: + // metricsOutput := getMetricsOutput() + // Expect(metricsOutput).To(ContainSubstring( + // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`, + // strings.ToLower(), + // )) + }) +}) + +// serviceAccountToken returns a token for the specified service account in the given namespace. +// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request +// and parsing the resulting token from the API response. +func serviceAccountToken() (string, error) { + const tokenRequestRawString = `{ + "apiVersion": "authentication.k8s.io/v1", + "kind": "TokenRequest" + }` + + // Temporary file to store the token request + secretName := fmt.Sprintf("%s-token-request", serviceAccountName) + tokenRequestFile := filepath.Join("/tmp", secretName) + err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644)) + if err != nil { + return "", err + } + + var out string + verifyTokenCreation := func(g Gomega) { + // Execute kubectl command to create the token + cmd := exec.Command("kubectl", "create", "--raw", fmt.Sprintf( + "/api/v1/namespaces/%s/serviceaccounts/%s/token", + namespace, + serviceAccountName, + ), "-f", tokenRequestFile) + + output, err := cmd.CombinedOutput() + g.Expect(err).NotTo(HaveOccurred()) + + // Parse the JSON output to extract the token + var token tokenRequest + err = json.Unmarshal(output, &token) + g.Expect(err).NotTo(HaveOccurred()) + + out = token.Status.Token + } + Eventually(verifyTokenCreation).Should(Succeed()) + + return out, err +} + +// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint. +func getMetricsOutput() string { + By("getting the curl-metrics logs") + cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) + metricsOutput, err := utils.Run(cmd) + Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod") + Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK")) + return metricsOutput +} + +// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response, +// containing only the token field that we need to extract. +type tokenRequest struct { + Status struct { + Token string `json:"token"` + } `json:"status"` +} diff --git a/test/utils/utils.go b/test/utils/utils.go new file mode 100644 index 0000000..04a5141 --- /dev/null +++ b/test/utils/utils.go @@ -0,0 +1,251 @@ +/* +Copyright 2025. + +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 utils + +import ( + "bufio" + "bytes" + "fmt" + "os" + "os/exec" + "strings" + + . "github.com/onsi/ginkgo/v2" //nolint:golint,revive +) + +const ( + prometheusOperatorVersion = "v0.77.1" + prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + + "releases/download/%s/bundle.yaml" + + certmanagerVersion = "v1.16.3" + certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" +) + +func warnError(err error) { + _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) +} + +// Run executes the provided command within this context +func Run(cmd *exec.Cmd) (string, error) { + dir, _ := GetProjectDir() + cmd.Dir = dir + + if err := os.Chdir(cmd.Dir); err != nil { + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + } + + cmd.Env = append(os.Environ(), "GO111MODULE=on") + command := strings.Join(cmd.Args, " ") + _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + output, err := cmd.CombinedOutput() + if err != nil { + return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) + } + + return string(output), nil +} + +// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. +func InstallPrometheusOperator() error { + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) + cmd := exec.Command("kubectl", "create", "-f", url) + _, err := Run(cmd) + return err +} + +// UninstallPrometheusOperator uninstalls the prometheus +func UninstallPrometheusOperator() { + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) + cmd := exec.Command("kubectl", "delete", "-f", url) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed +// by verifying the existence of key CRDs related to Prometheus. +func IsPrometheusCRDsInstalled() bool { + // List of common Prometheus CRDs + prometheusCRDs := []string{ + "prometheuses.monitoring.coreos.com", + "prometheusrules.monitoring.coreos.com", + "prometheusagents.monitoring.coreos.com", + } + + cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") + output, err := Run(cmd) + if err != nil { + return false + } + crdList := GetNonEmptyLines(output) + for _, crd := range prometheusCRDs { + for _, line := range crdList { + if strings.Contains(line, crd) { + return true + } + } + } + + return false +} + +// UninstallCertManager uninstalls the cert manager +func UninstallCertManager() { + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) + cmd := exec.Command("kubectl", "delete", "-f", url) + if _, err := Run(cmd); err != nil { + warnError(err) + } +} + +// InstallCertManager installs the cert manager bundle. +func InstallCertManager() error { + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) + cmd := exec.Command("kubectl", "apply", "-f", url) + if _, err := Run(cmd); err != nil { + return err + } + // Wait for cert-manager-webhook to be ready, which can take time if cert-manager + // was re-installed after uninstalling on a cluster. + cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", + "--for", "condition=Available", + "--namespace", "cert-manager", + "--timeout", "5m", + ) + + _, err := Run(cmd) + return err +} + +// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed +// by verifying the existence of key CRDs related to Cert Manager. +func IsCertManagerCRDsInstalled() bool { + // List of common Cert Manager CRDs + certManagerCRDs := []string{ + "certificates.cert-manager.io", + "issuers.cert-manager.io", + "clusterissuers.cert-manager.io", + "certificaterequests.cert-manager.io", + "orders.acme.cert-manager.io", + "challenges.acme.cert-manager.io", + } + + // Execute the kubectl command to get all CRDs + cmd := exec.Command("kubectl", "get", "crds") + output, err := Run(cmd) + if err != nil { + return false + } + + // Check if any of the Cert Manager CRDs are present + crdList := GetNonEmptyLines(output) + for _, crd := range certManagerCRDs { + for _, line := range crdList { + if strings.Contains(line, crd) { + return true + } + } + } + + return false +} + +// LoadImageToKindClusterWithName loads a local docker image to the kind cluster +func LoadImageToKindClusterWithName(name string) error { + cluster := "kind" + if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { + cluster = v + } + kindOptions := []string{"load", "docker-image", name, "--name", cluster} + cmd := exec.Command("kind", kindOptions...) + _, err := Run(cmd) + return err +} + +// GetNonEmptyLines converts given command output string into individual objects +// according to line breakers, and ignores the empty elements in it. +func GetNonEmptyLines(output string) []string { + var res []string + elements := strings.Split(output, "\n") + for _, element := range elements { + if element != "" { + res = append(res, element) + } + } + + return res +} + +// GetProjectDir will return the directory where the project is +func GetProjectDir() (string, error) { + wd, err := os.Getwd() + if err != nil { + return wd, err + } + wd = strings.Replace(wd, "/test/e2e", "", -1) + return wd, nil +} + +// UncommentCode searches for target in the file and remove the comment prefix +// of the target content. The target content may span multiple lines. +func UncommentCode(filename, target, prefix string) error { + // false positive + // nolint:gosec + content, err := os.ReadFile(filename) + if err != nil { + return err + } + strContent := string(content) + + idx := strings.Index(strContent, target) + if idx < 0 { + return fmt.Errorf("unable to find the code %s to be uncomment", target) + } + + out := new(bytes.Buffer) + _, err = out.Write(content[:idx]) + if err != nil { + return err + } + + scanner := bufio.NewScanner(bytes.NewBufferString(target)) + if !scanner.Scan() { + return nil + } + for { + _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) + if err != nil { + return err + } + // Avoid writing a newline in case the previous line was the last in target. + if !scanner.Scan() { + break + } + if _, err := out.WriteString("\n"); err != nil { + return err + } + } + + _, err = out.Write(content[idx+len(target):]) + if err != nil { + return err + } + // false positive + // nolint:gosec + return os.WriteFile(filename, out.Bytes(), 0644) +} From 11adf7cedb3ea29b2cb2608fb360959fc2bbcc4a Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 13:00:57 -0500 Subject: [PATCH 03/35] chore: update license --- cmd/main.go | 17 +---------------- hack/boilerplate.go.txt | 16 +--------------- test/e2e/e2e_suite_test.go | 17 +---------------- test/e2e/e2e_test.go | 17 +---------------- test/utils/utils.go | 17 +---------------- 5 files changed, 5 insertions(+), 79 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 63179be..f020b2b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,19 +1,4 @@ -/* -Copyright 2025. - -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. -*/ - +// SPDX-License-Identifier: AGPL-3.0-only package main import ( diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 221dcbe..ea8ae64 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,15 +1 @@ -/* -Copyright 2025. - -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. -*/ \ No newline at end of file +// SPDX-License-Identifier: AGPL-3.0-only diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go index 4507a6b..142058a 100644 --- a/test/e2e/e2e_suite_test.go +++ b/test/e2e/e2e_suite_test.go @@ -1,19 +1,4 @@ -/* -Copyright 2025. - -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. -*/ - +// SPDX-License-Identifier: AGPL-3.0-only package e2e import ( diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index d77af78..09c5661 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -1,19 +1,4 @@ -/* -Copyright 2025. - -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. -*/ - +// SPDX-License-Identifier: AGPL-3.0-only package e2e import ( diff --git a/test/utils/utils.go b/test/utils/utils.go index 04a5141..00ff671 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -1,19 +1,4 @@ -/* -Copyright 2025. - -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. -*/ - +// SPDX-License-Identifier: AGPL-3.0-only package utils import ( From 737c4a003eebf2d38ec9d35f2e57b9cda336cdfe Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 14:47:31 -0500 Subject: [PATCH 04/35] chore: use sub-commands --- Dockerfile | 10 +- cmd/controller/manager.go | 274 ++++++++++++++++++++++++++++++++++++ cmd/main.go | 256 ++++++++------------------------- config/manager/manager.yaml | 9 +- go.mod | 46 +++--- go.sum | 93 +++++++----- 6 files changed, 427 insertions(+), 261 deletions(-) create mode 100644 cmd/controller/manager.go diff --git a/Dockerfile b/Dockerfile index 348b837..c6ef290 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,22 +12,20 @@ COPY go.sum go.sum RUN go mod download # Copy the go source -COPY cmd/main.go cmd/main.go -COPY api/ api/ -COPY internal/ internal/ +COPY cmd/ cmd/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. -RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go +RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o datum ./cmd/ # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details FROM gcr.io/distroless/static:nonroot WORKDIR / -COPY --from=builder /workspace/manager . +COPY --from=builder /workspace/datum . USER 65532:65532 -ENTRYPOINT ["/manager"] +ENTRYPOINT ["/datum"] diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go new file mode 100644 index 0000000..c7bdafd --- /dev/null +++ b/cmd/controller/manager.go @@ -0,0 +1,274 @@ +// SPDX-License-Identifier: AGPL-3.0-only +package controller + +import ( + "crypto/tls" + "flag" + "path/filepath" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/spf13/cobra" + // +kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + // +kubebuilder:scaffold:scheme +} + +// NewControllerManagerCommand creates a new controller-manager command +func NewControllerManagerCommand() *cobra.Command { + var metricsAddr string + var metricsCertPath, metricsCertName, metricsCertKey string + var webhookCertPath, webhookCertName, webhookCertKey string + var enableLeaderElection bool + var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + + cmd := &cobra.Command{ + Use: "controller-manager", + Short: "Run the Datum control plane controller manager", + Long: `The controller-manager extends the Milo control plane with Datum Cloud specific +functionality for AI-native infrastructure orchestration. + +This controller manager handles: +- Declarative management of Datum resources using Kubernetes API patterns +- Reconciliation of infrastructure state across heterogeneous environments +- Integration with pluggable infrastructure providers (GCP, AWS, bare metal, etc.) +- Management of Workloads, Networks, Gateways and other Datum primitives +- Support for GitOps workflows and infrastructure-as-code practices + +The controller ensures your declared infrastructure configuration is continuously +reconciled to match the desired state across your distributed infrastructure.`, + RunE: func(cmd *cobra.Command, args []string) error { + return runControllerManager( + metricsAddr, + metricsCertPath, metricsCertName, metricsCertKey, + webhookCertPath, webhookCertName, webhookCertKey, + enableLeaderElection, + probeAddr, + secureMetrics, + enableHTTP2, + ) + }, + } + + // Add flags + cmd.Flags().StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") + cmd.Flags().StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + cmd.Flags().BoolVar(&enableLeaderElection, "leader-elect", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + cmd.Flags().BoolVar(&secureMetrics, "metrics-secure", true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") + cmd.Flags().StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") + cmd.Flags().StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") + cmd.Flags().StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") + cmd.Flags().StringVar(&metricsCertPath, "metrics-cert-path", "", + "The directory that contains the metrics server certificate.") + cmd.Flags().StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") + cmd.Flags().StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") + cmd.Flags().BoolVar(&enableHTTP2, "enable-http2", false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers") + + // Add zap logging flags + opts := zap.Options{ + Development: true, + } + // Convert cobra pflag to standard flag for zap compatibility + cmd.Flags().AddGoFlagSet(flag.CommandLine) + opts.BindFlags(flag.CommandLine) + + return cmd +} + +// nolint:gocyclo +func runControllerManager( + metricsAddr string, + metricsCertPath, metricsCertName, metricsCertKey string, + webhookCertPath, webhookCertName, webhookCertKey string, + enableLeaderElection bool, + probeAddr string, + secureMetrics bool, + enableHTTP2 bool, +) error { + var tlsOpts []func(*tls.Config) + + // Initialize zap logger + opts := zap.Options{ + Development: true, + } + // Parse the command line flags to get the zap options + flag.Parse() + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + // Create watchers for metrics and webhooks certificates + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher + + // Initial webhook TLS options + webhookTLSOpts := tlsOpts + + if len(webhookCertPath) > 0 { + setupLog.Info("Initializing webhook certificate watcher using provided certificates", + "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) + + var err error + webhookCertWatcher, err = certwatcher.New( + filepath.Join(webhookCertPath, webhookCertName), + filepath.Join(webhookCertPath, webhookCertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize webhook certificate watcher") + return err + } + + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { + config.GetCertificate = webhookCertWatcher.GetCertificate + }) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: webhookTLSOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + // If the certificate is not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + // + // TODO(user): If you enable certManager, uncomment the following lines: + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates + // managed by cert-manager for the metrics server. + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. + if len(metricsCertPath) > 0 { + setupLog.Info("Initializing metrics certificate watcher using provided certificates", + "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) + + var err error + metricsCertWatcher, err = certwatcher.New( + filepath.Join(metricsCertPath, metricsCertName), + filepath.Join(metricsCertPath, metricsCertKey), + ) + if err != nil { + setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) + return err + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = metricsCertWatcher.GetCertificate + }) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "81afa9db.datumapis.com", + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily + // when the Manager ends. This requires the binary to immediately end when the + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly + // speeds up voluntary leader transitions as the new leader don't have to wait + // LeaseDuration time first. + // + // In the default scaffold provided, the program ends immediately after + // the manager stops, so would be fine to enable this option. However, + // if you are doing or is intended to do any operation such as perform cleanups + // after the manager stops then its usage might be unsafe. + // LeaderElectionReleaseOnCancel: true, + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + return err + } + + // +kubebuilder:scaffold:builder + + if metricsCertWatcher != nil { + setupLog.Info("Adding metrics certificate watcher to manager") + if err := mgr.Add(metricsCertWatcher); err != nil { + setupLog.Error(err, "unable to add metrics certificate watcher to manager") + return err + } + } + + if webhookCertWatcher != nil { + setupLog.Info("Adding webhook certificate watcher to manager") + if err := mgr.Add(webhookCertWatcher); err != nil { + setupLog.Error(err, "unable to add webhook certificate watcher to manager") + return err + } + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + return err + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + return err + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + return err + } + + return nil +} diff --git a/cmd/main.go b/cmd/main.go index f020b2b..6b8f6c9 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,217 +2,85 @@ package main import ( - "crypto/tls" - "flag" + "context" + "fmt" "os" - "path/filepath" - - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - // to ensure that exec-entrypoint and run can make use of them. - _ "k8s.io/client-go/plugin/pkg/client/auth" - - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/certwatcher" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/filters" - metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "sigs.k8s.io/controller-runtime/pkg/webhook" - // +kubebuilder:scaffold:imports + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "go.datum.net/datum/cmd/controller" ) var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + cfgFile string ) -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - // +kubebuilder:scaffold:scheme +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "datum", + Short: "Datum control plane tooling for AI-native infrastructure orchestration", + Long: `Datum is building the internet for AI - a neutral, programmable middle layer +where companies can programmatically connect without building the entire stack themselves. + +This component provides tooling for extending the Milo control plane with Datum Cloud +specific functionality, enabling AI-native infrastructure orchestration using +Kubernetes API patterns and declarative management. + +Key capabilities: +- AI-native principles with developer and agent-friendly interfaces +- Neutral by design with no allegiance to a single cloud or vendor +- Fully programmable using Kubernetes API patterns +- Support for bring-your-own infrastructure +- Comprehensive observability and determinism`, } -// nolint:gocyclo -func main() { - var metricsAddr string - var metricsCertPath, metricsCertName, metricsCertKey string - var webhookCertPath, webhookCertName, webhookCertKey string - var enableLeaderElection bool - var probeAddr string - var secureMetrics bool - var enableHTTP2 bool - var tlsOpts []func(*tls.Config) - flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ - "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") - flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - flag.BoolVar(&secureMetrics, "metrics-secure", true, - "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") - flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") - flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") - flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") - flag.StringVar(&metricsCertPath, "metrics-cert-path", "", - "The directory that contains the metrics server certificate.") - flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") - flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") - flag.BoolVar(&enableHTTP2, "enable-http2", false, - "If set, HTTP/2 will be enabled for the metrics and webhook servers") - opts := zap.Options{ - Development: true, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - // if the enable-http2 flag is false (the default), http/2 should be disabled - // due to its vulnerabilities. More specifically, disabling http/2 will - // prevent from being vulnerable to the HTTP/2 Stream Cancellation and - // Rapid Reset CVEs. For more information see: - // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 - // - https://github.com/advisories/GHSA-4374-p667-p6c8 - disableHTTP2 := func(c *tls.Config) { - setupLog.Info("disabling http/2") - c.NextProtos = []string{"http/1.1"} - } - - if !enableHTTP2 { - tlsOpts = append(tlsOpts, disableHTTP2) - } - - // Create watchers for metrics and webhooks certificates - var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher - - // Initial webhook TLS options - webhookTLSOpts := tlsOpts - - if len(webhookCertPath) > 0 { - setupLog.Info("Initializing webhook certificate watcher using provided certificates", - "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) - - var err error - webhookCertWatcher, err = certwatcher.New( - filepath.Join(webhookCertPath, webhookCertName), - filepath.Join(webhookCertPath, webhookCertKey), - ) - if err != nil { - setupLog.Error(err, "Failed to initialize webhook certificate watcher") - os.Exit(1) - } - - webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { - config.GetCertificate = webhookCertWatcher.GetCertificate - }) - } - - webhookServer := webhook.NewServer(webhook.Options{ - TLSOpts: webhookTLSOpts, - }) - - // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. - // More info: - // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/server - // - https://book.kubebuilder.io/reference/metrics.html - metricsServerOptions := metricsserver.Options{ - BindAddress: metricsAddr, - SecureServing: secureMetrics, - TLSOpts: tlsOpts, +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.ExecuteContext(context.Background()) + if err != nil { + os.Exit(1) } +} - if secureMetrics { - // FilterProvider is used to protect the metrics endpoint with authn/authz. - // These configurations ensure that only authorized users and service accounts - // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: - // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/metrics/filters#WithAuthenticationAndAuthorization - metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization - } +func init() { + cobra.OnInitialize(initConfig) - // If the certificate is not specified, controller-runtime will automatically - // generate self-signed certificates for the metrics server. While convenient for development and testing, - // this setup is not recommended for production. - // - // TODO(user): If you enable certManager, uncomment the following lines: - // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates - // managed by cert-manager for the metrics server. - // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. - if len(metricsCertPath) > 0 { - setupLog.Info("Initializing metrics certificate watcher using provided certificates", - "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) - - var err error - metricsCertWatcher, err = certwatcher.New( - filepath.Join(metricsCertPath, metricsCertName), - filepath.Join(metricsCertPath, metricsCertKey), - ) - if err != nil { - setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) - os.Exit(1) - } - - metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { - config.GetCertificate = metricsCertWatcher.GetCertificate - }) - } + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Metrics: metricsServerOptions, - WebhookServer: webhookServer, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "81afa9db.datumapis.com", - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, - }) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.datum.yaml)") - // +kubebuilder:scaffold:builder + // Add subcommands + rootCmd.AddCommand(controller.NewControllerManagerCommand()) +} - if metricsCertWatcher != nil { - setupLog.Info("Adding metrics certificate watcher to manager") - if err := mgr.Add(metricsCertWatcher); err != nil { - setupLog.Error(err, "unable to add metrics certificate watcher to manager") - os.Exit(1) - } +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".datum" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".datum") } - if webhookCertWatcher != nil { - setupLog.Info("Adding webhook certificate watcher to manager") - if err := mgr.Add(webhookCertWatcher); err != nil { - setupLog.Error(err, "unable to add webhook certificate watcher to manager") - os.Exit(1) - } - } + viper.AutomaticEnv() // read in environment variables that match - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } +} - setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) - } +func main() { + Execute() } diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 69f2bd3..d8c66a9 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -58,13 +58,12 @@ spec: seccompProfile: type: RuntimeDefault containers: - - command: - - /manager - args: + - args: + - controller-manager - --leader-elect - --health-probe-bind-address=:8081 - image: controller:latest - name: manager + image: ghcr.io/datum-cloud/datum:latest + name: datum-controller-manager ports: [] securityContext: allowPrivilegeEscalation: false diff --git a/go.mod b/go.mod index 9a57008..0704d3b 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,8 @@ godebug default=go1.24 require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.20.1 k8s.io/apimachinery v0.32.1 k8s.io/client-go v0.32.1 sigs.k8s.io/controller-runtime v0.20.4 @@ -24,7 +26,7 @@ require ( github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -33,6 +35,7 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect @@ -50,39 +53,44 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/spf13/cobra v1.8.1 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.29.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/trace v1.29.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/oauth2 v0.25.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 // indirect - google.golang.org/grpc v1.65.0 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect + google.golang.org/grpc v1.67.3 // indirect + google.golang.org/protobuf v1.36.1 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 3719e6c..1a400bd 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,10 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -47,6 +49,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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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= @@ -97,6 +101,8 @@ 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/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -113,10 +119,21 @@ github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoG github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -126,26 +143,28 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= +go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -165,28 +184,28 @@ 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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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= @@ -199,14 +218,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7 h1:YcyjlL1PRr2Q17/I0dPk2JmYS5CDXfcdb2Z3YRioEbw= -google.golang.org/genproto/googleapis/api v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:OCdP9MfskevB/rbYvHTsXTtKC+3bHWajPdoKgjcYkfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7 h1:2035KHhUv+EpyB+hWgJnaWKJOdX1E95w2S8Rr4uWKTs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240826202546-f6391c0de4c7/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= +google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From b2f5b0652355c786c9741428e6832f978fe04e2e Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 14:53:22 -0500 Subject: [PATCH 05/35] chore: update command descriptions --- cmd/controller/manager.go | 13 +------------ cmd/main.go | 10 +--------- 2 files changed, 2 insertions(+), 21 deletions(-) diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go index c7bdafd..3690705 100644 --- a/cmd/controller/manager.go +++ b/cmd/controller/manager.go @@ -49,18 +49,7 @@ func NewControllerManagerCommand() *cobra.Command { cmd := &cobra.Command{ Use: "controller-manager", Short: "Run the Datum control plane controller manager", - Long: `The controller-manager extends the Milo control plane with Datum Cloud specific -functionality for AI-native infrastructure orchestration. - -This controller manager handles: -- Declarative management of Datum resources using Kubernetes API patterns -- Reconciliation of infrastructure state across heterogeneous environments -- Integration with pluggable infrastructure providers (GCP, AWS, bare metal, etc.) -- Management of Workloads, Networks, Gateways and other Datum primitives -- Support for GitOps workflows and infrastructure-as-code practices - -The controller ensures your declared infrastructure configuration is continuously -reconciled to match the desired state across your distributed infrastructure.`, + Long: `The controller-manager extends the Milo control plane with Datum Cloud specific functionality.`, RunE: func(cmd *cobra.Command, args []string) error { return runControllerManager( metricsAddr, diff --git a/cmd/main.go b/cmd/main.go index 6b8f6c9..3eb4a61 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -24,15 +24,7 @@ var rootCmd = &cobra.Command{ where companies can programmatically connect without building the entire stack themselves. This component provides tooling for extending the Milo control plane with Datum Cloud -specific functionality, enabling AI-native infrastructure orchestration using -Kubernetes API patterns and declarative management. - -Key capabilities: -- AI-native principles with developer and agent-friendly interfaces -- Neutral by design with no allegiance to a single cloud or vendor -- Fully programmable using Kubernetes API patterns -- Support for bring-your-own infrastructure -- Comprehensive observability and determinism`, +specific functionality.`, } // Execute adds all child commands to the root command and sets flags appropriately. From d183c2efca43d5db4a38e4c3c6f16212df6f0b14 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 16:51:08 -0500 Subject: [PATCH 06/35] chore: create personal organizations --- .gitignore | 3 + Dockerfile | 1 + Makefile | 10 +- PROJECT | 7 + cmd/controller/manager.go | 37 ++++ cmd/main.go | 34 ---- go.mod | 62 +++---- go.sum | 131 +++++++------- internal/config/config.go | 166 ++++++++++++++++++ internal/config/groupversion_info.go | 17 ++ internal/config/zz_generated.deepcopy.go | 79 +++++++++ internal/config/zz_generated.defaults.go | 23 +++ .../personal_organization_controller.go | 156 ++++++++++++++++ .../personal_organization_controller_test.go | 18 ++ .../controller/resourcemanager/suite_test.go | 101 +++++++++++ 15 files changed, 720 insertions(+), 125 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/groupversion_info.go create mode 100644 internal/config/zz_generated.deepcopy.go create mode 100644 internal/config/zz_generated.defaults.go create mode 100644 internal/controller/resourcemanager/personal_organization_controller.go create mode 100644 internal/controller/resourcemanager/personal_organization_controller_test.go create mode 100644 internal/controller/resourcemanager/suite_test.go diff --git a/.gitignore b/.gitignore index ada68ff..302a676 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ go.work *.swp *.swo *~ + +# Ignore kubeconfig +.kube diff --git a/Dockerfile b/Dockerfile index c6ef290..21c62d7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,7 @@ RUN go mod download # Copy the go source COPY cmd/ cmd/ +COPY internal/ internal/ # Build # the GOARCH has not a default value to allow the binary be built according to the host where the command diff --git a/Makefile b/Makefile index b491df7..3793cf2 100644 --- a/Makefile +++ b/Makefile @@ -46,8 +46,9 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +generate: controller-gen defaulter-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + $(DEFAULTER_GEN) ./internal/config --output-file=zz_generated.defaults.go .PHONY: fmt fmt: ## Run go fmt against code. @@ -168,12 +169,14 @@ KUBECTL ?= kubectl KIND ?= kind KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +DEFAULTER_GEN ?= $(LOCALBIN)/defaulter-gen ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions KUSTOMIZE_VERSION ?= v5.6.0 CONTROLLER_TOOLS_VERSION ?= v0.17.2 +DEFAULTER_GEN_VERSION ?= v0.32.3 #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) @@ -190,6 +193,11 @@ controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessar $(CONTROLLER_GEN): $(LOCALBIN) $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) +.PHONY: defaulter-gen +defaulter-gen: $(DEFAULTER_GEN) ## Download defaulter-gen locally if necessary. +$(DEFAULTER_GEN): $(LOCALBIN) + $(call go-install-tool,$(DEFAULTER_GEN),k8s.io/code-generator/cmd/defaulter-gen,$(DEFAULTER_GEN_VERSION)) + .PHONY: setup-envtest setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory. @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..." diff --git a/PROJECT b/PROJECT index 804d72b..a946dcc 100644 --- a/PROJECT +++ b/PROJECT @@ -7,4 +7,11 @@ layout: - go.kubebuilder.io/v4 projectName: datum-cloud repo: go.datum.net/datum +resources: +- controller: true + domain: iam.datumapis.com + external: true + kind: User + path: go.miloapis.com/milo/pkg/apis/iam/v1alpha1 + version: v1alpha1 version: "3" diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go index 3690705..8a8b1ec 100644 --- a/cmd/controller/manager.go +++ b/cmd/controller/manager.go @@ -4,6 +4,8 @@ package controller import ( "crypto/tls" "flag" + "fmt" + "os" "path/filepath" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -11,6 +13,7 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -23,15 +26,22 @@ import ( "github.com/spf13/cobra" // +kubebuilder:scaffold:imports + "go.datum.net/datum/internal/config" + resourcemanagercontroller "go.datum.net/datum/internal/controller/resourcemanager" + iamv1alpha1 "go.miloapis.com/milo/pkg/apis/iam/v1alpha1" + resourcemanagerv1alpha1 "go.miloapis.com/milo/pkg/apis/resourcemanager/v1alpha1" ) var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") + codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(iamv1alpha1.AddToScheme(scheme)) + utilruntime.Must(resourcemanagerv1alpha1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } @@ -45,6 +55,7 @@ func NewControllerManagerCommand() *cobra.Command { var probeAddr string var secureMetrics bool var enableHTTP2 bool + var serverConfigFile string cmd := &cobra.Command{ Use: "controller-manager", @@ -56,6 +67,7 @@ func NewControllerManagerCommand() *cobra.Command { metricsCertPath, metricsCertName, metricsCertKey, webhookCertPath, webhookCertName, webhookCertKey, enableLeaderElection, + serverConfigFile, probeAddr, secureMetrics, enableHTTP2, @@ -81,6 +93,7 @@ func NewControllerManagerCommand() *cobra.Command { cmd.Flags().StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") cmd.Flags().BoolVar(&enableHTTP2, "enable-http2", false, "If set, HTTP/2 will be enabled for the metrics and webhook servers") + cmd.Flags().StringVar(&serverConfigFile, "config", "", "path to the controller manager config file") // Add zap logging flags opts := zap.Options{ @@ -99,6 +112,7 @@ func runControllerManager( metricsCertPath, metricsCertName, metricsCertKey string, webhookCertPath, webhookCertName, webhookCertKey string, enableLeaderElection bool, + serverConfigFile string, probeAddr string, secureMetrics bool, enableHTTP2 bool, @@ -128,6 +142,21 @@ func runControllerManager( tlsOpts = append(tlsOpts, disableHTTP2) } + var serverConfig config.DatumControllerManager + var configData []byte + if len(serverConfigFile) > 0 { + var err error + configData, err = os.ReadFile(serverConfigFile) + if err != nil { + setupLog.Error(fmt.Errorf("unable to read server config from %q", serverConfigFile), "") + os.Exit(1) + } + } + + if err := runtime.DecodeInto(codecs.UniversalDecoder(), configData, &serverConfig); err != nil { + return fmt.Errorf("unable to decode server config: %w", err) + } + // Create watchers for metrics and webhooks certificates var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher @@ -226,6 +255,14 @@ func runControllerManager( return err } + if err = (&resourcemanagercontroller.PersonalOrganizationController{ + Client: mgr.GetClient(), + Config: serverConfig.PersonalOrganizationController, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PersonalOrganization") + return err + } + // +kubebuilder:scaffold:builder if metricsCertWatcher != nil { diff --git a/cmd/main.go b/cmd/main.go index 3eb4a61..ccdc43a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,11 +3,9 @@ package main import ( "context" - "fmt" "os" "github.com/spf13/cobra" - "github.com/spf13/viper" "go.datum.net/datum/cmd/controller" ) @@ -37,42 +35,10 @@ func Execute() { } func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.datum.yaml)") - // Add subcommands rootCmd.AddCommand(controller.NewControllerManagerCommand()) } -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := os.UserHomeDir() - cobra.CheckErr(err) - - // Search config in home directory with name ".datum" (without extension). - viper.AddConfigPath(home) - viper.SetConfigType("yaml") - viper.SetConfigName(".datum") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) - } -} - func main() { Execute() } diff --git a/go.mod b/go.mod index 0704d3b..fcb2be3 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,14 @@ require ( github.com/onsi/gomega v1.36.1 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.20.1 - k8s.io/apimachinery v0.32.1 + go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7 + k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.32.1 sigs.k8s.io/controller-runtime v0.20.4 ) require ( - cel.dev/expr v0.18.0 // indirect + cel.dev/expr v0.19.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -27,8 +28,8 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/fxamacker/cbor/v2 v2.8.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -40,12 +41,11 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/cel-go v0.22.0 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -67,30 +67,31 @@ require ( github.com/stoewer/go-strcase v1.3.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect - go.opentelemetry.io/otel v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect - go.opentelemetry.io/otel/metric v1.29.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect - go.opentelemetry.io/otel/trace v1.29.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect + go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.35.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.35.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.33.0 // indirect + golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.27.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/time v0.8.0 // indirect - golang.org/x/tools v0.26.0 // indirect + golang.org/x/sync v0.15.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect + golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.33.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect - google.golang.org/grpc v1.67.3 // indirect - google.golang.org/protobuf v1.36.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/grpc v1.71.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -99,10 +100,11 @@ require ( k8s.io/apiserver v0.32.1 // indirect k8s.io/component-base v0.32.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 1a400bd..77f0192 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.18.0 h1:CJ6drgk+Hf96lkLikr4rFf19WrU0BOWEihyZnI2TAzo= -cel.dev/expr v0.18.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA= @@ -30,11 +30,11 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -59,11 +59,11 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= +github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 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= @@ -71,8 +71,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -116,8 +116,8 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +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/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= @@ -151,20 +151,28 @@ 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.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= -go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= -go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= -go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= -go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= -go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.miloapis.com/milo v0.0.0-20250624185205-374dc7fff291 h1:LKCMBlDlrFbHxW04r16R4NjD3nljTkGjB7PglpGjMz0= +go.miloapis.com/milo v0.0.0-20250624185205-374dc7fff291/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= +go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7 h1:VTo62HkAfnlqJixH1nF3ktl/+Z/dZ/CemNVEIjnvzK8= +go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -184,48 +192,48 @@ 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.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +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/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +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/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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 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.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= -google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v1.67.3 h1:OgPcDAFKHnH8X3O4WcO4XUc8GRDeKsKReqbQtiCj7N8= -google.golang.org/grpc v1.67.3/go.mod h1:YGaHCc6Oap+FzBJTZLBzkGSYt/cvGPFTPxkn7QfSU8s= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -240,8 +248,8 @@ k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs= -k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/apimachinery v0.33.2 h1:IHFVhqg59mb8PJWTLi8m1mAoepkUNYmptHsV+Z1m5jY= +k8s.io/apimachinery v0.33.2/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/apiserver v0.32.1 h1:oo0OozRos66WFq87Zc5tclUX2r0mymoVHRq8JmR7Aak= k8s.io/apiserver v0.32.1/go.mod h1:UcB9tWjBY7aryeI5zAgzVJB/6k7E97bkr1RgqDz0jPw= k8s.io/client-go v0.32.1 h1:otM0AxdhdBIaQh7l1Q0jQpmo7WOFIk5FFa4bg6YMdUU= @@ -250,17 +258,20 @@ k8s.io/component-base v0.32.1 h1:/5IfJ0dHIKBWysGV0yKTFfacZ5yNV1sulPh3ilJjRZk= k8s.io/component-base v0.32.1/go.mod h1:j1iMMHi/sqAHeG5z+O9BFNCF698a1u0186zkjMZQ28w= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= +k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= +k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 h1:CPT0ExVicCzcpeN4baWEV2ko2Z/AsiZgEdwgcfwLgMo= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= +sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..1d474f0 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,166 @@ +package config + +import ( + "context" + "crypto/tls" + "fmt" + "os" + "path/filepath" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + resourcemanagercontroller "go.datum.net/datum/internal/controller/resourcemanager" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +k8s:defaulter-gen=true + +type DatumControllerManager struct { + metav1.TypeMeta + + // MetricsServer is the configuration for the metrics server. + MetricsServer MetricsServerConfig `json:"metricsServer"` + + // PersonalOrganizationController is the configuration for the personal + // organization controller. + PersonalOrganizationController resourcemanagercontroller.PersonalOrganizationControllerConfig `json:"personalOrganizationController"` +} + +// +k8s:deepcopy-gen=true + +type MetricsServerConfig struct { + // SecureServing enables serving metrics via https. + // Per default metrics will be served via http. + SecureServing *bool `json:"secureServing,omitempty"` + + // BindAddress is the bind address for the metrics server. + // It will be defaulted to "0" if unspecified. + // Use :8443 for HTTPS or :8080 for HTTP + // + // Set this to "0" to disable the metrics server. + BindAddress string `json:"bindAddress"` + + // TLS is the TLS configuration for the metrics server, allowing configuration + // of what path to find a certificate and key in, and what file names to use. + TLS TLSConfig `json:"tls"` +} + +func SetDefaults_MetricsServerConfig(obj *MetricsServerConfig) { + if obj.SecureServing == nil { + obj.SecureServing = ptr.To(true) + } + + if obj.BindAddress == "" { + obj.BindAddress = "0" + } +} + +func (c *MetricsServerConfig) Options(ctx context.Context, secretsClient client.Client) metricsserver.Options { + opts := metricsserver.Options{ + SecureServing: *c.SecureServing, + BindAddress: c.BindAddress, + CertDir: c.TLS.CertDir, + CertName: c.TLS.CertName, + KeyName: c.TLS.KeyName, + } + + if *c.SecureServing { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.1/pkg/metrics/filters#WithAuthenticationAndAuthorization + opts.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + if secretRef := c.TLS.SecretRef; secretRef != nil { + opts.TLSOpts = c.TLS.Options(ctx, secretsClient) + } + + return opts +} + +// +k8s:deepcopy-gen=true + +type TLSConfig struct { + // SecretRef is a reference to a secret that contains the server key and + // certificate. If provided, CertDir will be ignored, and CertName and KeyName + // will be used as key names in the secret data. + // + // Note: This option is not currently recommended for production, as the secret + // will be read from the API on every request. + SecretRef *corev1.ObjectReference `json:"secretRef,omitempty"` + + // CertDir is the directory that contains the server key and certificate. Defaults to + // /k8s-webhook-server/serving-certs. + CertDir string `json:"certDir"` + + // CertName is the server certificate name. Defaults to tls.crt. + // + // Note: This option is only used when TLSOpts does not set GetCertificate. + CertName string `json:"certName"` + + // KeyName is the server key name. Defaults to tls.key. + // + // Note: This option is only used when TLSOpts does not set GetCertificate. + KeyName string `json:"keyName"` +} + +func (c *TLSConfig) Options(ctx context.Context, secretsClient client.Client) []func(*tls.Config) { + var tlsOpts []func(*tls.Config) + + if secretRef := c.SecretRef; secretRef != nil { + tlsOpts = append(tlsOpts, func(c *tls.Config) { + logger := ctrl.Log.WithName("webhook-tls-client") + c.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { + logger.Info("getting certificate") + + // Look at https://github.com/cert-manager/cert-manager/blob/master/pkg/server/tls/dynamic_source.go + + // TODO(jreese) caching & background refresh + + var secret corev1.Secret + secretObjectKey := types.NamespacedName{ + Name: secretRef.Name, + Namespace: secretRef.Namespace, + } + if err := secretsClient.Get(ctx, secretObjectKey, &secret); err != nil { + return nil, fmt.Errorf("failed to get secret: %w", err) + } + + cert, err := tls.X509KeyPair(secret.Data["tls.crt"], secret.Data["tls.key"]) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %w", err) + } + + return &cert, nil + } + }) + } + + return tlsOpts +} + +func SetDefaults_TLSConfig(obj *TLSConfig) { + if len(obj.CertDir) == 0 { + obj.CertDir = filepath.Join(os.TempDir(), "k8s-metrics-server", "serving-certs") + } + + if len(obj.CertName) == 0 { + obj.CertName = "tls.crt" + } + + if len(obj.KeyName) == 0 { + obj.KeyName = "tls.key" + } +} + +func init() { + SchemeBuilder.Register(&DatumControllerManager{}) +} diff --git a/internal/config/groupversion_info.go b/internal/config/groupversion_info.go new file mode 100644 index 0000000..df87c6c --- /dev/null +++ b/internal/config/groupversion_info.go @@ -0,0 +1,17 @@ +package config + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects. + GroupVersion = schema.GroupVersion{Group: "apiserver.config.datumapis.com", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme. + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/internal/config/zz_generated.deepcopy.go b/internal/config/zz_generated.deepcopy.go new file mode 100644 index 0000000..8a4368e --- /dev/null +++ b/internal/config/zz_generated.deepcopy.go @@ -0,0 +1,79 @@ +//go:build !ignore_autogenerated + +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by controller-gen. DO NOT EDIT. + +package config + +import ( + "k8s.io/api/core/v1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DatumControllerManager) DeepCopyInto(out *DatumControllerManager) { + *out = *in + out.TypeMeta = in.TypeMeta + in.MetricsServer.DeepCopyInto(&out.MetricsServer) + out.PersonalOrganizationController = in.PersonalOrganizationController +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DatumControllerManager. +func (in *DatumControllerManager) DeepCopy() *DatumControllerManager { + if in == nil { + return nil + } + out := new(DatumControllerManager) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *DatumControllerManager) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MetricsServerConfig) DeepCopyInto(out *MetricsServerConfig) { + *out = *in + if in.SecureServing != nil { + in, out := &in.SecureServing, &out.SecureServing + *out = new(bool) + **out = **in + } + in.TLS.DeepCopyInto(&out.TLS) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MetricsServerConfig. +func (in *MetricsServerConfig) DeepCopy() *MetricsServerConfig { + if in == nil { + return nil + } + out := new(MetricsServerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + if in.SecretRef != nil { + in, out := &in.SecretRef, &out.SecretRef + *out = new(v1.ObjectReference) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} diff --git a/internal/config/zz_generated.defaults.go b/internal/config/zz_generated.defaults.go new file mode 100644 index 0000000..728aa63 --- /dev/null +++ b/internal/config/zz_generated.defaults.go @@ -0,0 +1,23 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// Code generated by defaulter-gen. DO NOT EDIT. + +package config + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&DatumControllerManager{}, func(obj interface{}) { SetObjectDefaults_DatumControllerManager(obj.(*DatumControllerManager)) }) + return nil +} + +func SetObjectDefaults_DatumControllerManager(in *DatumControllerManager) { + SetDefaults_MetricsServerConfig(&in.MetricsServer) + SetDefaults_TLSConfig(&in.MetricsServer.TLS) +} diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go new file mode 100644 index 0000000..bd9f5e1 --- /dev/null +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package resourcemanager + +import ( + "context" + "encoding/hex" + "fmt" + "hash/fnv" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + iamv1alpha1 "go.miloapis.com/milo/pkg/apis/iam/v1alpha1" + resourcemanagerv1alpha1 "go.miloapis.com/milo/pkg/apis/resourcemanager/v1alpha1" +) + +type PersonalOrganizationControllerConfig struct { + RoleName string `json:"roleName"` + RoleNamespace string `json:"roleNamespace"` +} + +// PersonalOrganizationController reconciles a User object +type PersonalOrganizationController struct { + Client client.Client + + Config PersonalOrganizationControllerConfig +} + +// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users/finalizers,verbs=update +// +kubebuilder:rbac:groups=resourcemanager.datumapis.com,resources=organizations,verbs=create;delete + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.20.4/pkg/reconcile +func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := logf.FromContext(ctx) + + // Get the user. + user := &iamv1alpha1.User{} + if err := r.Client.Get(ctx, req.NamespacedName, user); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to get user: %w", err) + } + + // Automatically create a personal organization for the user. They should not + // be able to modify or delete the organization. + personalOrg := &resourcemanagerv1alpha1.Organization{ + ObjectMeta: metav1.ObjectMeta{ + // Create a unique name for the personal organization. + Name: fmt.Sprintf("personal-org-%s", hashPersonalOrgName(string(user.UID))), + Annotations: map[string]string{ + "kubernetes.io/display-name": fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName), + }, + OwnerReferences: []metav1.OwnerReference{ + // The owner reference is used to ensure that the personal organization + // is deleted when the user is deleted. + { + APIVersion: iamv1alpha1.SchemeGroupVersion.String(), + Kind: "User", + Name: user.Name, + UID: user.UID, + }, + }, + }, + Spec: resourcemanagerv1alpha1.OrganizationSpec{ + Type: "Personal", + }, + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { + logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) + personalOrg.Annotations["kubernetes.io/display-name"] = fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName) + return nil + }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create or update personal organization: %w", err) + } + + // Now we need to create the OrganizationMembership for the user to grant them + // access to the personal organization. + membership := &resourcemanagerv1alpha1.OrganizationMembership{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("membership-%s", user.Name), + Namespace: fmt.Sprintf("organization-%s", personalOrg.Name), + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, r.Client, membership, func() error { + membership.Spec = resourcemanagerv1alpha1.OrganizationMembershipSpec{ + OrganizationRef: resourcemanagerv1alpha1.OrganizationReference{ + Name: personalOrg.Name, + }, + UserRef: resourcemanagerv1alpha1.MemberReference{ + Name: user.Name, + }, + } + return nil + }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create or update organization membership: %w", err) + } + + // Assign the default role to the user. + policyBinding := &iamv1alpha1.PolicyBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("policy-binding-%s", user.Name), + Namespace: fmt.Sprintf("organization-%s", personalOrg.Name), + }, + } + + _, err = controllerutil.CreateOrUpdate(ctx, r.Client, policyBinding, func() error { + policyBinding.Spec = iamv1alpha1.PolicyBindingSpec{ + RoleRef: iamv1alpha1.RoleReference{ + Name: r.Config.RoleName, + Namespace: r.Config.RoleNamespace, + }, + Subjects: []iamv1alpha1.Subject{ + { + Kind: "User", + Name: user.Name, + UID: string(user.UID), + }, + }, + } + + return nil + }) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to create or update policy binding: %w", err) + } + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PersonalOrganizationController) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&iamv1alpha1.User{}). + Named("personal-organization"). + Complete(r) +} + +func hashPersonalOrgName(name string) string { + hasher := fnv.New32a() + //revive:disable-next-line:unhandled-error a + hasher.Write([]byte(name)) + + return hex.EncodeToString(hasher.Sum(nil)) +} diff --git a/internal/controller/resourcemanager/personal_organization_controller_test.go b/internal/controller/resourcemanager/personal_organization_controller_test.go new file mode 100644 index 0000000..156023a --- /dev/null +++ b/internal/controller/resourcemanager/personal_organization_controller_test.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package resourcemanager + +import ( + . "github.com/onsi/ginkgo/v2" +) + +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. + }) + }) +}) diff --git a/internal/controller/resourcemanager/suite_test.go b/internal/controller/resourcemanager/suite_test.go new file mode 100644 index 0000000..8d90568 --- /dev/null +++ b/internal/controller/resourcemanager/suite_test.go @@ -0,0 +1,101 @@ +// 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 "" +} From 2b70678c5e719846856b7732ad69770443dca23f Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 16:54:07 -0500 Subject: [PATCH 07/35] chore: publish artifacts --- .github/workflows/build-and-test.yaml | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/build-and-test.yaml diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml new file mode 100644 index 0000000..007dae2 --- /dev/null +++ b/.github/workflows/build-and-test.yaml @@ -0,0 +1,28 @@ +name: Build and Test + +on: + push: + pull_request: + +jobs: + publish-container-image: + permissions: + id-token: write + contents: read + packages: write + uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.0 + with: + image-name: datum + secrets: inherit + + + publish-kustomize-bundles: + permissions: + id-token: write + contents: read + packages: write + uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.5.1 + with: + bundle-name: ghcr.io/datum-cloud/datum-kustomize + bundle-path: config + secrets: inherit From 4bcb48de3b8b186bc7ec2c96246ff091e749bee1 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 17:17:59 -0500 Subject: [PATCH 08/35] chore: update golang image --- .devcontainer/devcontainer.json | 3 +-- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 0e0eed2..30912a8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,6 +1,6 @@ { "name": "Kubebuilder DevContainer", - "image": "docker.io/golang:1.23", + "image": "docker.io/golang:1.24", "features": { "ghcr.io/devcontainers/features/docker-in-docker:2": {}, "ghcr.io/devcontainers/features/git:1": {} @@ -22,4 +22,3 @@ "onCreateCommand": "bash .devcontainer/post-install.sh" } - diff --git a/Dockerfile b/Dockerfile index 21c62d7..7e6db25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM docker.io/golang:1.23 AS builder +FROM docker.io/golang:1.24 AS builder ARG TARGETOS ARG TARGETARCH From b147fe32fa465307ebfc217856923ae526eb3c4b Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 17:26:59 -0500 Subject: [PATCH 09/35] chore: update image build --- .github/workflows/build-and-test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 007dae2..0bafcc7 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -10,7 +10,7 @@ jobs: id-token: write contents: read packages: write - uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.0 + uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.1 with: image-name: datum secrets: inherit From 91e8ee843f611681908387034cf09b6970c2400f Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 17:54:56 -0500 Subject: [PATCH 10/35] chore: remove namespace config --- config/manager/manager.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index d8c66a9..c1c6ce8 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -11,7 +11,6 @@ apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager - namespace: system labels: control-plane: controller-manager app.kubernetes.io/name: datum From acfb998eb426afb3a8a5f6769246a49e94c03975 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 17:55:39 -0500 Subject: [PATCH 11/35] chore: remove default namespace --- config/manager/manager.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index c1c6ce8..d31f288 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -1,12 +1,3 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: datum - app.kubernetes.io/managed-by: kustomize - name: system ---- apiVersion: apps/v1 kind: Deployment metadata: From e00259b6733b630be13114620d4e3ce023cfb740 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 18:05:33 -0500 Subject: [PATCH 12/35] chore: append arg --- config/default/manager_metrics_patch.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default/manager_metrics_patch.yaml b/config/default/manager_metrics_patch.yaml index 2aaef65..8e0cedb 100644 --- a/config/default/manager_metrics_patch.yaml +++ b/config/default/manager_metrics_patch.yaml @@ -1,4 +1,4 @@ # This patch adds the args to allow exposing the metrics endpoint using HTTPS - op: add - path: /spec/template/spec/containers/0/args/0 + path: /spec/template/spec/containers/0/args/- value: --metrics-bind-address=:8443 From ac6d6f33cd63e22ac2529ec9c09daf699029ad77 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 18:16:09 -0500 Subject: [PATCH 13/35] chore: make leadership election configurable --- cmd/controller/manager.go | 65 +++++++++++++++++++++++++++---------- config/manager/manager.yaml | 57 ++++++++++++++++++++++++++++++-- 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go index 8a8b1ec..7474ce7 100644 --- a/cmd/controller/manager.go +++ b/cmd/controller/manager.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. @@ -52,6 +53,12 @@ func NewControllerManagerCommand() *cobra.Command { var metricsCertPath, metricsCertName, metricsCertKey string var webhookCertPath, webhookCertName, webhookCertKey string var enableLeaderElection bool + var leaderElectionID string + var leaderElectionNamespace string + var leaderElectionLeaseDuration time.Duration + var leaderElectionRenewDeadline time.Duration + var leaderElectionRetryPeriod time.Duration + var leaderElectionReleaseOnCancel bool var probeAddr string var secureMetrics bool var enableHTTP2 bool @@ -67,6 +74,12 @@ func NewControllerManagerCommand() *cobra.Command { metricsCertPath, metricsCertName, metricsCertKey, webhookCertPath, webhookCertName, webhookCertKey, enableLeaderElection, + leaderElectionID, + leaderElectionNamespace, + leaderElectionLeaseDuration, + leaderElectionRenewDeadline, + leaderElectionRetryPeriod, + leaderElectionReleaseOnCancel, serverConfigFile, probeAddr, secureMetrics, @@ -79,9 +92,27 @@ func NewControllerManagerCommand() *cobra.Command { cmd.Flags().StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") cmd.Flags().StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + + // Leader election flags cmd.Flags().BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") + cmd.Flags().StringVar(&leaderElectionID, "leader-election-id", "81afa9db.datumapis.com", + "The name of the resource that leader election will use for holding the leader lock.") + cmd.Flags().StringVar(&leaderElectionNamespace, "leader-election-namespace", "", + "The namespace in which the leader election resource will be created. "+ + "If not specified, it will use the namespace where the controller is running.") + cmd.Flags().DurationVar(&leaderElectionLeaseDuration, "leader-election-lease-duration", 15*time.Second, + "The duration that non-leader candidates will wait to force acquire leadership.") + cmd.Flags().DurationVar(&leaderElectionRenewDeadline, "leader-election-renew-deadline", 10*time.Second, + "The duration that the acting leader will retry refreshing leadership before giving up.") + cmd.Flags().DurationVar(&leaderElectionRetryPeriod, "leader-election-retry-period", 2*time.Second, + "The duration the LeaderElector clients should wait between tries of actions.") + cmd.Flags().BoolVar(&leaderElectionReleaseOnCancel, "leader-election-release-on-cancel", false, + "If the leader should step down voluntarily when the Manager ends. "+ + "This requires the binary to immediately end when the Manager is stopped.") + + // Security and certificate flags cmd.Flags().BoolVar(&secureMetrics, "metrics-secure", true, "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") cmd.Flags().StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") @@ -112,6 +143,12 @@ func runControllerManager( metricsCertPath, metricsCertName, metricsCertKey string, webhookCertPath, webhookCertName, webhookCertKey string, enableLeaderElection bool, + leaderElectionID string, + leaderElectionNamespace string, + leaderElectionLeaseDuration time.Duration, + leaderElectionRenewDeadline time.Duration, + leaderElectionRetryPeriod time.Duration, + leaderElectionReleaseOnCancel bool, serverConfigFile string, probeAddr string, secureMetrics bool, @@ -232,23 +269,17 @@ func runControllerManager( } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Metrics: metricsServerOptions, - WebhookServer: webhookServer, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "81afa9db.datumapis.com", - // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily - // when the Manager ends. This requires the binary to immediately end when the - // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly - // speeds up voluntary leader transitions as the new leader don't have to wait - // LeaseDuration time first. - // - // In the default scaffold provided, the program ends immediately after - // the manager stops, so would be fine to enable this option. However, - // if you are doing or is intended to do any operation such as perform cleanups - // after the manager stops then its usage might be unsafe. - // LeaderElectionReleaseOnCancel: true, + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: leaderElectionID, + LeaderElectionNamespace: leaderElectionNamespace, + LeaseDuration: &leaderElectionLeaseDuration, + RenewDeadline: &leaderElectionRenewDeadline, + RetryPeriod: &leaderElectionRetryPeriod, + LeaderElectionReleaseOnCancel: leaderElectionReleaseOnCancel, }) if err != nil { setupLog.Error(err, "unable to start manager") diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index d31f288..3c7eb4a 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -50,8 +50,61 @@ spec: containers: - args: - controller-manager - - --leader-elect - - --health-probe-bind-address=:8081 + - --metrics-bind-address=$(METRICS_BIND_ADDRESS) + - --health-probe-bind-address=$(HEALTH_PROBE_BIND_ADDRESS) + - --leader-elect=$(LEADER_ELECT) + - --leader-election-id=$(LEADER_ELECTION_ID) + - --leader-election-namespace=$(LEADER_ELECTION_NAMESPACE) + - --leader-election-lease-duration=$(LEADER_ELECTION_LEASE_DURATION) + - --leader-election-renew-deadline=$(LEADER_ELECTION_RENEW_DEADLINE) + - --leader-election-retry-period=$(LEADER_ELECTION_RETRY_PERIOD) + - --leader-election-release-on-cancel=$(LEADER_ELECTION_RELEASE_ON_CANCEL) + - --metrics-secure=$(METRICS_SECURE) + - --webhook-cert-path=$(WEBHOOK_CERT_PATH) + - --webhook-cert-name=$(WEBHOOK_CERT_NAME) + - --webhook-cert-key=$(WEBHOOK_CERT_KEY) + - --metrics-cert-path=$(METRICS_CERT_PATH) + - --metrics-cert-name=$(METRICS_CERT_NAME) + - --metrics-cert-key=$(METRICS_CERT_KEY) + - --enable-http2=$(ENABLE_HTTP2) + - --config=$(CONFIG_FILE) + env: + - name: METRICS_BIND_ADDRESS + value: "0" + - name: HEALTH_PROBE_BIND_ADDRESS + value: ":8081" + - name: LEADER_ELECT + value: "true" + - name: LEADER_ELECTION_ID + value: "81afa9db.datumapis.com" + - name: LEADER_ELECTION_NAMESPACE + value: "" + - name: LEADER_ELECTION_LEASE_DURATION + value: "15s" + - name: LEADER_ELECTION_RENEW_DEADLINE + value: "10s" + - name: LEADER_ELECTION_RETRY_PERIOD + value: "2s" + - name: LEADER_ELECTION_RELEASE_ON_CANCEL + value: "false" + - name: METRICS_SECURE + value: "true" + - name: WEBHOOK_CERT_PATH + value: "" + - name: WEBHOOK_CERT_NAME + value: "tls.crt" + - name: WEBHOOK_CERT_KEY + value: "tls.key" + - name: METRICS_CERT_PATH + value: "" + - name: METRICS_CERT_NAME + value: "tls.crt" + - name: METRICS_CERT_KEY + value: "tls.key" + - name: ENABLE_HTTP2 + value: "false" + - name: CONFIG_FILE + value: "" image: ghcr.io/datum-cloud/datum:latest name: datum-controller-manager ports: [] From e23226907b5ac698dc24f0b907627d5c886cbdfa Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 19:04:41 -0500 Subject: [PATCH 14/35] debug: add more logging --- .../resourcemanager/personal_organization_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index bd9f5e1..f2a9227 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -93,6 +93,7 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl } _, err = controllerutil.CreateOrUpdate(ctx, r.Client, membership, func() error { + logger.Info("Creating or updating personal organization membership", "organization", personalOrg.Name) membership.Spec = resourcemanagerv1alpha1.OrganizationMembershipSpec{ OrganizationRef: resourcemanagerv1alpha1.OrganizationReference{ Name: personalOrg.Name, @@ -116,6 +117,7 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl } _, err = controllerutil.CreateOrUpdate(ctx, r.Client, policyBinding, func() error { + logger.Info("Creating or updating personal organization policy binding", "organization", personalOrg.Name) policyBinding.Spec = iamv1alpha1.PolicyBindingSpec{ RoleRef: iamv1alpha1.RoleReference{ Name: r.Config.RoleName, @@ -136,6 +138,8 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, fmt.Errorf("failed to create or update policy binding: %w", err) } + logger.Info("Successfully created or updated personal organization resources", "organization", personalOrg.Name) + return ctrl.Result{}, nil } From e3085c51fe5604d0811f84f38ed086a14ea3483a Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 24 Jun 2025 19:10:58 -0500 Subject: [PATCH 15/35] chore: specify target ref --- .../resourcemanager/personal_organization_controller.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index f2a9227..2f050a1 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -123,6 +123,12 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl Name: r.Config.RoleName, Namespace: r.Config.RoleNamespace, }, + TargetRef: iamv1alpha1.TargetReference{ + APIGroup: resourcemanagerv1alpha1.GroupVersion.Group, + Kind: "Organization", + Name: personalOrg.Name, + UID: string(personalOrg.UID), + }, Subjects: []iamv1alpha1.Subject{ { Kind: "User", From d2c881f8c5327633e4730386f2bbbabc2c40f331 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 1 Jul 2025 18:00:02 -0500 Subject: [PATCH 16/35] chore: always update the personal organization --- .../personal_organization_controller.go | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index 2f050a1..fb0d91a 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -58,25 +58,23 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl Annotations: map[string]string{ "kubernetes.io/display-name": fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName), }, - OwnerReferences: []metav1.OwnerReference{ - // The owner reference is used to ensure that the personal organization - // is deleted when the user is deleted. - { - APIVersion: iamv1alpha1.SchemeGroupVersion.String(), - Kind: "User", - Name: user.Name, - UID: user.UID, - }, - }, - }, - Spec: resourcemanagerv1alpha1.OrganizationSpec{ - Type: "Personal", }, } _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) personalOrg.Annotations["kubernetes.io/display-name"] = fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName) + personalOrg.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ + // The owner reference is used to ensure that the personal organization + // is deleted when the user is deleted. + { + APIVersion: iamv1alpha1.SchemeGroupVersion.String(), + Kind: "User", + Name: user.Name, + UID: user.UID, + }, + } + personalOrg.Spec.Type = "Personal" return nil }) if err != nil { From b721b46bb6a28bf47abb160eecf321225de9627f Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Tue, 1 Jul 2025 20:54:24 -0500 Subject: [PATCH 17/35] bugfix: upgrade to resource selector --- go.mod | 5 +++-- go.sum | 6 ++++++ .../personal_organization_controller.go | 12 +++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index fcb2be3..5c18c35 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/onsi/gomega v1.36.1 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.20.1 - go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7 + go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.32.1 sigs.k8s.io/controller-runtime v0.20.4 @@ -78,6 +78,7 @@ require ( go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.25.0 // indirect @@ -106,5 +107,5 @@ require ( sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/yaml v1.5.0 // indirect ) diff --git a/go.sum b/go.sum index 77f0192..55200d5 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ go.miloapis.com/milo v0.0.0-20250624185205-374dc7fff291 h1:LKCMBlDlrFbHxW04r16R4 go.miloapis.com/milo v0.0.0-20250624185205-374dc7fff291/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7 h1:VTo62HkAfnlqJixH1nF3ktl/+Z/dZ/CemNVEIjnvzK8= go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= +go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 h1:aKbPb+19bA733kxBTIEE8jZ6kHXuJ29mgbcBMEXQxjM= +go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= @@ -181,6 +183,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -275,3 +279,5 @@ sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxg sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= +sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index fb0d91a..59e6628 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -121,11 +121,13 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl Name: r.Config.RoleName, Namespace: r.Config.RoleNamespace, }, - TargetRef: iamv1alpha1.TargetReference{ - APIGroup: resourcemanagerv1alpha1.GroupVersion.Group, - Kind: "Organization", - Name: personalOrg.Name, - UID: string(personalOrg.UID), + ResourceSelector: iamv1alpha1.ResourceSelector{ + ResourceRef: &iamv1alpha1.ResourceReference{ + APIGroup: resourcemanagerv1alpha1.GroupVersion.Group, + Kind: "Organization", + Name: personalOrg.Name, + UID: string(personalOrg.UID), + }, }, Subjects: []iamv1alpha1.Subject{ { From a6c2867475b567876e4115ce63398fa821f5ff28 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 11:50:41 -0500 Subject: [PATCH 18/35] chore: update RBAC for personal organization controller --- config/rbac/role.yaml | 21 +++++++++++++------ .../personal_organization_controller.go | 6 ++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c82dc1c..b65963f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,11 +1,20 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: - labels: - app.kubernetes.io/name: datum - app.kubernetes.io/managed-by: kustomize name: manager-role rules: -- apiGroups: [""] - resources: ["pods"] - verbs: ["get", "list", "watch"] +- apiGroups: + - iam.datumapis.com + resources: + - users + verbs: + - get + - list + - watch +- apiGroups: + - resourcemanager.datumapis.com + resources: + - organizations + verbs: + - create diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index 59e6628..ab92863 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -30,10 +30,8 @@ type PersonalOrganizationController struct { Config PersonalOrganizationControllerConfig } -// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users/finalizers,verbs=update -// +kubebuilder:rbac:groups=resourcemanager.datumapis.com,resources=organizations,verbs=create;delete +// +kubebuilder:rbac:groups=iam.datumapis.com,resources=users,verbs=get;list;watch +// +kubebuilder:rbac:groups=resourcemanager.datumapis.com,resources=organizations,verbs=create // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. From b194b8667b7d0bb90e01bb34de70a36a0c4a9e94 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 12:38:08 -0500 Subject: [PATCH 19/35] chore: update personal organization name --- .../resourcemanager/personal_organization_controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index ab92863..161ce03 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -61,7 +61,7 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) - personalOrg.Annotations["kubernetes.io/display-name"] = fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName) + personalOrg.Annotations["kubernetes.io/display-name"] = fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName) personalOrg.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ // The owner reference is used to ensure that the personal organization // is deleted when the user is deleted. From 912eb90f59976cb27c350d6b3af7357b949a0e39 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 12:58:54 -0500 Subject: [PATCH 20/35] chore: add policy to prevent changing personal organization display name --- config/default/kustomization.yaml | 1 + config/policies/kustomization.yaml | 2 ++ config/policies/validation/kustomization.yaml | 2 ++ .../validation/organization-update-policy.yaml | 15 +++++++++++++++ 4 files changed, 20 insertions(+) create mode 100644 config/policies/kustomization.yaml create mode 100644 config/policies/validation/kustomization.yaml create mode 100644 config/policies/validation/organization-update-policy.yaml diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 19db271..c50f863 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -18,6 +18,7 @@ resources: #- ../crd - ../rbac - ../manager +- ../policies # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml #- ../webhook diff --git a/config/policies/kustomization.yaml b/config/policies/kustomization.yaml new file mode 100644 index 0000000..ab7aef4 --- /dev/null +++ b/config/policies/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - validation/ diff --git a/config/policies/validation/kustomization.yaml b/config/policies/validation/kustomization.yaml new file mode 100644 index 0000000..49dfa8f --- /dev/null +++ b/config/policies/validation/kustomization.yaml @@ -0,0 +1,2 @@ +resources: + - organization-update-policy.yaml diff --git a/config/policies/validation/organization-update-policy.yaml b/config/policies/validation/organization-update-policy.yaml new file mode 100644 index 0000000..2ca1482 --- /dev/null +++ b/config/policies/validation/organization-update-policy.yaml @@ -0,0 +1,15 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicy +metadata: + name: "organization-update-policy.miloapis.com" +spec: + failurePolicy: Fail + matchConstraints: + resourceRules: + - apiGroups: ["resourcemanager.miloapis.com"] + apiVersions: ["v1alpha1"] + operations: ["UPDATE"] + resources: ["organizations"] + validations: + - expression: "object.spec.type != 'Personal' || oldObject.metadata.annotations.get('kubernetes.io/display-name', '') == object.metadata.annotations.get('kubernetes.io/display-name', '')" + message: "The display name of a personal organization cannot be changed." From 46bf2e6c75e2a53b156b8fae42808c1b5cf246e8 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 14:34:32 -0500 Subject: [PATCH 21/35] chore: update personal organization name --- .../resourcemanager/personal_organization_controller.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index 161ce03..b1333a6 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -53,15 +53,12 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl ObjectMeta: metav1.ObjectMeta{ // Create a unique name for the personal organization. Name: fmt.Sprintf("personal-org-%s", hashPersonalOrgName(string(user.UID))), - Annotations: map[string]string{ - "kubernetes.io/display-name": fmt.Sprintf("Personal Organization - %s %s", user.Spec.GivenName, user.Spec.FamilyName), - }, }, } _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) - personalOrg.Annotations["kubernetes.io/display-name"] = fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName) + metav1.SetMetaDataAnnotation(&personalOrg.ObjectMeta, "kubernetes.io/display-name", fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName)) personalOrg.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ // The owner reference is used to ensure that the personal organization // is deleted when the user is deleted. From 349b4dcf30636c8a7831acac2b95fca9e2306682 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 14:37:13 -0500 Subject: [PATCH 22/35] chore: use helper for setting personal organization owner --- cmd/controller/manager.go | 1 + .../personal_organization_controller.go | 16 +++++++--------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cmd/controller/manager.go b/cmd/controller/manager.go index 7474ce7..90677fd 100644 --- a/cmd/controller/manager.go +++ b/cmd/controller/manager.go @@ -289,6 +289,7 @@ func runControllerManager( if err = (&resourcemanagercontroller.PersonalOrganizationController{ Client: mgr.GetClient(), Config: serverConfig.PersonalOrganizationController, + Scheme: mgr.GetScheme(), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "PersonalOrganization") return err diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index b1333a6..6e994a2 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -9,6 +9,7 @@ import ( "hash/fnv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -28,6 +29,10 @@ type PersonalOrganizationController struct { Client client.Client Config PersonalOrganizationControllerConfig + + // The scheme is used to set the controller reference on the personal + // organization. + Scheme *runtime.Scheme } // +kubebuilder:rbac:groups=iam.datumapis.com,resources=users,verbs=get;list;watch @@ -59,15 +64,8 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) metav1.SetMetaDataAnnotation(&personalOrg.ObjectMeta, "kubernetes.io/display-name", fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName)) - personalOrg.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ - // The owner reference is used to ensure that the personal organization - // is deleted when the user is deleted. - { - APIVersion: iamv1alpha1.SchemeGroupVersion.String(), - Kind: "User", - Name: user.Name, - UID: user.UID, - }, + if err := controllerutil.SetControllerReference(user, personalOrg, r.Scheme); err != nil { + return fmt.Errorf("failed to set controller reference: %w", err) } personalOrg.Spec.Type = "Personal" return nil From 30bd4f65712eee2ee61eb22f6083f2b74f004d1d Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Wed, 9 Jul 2025 17:51:26 -0500 Subject: [PATCH 23/35] bugfix: fix formatting on organization policy --- config/policies/validation/organization-update-policy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/policies/validation/organization-update-policy.yaml b/config/policies/validation/organization-update-policy.yaml index 2ca1482..d67c948 100644 --- a/config/policies/validation/organization-update-policy.yaml +++ b/config/policies/validation/organization-update-policy.yaml @@ -11,5 +11,5 @@ spec: operations: ["UPDATE"] resources: ["organizations"] validations: - - expression: "object.spec.type != 'Personal' || oldObject.metadata.annotations.get('kubernetes.io/display-name', '') == object.metadata.annotations.get('kubernetes.io/display-name', '')" + - expression: "object.spec.type != 'Personal' || oldObject.metadata.annotations['kubernetes.io/display-name'] == object.metadata.annotations['kubernetes.io/display-name']" message: "The display name of a personal organization cannot be changed." From 9ce46162c6475ce93e73f4087f1c4ad2deac86b7 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 10 Jul 2025 14:46:27 -0500 Subject: [PATCH 24/35] chore: address linter issues and upgrade golang ci lint --- .github/workflows/lint.yml | 2 +- .golangci.yml | 53 +++++++++++++++++++------------------- Makefile | 4 +-- cmd/main.go | 4 --- test/utils/utils.go | 10 +++---- 5 files changed, 34 insertions(+), 39 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4951e33..fffe416 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,4 +20,4 @@ jobs: - name: Run linter uses: golangci/golangci-lint-action@v6 with: - version: v1.63.4 + version: v2.1.6 diff --git a/.golangci.yml b/.golangci.yml index 6b29746..9a69a7f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,47 +1,46 @@ +version: "2" run: - timeout: 5m allow-parallel-runners: true - -issues: - # don't skip warning about doc comments - # don't exclude the default set of lint - exclude-use-default: false - # restore some of the defaults - # (fill in the rest as needed) - exclude-rules: - - path: "api/*" - linters: - - lll - - path: "internal/*" - linters: - - dupl - - lll linters: - disable-all: true + default: none enable: + - copyloopvar - dupl - errcheck - - copyloopvar - ginkgolinter - goconst - gocyclo - - gofmt - - goimports - - gosimple - govet - ineffassign - - lll - misspell - nakedret - prealloc - revive - staticcheck - - typecheck - unconvert - unparam - unused - -linters-settings: - revive: + settings: + revive: + rules: + - name: comment-spacings + exclusions: + generated: lax rules: - - name: comment-spacings + - linters: + - dupl + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 3793cf2..10e5475 100644 --- a/Makefile +++ b/Makefile @@ -181,7 +181,7 @@ DEFAULTER_GEN_VERSION ?= v0.32.3 ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 +GOLANGCI_LINT_VERSION ?= v2.1.6 .PHONY: kustomize kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. @@ -214,7 +214,7 @@ $(ENVTEST): $(LOCALBIN) .PHONY: golangci-lint golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. $(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist # $1 - target path with name of binary diff --git a/cmd/main.go b/cmd/main.go index ccdc43a..ad83f08 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -10,10 +10,6 @@ import ( "go.datum.net/datum/cmd/controller" ) -var ( - cfgFile string -) - // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "datum", diff --git a/test/utils/utils.go b/test/utils/utils.go index 00ff671..c1ecfba 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -9,7 +9,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + ginkgo "github.com/onsi/ginkgo/v2" //nolint:golint,revive ) const ( @@ -22,7 +22,7 @@ const ( ) func warnError(err error) { - _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) + _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "warning: %v\n", err) } // Run executes the provided command within this context @@ -31,12 +31,12 @@ func Run(cmd *exec.Cmd) (string, error) { cmd.Dir = dir if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err) + _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "chdir dir: %s\n", err) } cmd.Env = append(os.Environ(), "GO111MODULE=on") command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(GinkgoWriter, "running: %s\n", command) + _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "running: %s\n", command) output, err := cmd.CombinedOutput() if err != nil { return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) @@ -182,7 +182,7 @@ func GetProjectDir() (string, error) { if err != nil { return wd, err } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil } From a4b559becda7e8a7fb5776c40f803e00c2fad7b5 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 10 Jul 2025 15:34:51 -0500 Subject: [PATCH 25/35] chore: run go mod tidy --- go.mod | 12 ++---------- go.sum | 25 ++----------------------- 2 files changed, 4 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 5c18c35..82c9db9 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,11 @@ require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 github.com/spf13/cobra v1.8.1 - github.com/spf13/viper v1.20.1 go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 + k8s.io/api v0.32.1 k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.32.1 + k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.20.4 ) @@ -36,7 +37,6 @@ require ( github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect @@ -53,19 +53,13 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.19.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/subosito/gotenv v1.6.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect @@ -96,13 +90,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.32.1 // indirect k8s.io/apiextensions-apiserver v0.32.1 // indirect k8s.io/apiserver v0.32.1 // indirect k8s.io/component-base v0.32.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 55200d5..2fa410a 100644 --- a/go.sum +++ b/go.sum @@ -26,8 +26,6 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= @@ -49,8 +47,6 @@ 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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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= @@ -101,8 +97,6 @@ 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/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -119,21 +113,11 @@ 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/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= -github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -145,16 +129,10 @@ 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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= -github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= 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.0.0-20250624185205-374dc7fff291 h1:LKCMBlDlrFbHxW04r16R4NjD3nljTkGjB7PglpGjMz0= -go.miloapis.com/milo v0.0.0-20250624185205-374dc7fff291/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= -go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7 h1:VTo62HkAfnlqJixH1nF3ktl/+Z/dZ/CemNVEIjnvzK8= -go.miloapis.com/milo v0.0.0-20250624192330-fd15c9091be7/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 h1:aKbPb+19bA733kxBTIEE8jZ6kHXuJ29mgbcBMEXQxjM= go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -185,6 +163,8 @@ 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= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -277,7 +257,6 @@ sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ= sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4= From d3a8a7223247ce9edf817d9da7802bb0c553f718 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Fri, 25 Jul 2025 13:30:05 -0500 Subject: [PATCH 26/35] chore: create policy binding for admission policy --- .../validation/organization-update-policy.yaml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/config/policies/validation/organization-update-policy.yaml b/config/policies/validation/organization-update-policy.yaml index d67c948..a5a08cc 100644 --- a/config/policies/validation/organization-update-policy.yaml +++ b/config/policies/validation/organization-update-policy.yaml @@ -1,7 +1,7 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingAdmissionPolicy metadata: - name: "organization-update-policy.miloapis.com" + name: "disallow-personal-org-name-change" spec: failurePolicy: Fail matchConstraints: @@ -13,3 +13,11 @@ spec: validations: - expression: "object.spec.type != 'Personal' || oldObject.metadata.annotations['kubernetes.io/display-name'] == object.metadata.annotations['kubernetes.io/display-name']" message: "The display name of a personal organization cannot be changed." +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingAdmissionPolicyBinding +metadata: + name: "disallow-personal-org-name-change" +spec: + policyName: "disallow-personal-org-name-change" + validationActions: [Deny] From ebb5142880946b92d358af3ff089fd0ef95a602e Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 16:12:57 -0500 Subject: [PATCH 27/35] chore: remove end to end testing This will be replaced with Chainsaw based end to end tests in the future. --- .github/workflows/test-e2e.yml | 35 ---- test/e2e/e2e_suite_test.go | 74 -------- test/e2e/e2e_test.go | 314 --------------------------------- test/utils/utils.go | 236 ------------------------- 4 files changed, 659 deletions(-) delete mode 100644 .github/workflows/test-e2e.yml delete mode 100644 test/e2e/e2e_suite_test.go delete mode 100644 test/e2e/e2e_test.go delete mode 100644 test/utils/utils.go diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml deleted file mode 100644 index b2eda8c..0000000 --- a/.github/workflows/test-e2e.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: E2E Tests - -on: - push: - pull_request: - -jobs: - test-e2e: - name: Run on Ubuntu - runs-on: ubuntu-latest - steps: - - name: Clone the code - uses: actions/checkout@v4 - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Install the latest version of kind - run: | - curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind - - - name: Verify kind installation - run: kind version - - - name: Create kind cluster - run: kind create cluster - - - name: Running Test e2e - run: | - go mod tidy - make test-e2e diff --git a/test/e2e/e2e_suite_test.go b/test/e2e/e2e_suite_test.go deleted file mode 100644 index 142058a..0000000 --- a/test/e2e/e2e_suite_test.go +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -package e2e - -import ( - "fmt" - "os" - "os/exec" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "go.datum.net/datum/test/utils" -) - -var ( - // Optional Environment Variables: - // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup. - // These variables are useful if CertManager is already installed, avoiding - // re-installation and conflicts. - skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true" - // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster - isCertManagerAlreadyInstalled = false - - // projectImage is the name of the image which will be build and loaded - // with the code source changes to be tested. - projectImage = "example.com/datum:v0.0.1" -) - -// TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, -// temporary environment to validate project changes with the purposed to be used in CI jobs. -// The default setup requires Kind, builds/loads the Manager Docker image locally, and installs -// CertManager. -func TestE2E(t *testing.T) { - RegisterFailHandler(Fail) - _, _ = fmt.Fprintf(GinkgoWriter, "Starting datum integration test suite\n") - RunSpecs(t, "e2e suite") -} - -var _ = BeforeSuite(func() { - By("building the manager(Operator) image") - cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage)) - _, err := utils.Run(cmd) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") - - // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is - // built and available before running the tests. Also, remove the following block. - By("loading the manager(Operator) image on Kind") - err = utils.LoadImageToKindClusterWithName(projectImage) - ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") - - // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing. - // To prevent errors when tests run in environments with CertManager already installed, - // we check for its presence before execution. - // Setup CertManager before the suite if not skipped and if not already installed - if !skipCertManagerInstall { - By("checking if cert manager is installed already") - isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled() - if !isCertManagerAlreadyInstalled { - _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n") - Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager") - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n") - } - } -}) - -var _ = AfterSuite(func() { - // Teardown CertManager after the suite if not skipped and if it was not already installed - if !skipCertManagerInstall && !isCertManagerAlreadyInstalled { - _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n") - utils.UninstallCertManager() - } -}) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go deleted file mode 100644 index 09c5661..0000000 --- a/test/e2e/e2e_test.go +++ /dev/null @@ -1,314 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -package e2e - -import ( - "encoding/json" - "fmt" - "os" - "os/exec" - "path/filepath" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "go.datum.net/datum/test/utils" -) - -// namespace where the project is deployed in -const namespace = "datum-system" - -// serviceAccountName created for the project -const serviceAccountName = "datum-controller-manager" - -// metricsServiceName is the name of the metrics service of the project -const metricsServiceName = "datum-controller-manager-metrics-service" - -// metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data -const metricsRoleBindingName = "datum-metrics-binding" - -var _ = Describe("Manager", Ordered, func() { - var controllerPodName string - - // Before running the tests, set up the environment by creating the namespace, - // enforce the restricted security policy to the namespace, installing CRDs, - // and deploying the controller. - BeforeAll(func() { - By("creating manager namespace") - cmd := exec.Command("kubectl", "create", "ns", namespace) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") - - By("labeling the namespace to enforce the restricted security policy") - cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace, - "pod-security.kubernetes.io/enforce=restricted") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy") - - By("installing CRDs") - cmd = exec.Command("make", "install") - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs") - - By("deploying the controller-manager") - cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage)) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager") - }) - - // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, - // and deleting the namespace. - AfterAll(func() { - By("cleaning up the curl pod for metrics") - cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) - _, _ = utils.Run(cmd) - - By("undeploying the controller-manager") - cmd = exec.Command("make", "undeploy") - _, _ = utils.Run(cmd) - - By("uninstalling CRDs") - cmd = exec.Command("make", "uninstall") - _, _ = utils.Run(cmd) - - By("removing manager namespace") - cmd = exec.Command("kubectl", "delete", "ns", namespace) - _, _ = utils.Run(cmd) - }) - - // After each test, check for failures and collect logs, events, - // and pod descriptions for debugging. - AfterEach(func() { - specReport := CurrentSpecReport() - if specReport.Failed() { - By("Fetching controller manager pod logs") - cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) - controllerLogs, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) - } - - By("Fetching Kubernetes events") - cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") - eventsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) - } - - By("Fetching curl-metrics logs") - cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) - metricsOutput, err := utils.Run(cmd) - if err == nil { - _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) - } else { - _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) - } - - By("Fetching controller manager pod description") - cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace) - podDescription, err := utils.Run(cmd) - if err == nil { - fmt.Println("Pod description:\n", podDescription) - } else { - fmt.Println("Failed to describe controller pod") - } - } - }) - - SetDefaultEventuallyTimeout(2 * time.Minute) - SetDefaultEventuallyPollingInterval(time.Second) - - Context("Manager", func() { - It("should run successfully", func() { - By("validating that the controller-manager pod is running as expected") - verifyControllerUp := func(g Gomega) { - // Get the name of the controller-manager pod - cmd := exec.Command("kubectl", "get", - "pods", "-l", "control-plane=controller-manager", - "-o", "go-template={{ range .items }}"+ - "{{ if not .metadata.deletionTimestamp }}"+ - "{{ .metadata.name }}"+ - "{{ \"\\n\" }}{{ end }}{{ end }}", - "-n", namespace, - ) - - podOutput, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information") - podNames := utils.GetNonEmptyLines(podOutput) - g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running") - controllerPodName = podNames[0] - g.Expect(controllerPodName).To(ContainSubstring("controller-manager")) - - // Validate the pod's status - cmd = exec.Command("kubectl", "get", - "pods", controllerPodName, "-o", "jsonpath={.status.phase}", - "-n", namespace, - ) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status") - } - Eventually(verifyControllerUp).Should(Succeed()) - }) - - It("should ensure the metrics endpoint is serving metrics", func() { - By("creating a ClusterRoleBinding for the service account to allow access to metrics") - cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, - "--clusterrole=datum-metrics-reader", - fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), - ) - _, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create ClusterRoleBinding") - - By("validating that the metrics service is available") - cmd = exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Metrics service should exist") - - By("getting the service account token") - token, err := serviceAccountToken() - Expect(err).NotTo(HaveOccurred()) - Expect(token).NotTo(BeEmpty()) - - By("waiting for the metrics endpoint to be ready") - verifyMetricsEndpointReady := func(g Gomega) { - cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(ContainSubstring("8443"), "Metrics endpoint is not ready") - } - Eventually(verifyMetricsEndpointReady).Should(Succeed()) - - By("verifying that the controller manager is serving the metrics server") - verifyMetricsServerStarted := func(g Gomega) { - cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"), - "Metrics server not yet started") - } - Eventually(verifyMetricsServerStarted).Should(Succeed()) - - By("creating the curl-metrics pod to access the metrics endpoint") - cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never", - "--namespace", namespace, - "--image=curlimages/curl:latest", - "--overrides", - fmt.Sprintf(`{ - "spec": { - "containers": [{ - "name": "curl", - "image": "curlimages/curl:latest", - "command": ["/bin/sh", "-c"], - "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": ["ALL"] - }, - "runAsNonRoot": true, - "runAsUser": 1000, - "seccompProfile": { - "type": "RuntimeDefault" - } - } - }], - "serviceAccount": "%s" - } - }`, token, metricsServiceName, namespace, serviceAccountName)) - _, err = utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod") - - By("waiting for the curl-metrics pod to complete.") - verifyCurlUp := func(g Gomega) { - cmd := exec.Command("kubectl", "get", "pods", "curl-metrics", - "-o", "jsonpath={.status.phase}", - "-n", namespace) - output, err := utils.Run(cmd) - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status") - } - Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed()) - - By("getting the metrics by checking curl-metrics logs") - metricsOutput := getMetricsOutput() - Expect(metricsOutput).To(ContainSubstring( - "controller_runtime_reconcile_total", - )) - }) - - // +kubebuilder:scaffold:e2e-webhooks-checks - - // TODO: Customize the e2e test suite with scenarios specific to your project. - // Consider applying sample/CR(s) and check their status and/or verifying - // the reconciliation by using the metrics, i.e.: - // metricsOutput := getMetricsOutput() - // Expect(metricsOutput).To(ContainSubstring( - // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`, - // strings.ToLower(), - // )) - }) -}) - -// serviceAccountToken returns a token for the specified service account in the given namespace. -// It uses the Kubernetes TokenRequest API to generate a token by directly sending a request -// and parsing the resulting token from the API response. -func serviceAccountToken() (string, error) { - const tokenRequestRawString = `{ - "apiVersion": "authentication.k8s.io/v1", - "kind": "TokenRequest" - }` - - // Temporary file to store the token request - secretName := fmt.Sprintf("%s-token-request", serviceAccountName) - tokenRequestFile := filepath.Join("/tmp", secretName) - err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644)) - if err != nil { - return "", err - } - - var out string - verifyTokenCreation := func(g Gomega) { - // Execute kubectl command to create the token - cmd := exec.Command("kubectl", "create", "--raw", fmt.Sprintf( - "/api/v1/namespaces/%s/serviceaccounts/%s/token", - namespace, - serviceAccountName, - ), "-f", tokenRequestFile) - - output, err := cmd.CombinedOutput() - g.Expect(err).NotTo(HaveOccurred()) - - // Parse the JSON output to extract the token - var token tokenRequest - err = json.Unmarshal(output, &token) - g.Expect(err).NotTo(HaveOccurred()) - - out = token.Status.Token - } - Eventually(verifyTokenCreation).Should(Succeed()) - - return out, err -} - -// getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint. -func getMetricsOutput() string { - By("getting the curl-metrics logs") - cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) - metricsOutput, err := utils.Run(cmd) - Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod") - Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK")) - return metricsOutput -} - -// tokenRequest is a simplified representation of the Kubernetes TokenRequest API response, -// containing only the token field that we need to extract. -type tokenRequest struct { - Status struct { - Token string `json:"token"` - } `json:"status"` -} diff --git a/test/utils/utils.go b/test/utils/utils.go deleted file mode 100644 index c1ecfba..0000000 --- a/test/utils/utils.go +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -package utils - -import ( - "bufio" - "bytes" - "fmt" - "os" - "os/exec" - "strings" - - ginkgo "github.com/onsi/ginkgo/v2" //nolint:golint,revive -) - -const ( - prometheusOperatorVersion = "v0.77.1" - prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + - "releases/download/%s/bundle.yaml" - - certmanagerVersion = "v1.16.3" - certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" -) - -func warnError(err error) { - _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "warning: %v\n", err) -} - -// Run executes the provided command within this context -func Run(cmd *exec.Cmd) (string, error) { - dir, _ := GetProjectDir() - cmd.Dir = dir - - if err := os.Chdir(cmd.Dir); err != nil { - _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "chdir dir: %s\n", err) - } - - cmd.Env = append(os.Environ(), "GO111MODULE=on") - command := strings.Join(cmd.Args, " ") - _, _ = fmt.Fprintf(ginkgo.GinkgoWriter, "running: %s\n", command) - output, err := cmd.CombinedOutput() - if err != nil { - return string(output), fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output)) - } - - return string(output), nil -} - -// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. -func InstallPrometheusOperator() error { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - cmd := exec.Command("kubectl", "create", "-f", url) - _, err := Run(cmd) - return err -} - -// UninstallPrometheusOperator uninstalls the prometheus -func UninstallPrometheusOperator() { - url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed -// by verifying the existence of key CRDs related to Prometheus. -func IsPrometheusCRDsInstalled() bool { - // List of common Prometheus CRDs - prometheusCRDs := []string{ - "prometheuses.monitoring.coreos.com", - "prometheusrules.monitoring.coreos.com", - "prometheusagents.monitoring.coreos.com", - } - - cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") - output, err := Run(cmd) - if err != nil { - return false - } - crdList := GetNonEmptyLines(output) - for _, crd := range prometheusCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// UninstallCertManager uninstalls the cert manager -func UninstallCertManager() { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - cmd := exec.Command("kubectl", "delete", "-f", url) - if _, err := Run(cmd); err != nil { - warnError(err) - } -} - -// InstallCertManager installs the cert manager bundle. -func InstallCertManager() error { - url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) - cmd := exec.Command("kubectl", "apply", "-f", url) - if _, err := Run(cmd); err != nil { - return err - } - // Wait for cert-manager-webhook to be ready, which can take time if cert-manager - // was re-installed after uninstalling on a cluster. - cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", - "--for", "condition=Available", - "--namespace", "cert-manager", - "--timeout", "5m", - ) - - _, err := Run(cmd) - return err -} - -// IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed -// by verifying the existence of key CRDs related to Cert Manager. -func IsCertManagerCRDsInstalled() bool { - // List of common Cert Manager CRDs - certManagerCRDs := []string{ - "certificates.cert-manager.io", - "issuers.cert-manager.io", - "clusterissuers.cert-manager.io", - "certificaterequests.cert-manager.io", - "orders.acme.cert-manager.io", - "challenges.acme.cert-manager.io", - } - - // Execute the kubectl command to get all CRDs - cmd := exec.Command("kubectl", "get", "crds") - output, err := Run(cmd) - if err != nil { - return false - } - - // Check if any of the Cert Manager CRDs are present - crdList := GetNonEmptyLines(output) - for _, crd := range certManagerCRDs { - for _, line := range crdList { - if strings.Contains(line, crd) { - return true - } - } - } - - return false -} - -// LoadImageToKindClusterWithName loads a local docker image to the kind cluster -func LoadImageToKindClusterWithName(name string) error { - cluster := "kind" - if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { - cluster = v - } - kindOptions := []string{"load", "docker-image", name, "--name", cluster} - cmd := exec.Command("kind", kindOptions...) - _, err := Run(cmd) - return err -} - -// GetNonEmptyLines converts given command output string into individual objects -// according to line breakers, and ignores the empty elements in it. -func GetNonEmptyLines(output string) []string { - var res []string - elements := strings.Split(output, "\n") - for _, element := range elements { - if element != "" { - res = append(res, element) - } - } - - return res -} - -// GetProjectDir will return the directory where the project is -func GetProjectDir() (string, error) { - wd, err := os.Getwd() - if err != nil { - return wd, err - } - wd = strings.ReplaceAll(wd, "/test/e2e", "") - return wd, nil -} - -// UncommentCode searches for target in the file and remove the comment prefix -// of the target content. The target content may span multiple lines. -func UncommentCode(filename, target, prefix string) error { - // false positive - // nolint:gosec - content, err := os.ReadFile(filename) - if err != nil { - return err - } - strContent := string(content) - - idx := strings.Index(strContent, target) - if idx < 0 { - return fmt.Errorf("unable to find the code %s to be uncomment", target) - } - - out := new(bytes.Buffer) - _, err = out.Write(content[:idx]) - if err != nil { - return err - } - - scanner := bufio.NewScanner(bytes.NewBufferString(target)) - if !scanner.Scan() { - return nil - } - for { - _, err := out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)) - if err != nil { - return err - } - // Avoid writing a newline in case the previous line was the last in target. - if !scanner.Scan() { - break - } - if _, err := out.WriteString("\n"); err != nil { - return err - } - } - - _, err = out.Write(content[idx+len(target):]) - if err != nil { - return err - } - // false positive - // nolint:gosec - return os.WriteFile(filename, out.Bytes(), 0644) -} From 9c3399ebc23e2f5c36c17a705627976a2cce42db Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 16:38:11 -0500 Subject: [PATCH 28/35] chore: use tagged milo version --- go.mod | 6 +++--- go.sum | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 82c9db9..5c7b9a7 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ godebug default=go1.24 require ( github.com/onsi/ginkgo/v2 v2.22.0 github.com/onsi/gomega v1.36.1 - github.com/spf13/cobra v1.8.1 - go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 + github.com/spf13/cobra v1.9.1 + go.miloapis.com/milo v0.1.0 k8s.io/api v0.32.1 k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.32.1 @@ -58,7 +58,7 @@ require ( github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/go.sum b/go.sum index 2fa410a..1aaa5dd 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,7 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -115,9 +116,11 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -135,6 +138,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23 h1:aKbPb+19bA733kxBTIEE8jZ6kHXuJ29mgbcBMEXQxjM= go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= +go.miloapis.com/milo v0.1.0 h1:AYFVz1lfta/NbWSFSSKPtnkCA2rN+iegxlfQrDgEvYY= +go.miloapis.com/milo v0.1.0/go.mod h1:X+DpWOchv/Vm63mwHnboW00KRGsODY2bUTS/bBbK1+E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= From 8b009340e449f541d87f9e341252e3d101e23802 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 16:39:13 -0500 Subject: [PATCH 29/35] chore: update lint action version --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index fffe416..048a03a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,6 +18,6 @@ jobs: go-version-file: go.mod - name: Run linter - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: version: v2.1.6 From c208e4126f50fc86ce5cbd543deffcb559498d02 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 16:41:06 -0500 Subject: [PATCH 30/35] chore: run go mod tidy --- go.sum | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/go.sum b/go.sum index 1aaa5dd..ea878b0 100644 --- a/go.sum +++ b/go.sum @@ -12,7 +12,6 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -114,12 +113,10 @@ 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.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 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/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= @@ -136,8 +133,6 @@ 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.0.0-20250702010202-c34f18ccdb23 h1:aKbPb+19bA733kxBTIEE8jZ6kHXuJ29mgbcBMEXQxjM= -go.miloapis.com/milo v0.0.0-20250702010202-c34f18ccdb23/go.mod h1:hh74+itTatDKu88z8rOItbrn3g98jei7ecaMKjJpFds= go.miloapis.com/milo v0.1.0 h1:AYFVz1lfta/NbWSFSSKPtnkCA2rN+iegxlfQrDgEvYY= go.miloapis.com/milo v0.1.0/go.mod h1:X+DpWOchv/Vm63mwHnboW00KRGsODY2bUTS/bBbK1+E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= From f49ec658d8e79c9e131f658535e29c7fbd518a73 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 16:42:02 -0500 Subject: [PATCH 31/35] chore: only run on push to main --- .github/workflows/build-and-test.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml index 0bafcc7..8088ea3 100644 --- a/.github/workflows/build-and-test.yaml +++ b/.github/workflows/build-and-test.yaml @@ -2,6 +2,8 @@ name: Build and Test on: push: + branches: + - main pull_request: jobs: @@ -10,7 +12,7 @@ jobs: id-token: write contents: read packages: write - uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.1 + uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.5.2 with: image-name: datum secrets: inherit @@ -21,7 +23,7 @@ jobs: id-token: write contents: read packages: write - uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.5.1 + uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.5.2 with: bundle-name: ghcr.io/datum-cloud/datum-kustomize bundle-path: config From 48f10b161f4757f611e09910cf25fe47ce258b71 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 18:05:00 -0500 Subject: [PATCH 32/35] chore: use correct api group for milo IAM apis --- PROJECT | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PROJECT b/PROJECT index a946dcc..3d0c53d 100644 --- a/PROJECT +++ b/PROJECT @@ -9,7 +9,7 @@ projectName: datum-cloud repo: go.datum.net/datum resources: - controller: true - domain: iam.datumapis.com + domain: iam.miloapis.com external: true kind: User path: go.miloapis.com/milo/pkg/apis/iam/v1alpha1 From 0994c321b3ee18f07dd1309dbe8821821eb7c683 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 18:07:43 -0500 Subject: [PATCH 33/35] chore: add code comments for personal org controller config --- .../resourcemanager/personal_organization_controller.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index 6e994a2..4155420 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -20,7 +20,12 @@ import ( ) type PersonalOrganizationControllerConfig struct { - RoleName string `json:"roleName"` + // The name of the role to use when assigning owner permissions to the user + // this organization is being created for. + RoleName string `json:"roleName"` + + // The namespace the owner role exists in that will be assigned to the user + // the organization is being created for. RoleNamespace string `json:"roleNamespace"` } From f5c29ecb9ddb16381e97f9b9a4591cc286402ec4 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 18:09:19 -0500 Subject: [PATCH 34/35] chore: add description annotation to personal organization --- .../resourcemanager/personal_organization_controller.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index 4155420..abd5161 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -68,7 +68,9 @@ func (r *PersonalOrganizationController) Reconcile(ctx context.Context, req ctrl _, err := controllerutil.CreateOrUpdate(ctx, r.Client, personalOrg, func() error { logger.Info("Creating or updating personal organization", "organization", personalOrg.Name) + // TODO: Remove once portal uses the description annotation metav1.SetMetaDataAnnotation(&personalOrg.ObjectMeta, "kubernetes.io/display-name", fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName)) + metav1.SetMetaDataAnnotation(&personalOrg.ObjectMeta, "kubernetes.io/description", fmt.Sprintf("%s %s's Personal Org", user.Spec.GivenName, user.Spec.FamilyName)) if err := controllerutil.SetControllerReference(user, personalOrg, r.Scheme); err != nil { return fmt.Errorf("failed to set controller reference: %w", err) } From 16f834b01e6c24bf52af663f4813776f74b6f874 Mon Sep 17 00:00:00 2001 From: Scot Wells Date: Thu, 31 Jul 2025 18:24:06 -0500 Subject: [PATCH 35/35] chore: update code comment --- .../resourcemanager/personal_organization_controller.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/controller/resourcemanager/personal_organization_controller.go b/internal/controller/resourcemanager/personal_organization_controller.go index abd5161..b6768a8 100644 --- a/internal/controller/resourcemanager/personal_organization_controller.go +++ b/internal/controller/resourcemanager/personal_organization_controller.go @@ -21,7 +21,9 @@ import ( type PersonalOrganizationControllerConfig struct { // The name of the role to use when assigning owner permissions to the user - // this organization is being created for. + // this organization is being created for. This role should be used to grant + // the default set of permissions that should be granted to the user the + // personal organization is being created. RoleName string `json:"roleName"` // The namespace the owner role exists in that will be assigned to the user