From 50544f3147ddbe15882c9733983a7d86753999c6 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 15:35:55 +0530 Subject: [PATCH 01/16] Merge placement-operator git history into nova-operator Import complete placement-operator codebase into placement-import directly using git subtree merge to preserve full commit history this merge connects plaement git history to nova-operator, enabling git blame and git log to show original authors and commit history of all placement code --- placement-import/.ci-operator.yaml | 4 + placement-import/.dockerignore | 4 + .../workflows/build-placement-operator.yaml | 26 + .../workflows/force-bump-pr-manual.yaml | 14 + .../workflows/force-bump-pr-scheduled.yaml | 15 + placement-import/.github/workflows/lints.yaml | 14 + .../workflows/release-branch-sync.yaml | 12 + .../workflows/release-placement-operator.yaml | 48 + placement-import/.gitignore | 37 + placement-import/.golangci.yaml | 20 + placement-import/.pre-commit-config.yaml | 75 + placement-import/.prow_ci.env | 2 + placement-import/.pull_request_pipeline | 39 + placement-import/.zuul.yaml | 9 + placement-import/Dockerfile | 75 + placement-import/LICENSE.txt | 202 +++ placement-import/Makefile | 399 +++++ placement-import/OWNERS | 8 + placement-import/OWNERS_ALIASES | 15 + placement-import/PROJECT | 27 + placement-import/README.md | 178 ++ ...placement.openstack.org_placementapis.yaml | 502 ++++++ placement-import/api/go.mod | 95 + placement-import/api/go.sum | 203 +++ .../api/v1beta1/groupversion_info.go | 36 + .../api/v1beta1/placementapi_types.go | 259 +++ .../api/v1beta1/placementapi_webhook.go | 199 +++ .../api/v1beta1/zz_generated.deepcopy.go | 273 +++ placement-import/cmd/main.go | 303 ++++ .../certmanager/certificate-metrics.yaml | 20 + .../certmanager/certificate-webhook.yaml | 20 + .../config/certmanager/issuer.yaml | 13 + .../config/certmanager/kustomization.yaml | 7 + .../config/certmanager/kustomizeconfig.yaml | 8 + ...placement.openstack.org_placementapis.yaml | 502 ++++++ .../config/crd/kustomization.yaml | 16 + .../config/crd/kustomizeconfig.yaml | 19 + .../default/cert_metrics_manager_patch.yaml | 30 + .../config/default/kustomization.yaml | 236 +++ .../default/manager_default_images.yaml | 15 + .../config/default/manager_metrics_patch.yaml | 4 + .../config/default/manager_webhook_patch.yaml | 31 + .../config/default/metrics_service.yaml | 18 + .../config/manager/kustomization.yaml | 8 + placement-import/config/manager/manager.yaml | 98 ++ ...cement-operator.clusterserviceversion.yaml | 63 + .../config/manifests/kustomization.yaml | 28 + .../network-policy/allow-metrics-traffic.yaml | 27 + .../network-policy/allow-webhook-traffic.yaml | 27 + .../config/network-policy/kustomization.yaml | 3 + .../config/prometheus/kustomization.yaml | 11 + .../config/prometheus/monitor.yaml | 27 + .../config/prometheus/monitor_tls_patch.yaml | 19 + .../config/rbac/kustomization.yaml | 27 + .../config/rbac/leader_election_role.yaml | 40 + .../rbac/leader_election_role_binding.yaml | 15 + .../config/rbac/metrics_auth_role.yaml | 17 + .../rbac/metrics_auth_role_binding.yaml | 12 + .../config/rbac/metrics_reader_role.yaml | 9 + .../config/rbac/placementapi_admin_role.yaml | 27 + .../config/rbac/placementapi_editor_role.yaml | 33 + .../config/rbac/placementapi_viewer_role.yaml | 29 + placement-import/config/rbac/role.yaml | 162 ++ .../config/rbac/role_binding.yaml | 15 + .../config/rbac/service_account.yaml | 8 + .../config/samples/kustomization.yaml | 4 + .../placement_v1beta1_placementapi.yaml | 18 + .../placement_v1beta1_placementtls.yaml | 21 + .../config/scorecard/bases/config.yaml | 7 + .../config/scorecard/kustomization.yaml | 18 + .../scorecard/patches/basic.config.yaml | 10 + .../config/scorecard/patches/olm.config.yaml | 50 + .../config/webhook/kustomization.yaml | 6 + .../config/webhook/kustomizeconfig.yaml | 22 + .../config/webhook/manifests.yaml | 52 + placement-import/config/webhook/service.yaml | 16 + placement-import/go.mod | 144 ++ placement-import/go.sum | 305 ++++ placement-import/hack/boilerplate.go.txt | 15 + .../hack/build-crd-schema-checker.sh | 15 + placement-import/hack/clean_local_webhook.sh | 5 + placement-import/hack/crd-schema-checker.sh | 22 + .../hack/run_with_local_webhook.sh | 139 ++ .../controller/placementapi_controller.go | 1493 ++++++++++++++++ placement-import/internal/placement/const.go | 39 + placement-import/internal/placement/dbsync.go | 91 + .../internal/placement/deployment.go | 186 ++ .../internal/placement/volumes.go | 81 + .../webhook/v1beta1/placementapi_webhook.go | 129 ++ placement-import/kuttl-test.yaml | 40 + placement-import/renovate.json | 16 + .../templates/placementapi/config/httpd.conf | 81 + .../config/placement-api-config.json | 70 + .../config/placement-dbsync-config.json | 23 + .../placementapi/config/placement.conf | 38 + .../templates/placementapi/config/ssl.conf | 21 + placement-import/test/functional/base_test.go | 221 +++ .../placementapi_controller_test.go | 1529 +++++++++++++++++ .../functional/placementapi_webhook_test.go | 219 +++ .../test/functional/suite_test.go | 253 +++ .../common/assert_sample_deployment.yaml | 287 ++++ .../test/kuttl/common/cleanup-placement.yaml | 6 + .../common/errors_cleanup_placement.yaml | 94 + .../kuttl/common/patch_placement_deploy.yaml | 5 + .../kuttl/common/placementapi_deploy.yaml | 1 + .../test/kuttl/common/tls_certificates.yaml | 31 + .../go_templates/apiEndpoints.gotemplate | 1 + .../test/kuttl/lib/helper_functions.sh | 13 + .../00-cleanup-placement.yaml | 1 + .../tests/placement_deploy_tls/01-assert.yaml | 14 + .../01-tls_certificates.yaml | 1 + .../02-placementapi_deploy_tls.yaml | 1 + .../tests/placement_deploy_tls/03-assert.yaml | 217 +++ .../03-patch_placement_deploy.yaml | 1 + .../04-cleanup-placement.yaml | 1 + .../tests/placement_deploy_tls/04-errors.yaml | 1 + .../placement_scale/00-cleanup-placement.yaml | 1 + .../placement_scale/01-deploy_placement.yaml | 1 + .../tests/placement_scale/02-assert.yaml | 1 + .../02-patch_placement_deploy.yaml | 1 + .../tests/placement_scale/03-assert.yaml | 26 + .../03-scale-placementapi.yaml | 5 + .../tests/placement_scale/04-assert.yaml | 26 + .../04-scale-down-placementapi.yaml | 5 + .../tests/placement_scale/05-assert.yaml | 23 + .../05-scale-down-zero-placementapi.yaml | 5 + .../placement_scale/06-cleanup-placement.yaml | 1 + .../tests/placement_scale/06-errors.yaml | 1 + 128 files changed, 10907 insertions(+) create mode 100644 placement-import/.ci-operator.yaml create mode 100644 placement-import/.dockerignore create mode 100644 placement-import/.github/workflows/build-placement-operator.yaml create mode 100644 placement-import/.github/workflows/force-bump-pr-manual.yaml create mode 100644 placement-import/.github/workflows/force-bump-pr-scheduled.yaml create mode 100644 placement-import/.github/workflows/lints.yaml create mode 100644 placement-import/.github/workflows/release-branch-sync.yaml create mode 100644 placement-import/.github/workflows/release-placement-operator.yaml create mode 100644 placement-import/.gitignore create mode 100644 placement-import/.golangci.yaml create mode 100644 placement-import/.pre-commit-config.yaml create mode 100644 placement-import/.prow_ci.env create mode 100644 placement-import/.pull_request_pipeline create mode 100644 placement-import/.zuul.yaml create mode 100644 placement-import/Dockerfile create mode 100644 placement-import/LICENSE.txt create mode 100644 placement-import/Makefile create mode 100644 placement-import/OWNERS create mode 100644 placement-import/OWNERS_ALIASES create mode 100644 placement-import/PROJECT create mode 100644 placement-import/README.md create mode 100644 placement-import/api/bases/placement.openstack.org_placementapis.yaml create mode 100644 placement-import/api/go.mod create mode 100644 placement-import/api/go.sum create mode 100644 placement-import/api/v1beta1/groupversion_info.go create mode 100644 placement-import/api/v1beta1/placementapi_types.go create mode 100644 placement-import/api/v1beta1/placementapi_webhook.go create mode 100644 placement-import/api/v1beta1/zz_generated.deepcopy.go create mode 100644 placement-import/cmd/main.go create mode 100644 placement-import/config/certmanager/certificate-metrics.yaml create mode 100644 placement-import/config/certmanager/certificate-webhook.yaml create mode 100644 placement-import/config/certmanager/issuer.yaml create mode 100644 placement-import/config/certmanager/kustomization.yaml create mode 100644 placement-import/config/certmanager/kustomizeconfig.yaml create mode 100644 placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml create mode 100644 placement-import/config/crd/kustomization.yaml create mode 100644 placement-import/config/crd/kustomizeconfig.yaml create mode 100644 placement-import/config/default/cert_metrics_manager_patch.yaml create mode 100644 placement-import/config/default/kustomization.yaml create mode 100644 placement-import/config/default/manager_default_images.yaml create mode 100644 placement-import/config/default/manager_metrics_patch.yaml create mode 100644 placement-import/config/default/manager_webhook_patch.yaml create mode 100644 placement-import/config/default/metrics_service.yaml create mode 100644 placement-import/config/manager/kustomization.yaml create mode 100644 placement-import/config/manager/manager.yaml create mode 100644 placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml create mode 100644 placement-import/config/manifests/kustomization.yaml create mode 100644 placement-import/config/network-policy/allow-metrics-traffic.yaml create mode 100644 placement-import/config/network-policy/allow-webhook-traffic.yaml create mode 100644 placement-import/config/network-policy/kustomization.yaml create mode 100644 placement-import/config/prometheus/kustomization.yaml create mode 100644 placement-import/config/prometheus/monitor.yaml create mode 100644 placement-import/config/prometheus/monitor_tls_patch.yaml create mode 100644 placement-import/config/rbac/kustomization.yaml create mode 100644 placement-import/config/rbac/leader_election_role.yaml create mode 100644 placement-import/config/rbac/leader_election_role_binding.yaml create mode 100644 placement-import/config/rbac/metrics_auth_role.yaml create mode 100644 placement-import/config/rbac/metrics_auth_role_binding.yaml create mode 100644 placement-import/config/rbac/metrics_reader_role.yaml create mode 100644 placement-import/config/rbac/placementapi_admin_role.yaml create mode 100644 placement-import/config/rbac/placementapi_editor_role.yaml create mode 100644 placement-import/config/rbac/placementapi_viewer_role.yaml create mode 100644 placement-import/config/rbac/role.yaml create mode 100644 placement-import/config/rbac/role_binding.yaml create mode 100644 placement-import/config/rbac/service_account.yaml create mode 100644 placement-import/config/samples/kustomization.yaml create mode 100644 placement-import/config/samples/placement_v1beta1_placementapi.yaml create mode 100644 placement-import/config/samples/placement_v1beta1_placementtls.yaml create mode 100644 placement-import/config/scorecard/bases/config.yaml create mode 100644 placement-import/config/scorecard/kustomization.yaml create mode 100644 placement-import/config/scorecard/patches/basic.config.yaml create mode 100644 placement-import/config/scorecard/patches/olm.config.yaml create mode 100644 placement-import/config/webhook/kustomization.yaml create mode 100644 placement-import/config/webhook/kustomizeconfig.yaml create mode 100644 placement-import/config/webhook/manifests.yaml create mode 100644 placement-import/config/webhook/service.yaml create mode 100644 placement-import/go.mod create mode 100644 placement-import/go.sum create mode 100644 placement-import/hack/boilerplate.go.txt create mode 100755 placement-import/hack/build-crd-schema-checker.sh create mode 100755 placement-import/hack/clean_local_webhook.sh create mode 100755 placement-import/hack/crd-schema-checker.sh create mode 100755 placement-import/hack/run_with_local_webhook.sh create mode 100644 placement-import/internal/controller/placementapi_controller.go create mode 100644 placement-import/internal/placement/const.go create mode 100644 placement-import/internal/placement/dbsync.go create mode 100644 placement-import/internal/placement/deployment.go create mode 100644 placement-import/internal/placement/volumes.go create mode 100644 placement-import/internal/webhook/v1beta1/placementapi_webhook.go create mode 100644 placement-import/kuttl-test.yaml create mode 100644 placement-import/renovate.json create mode 100644 placement-import/templates/placementapi/config/httpd.conf create mode 100644 placement-import/templates/placementapi/config/placement-api-config.json create mode 100644 placement-import/templates/placementapi/config/placement-dbsync-config.json create mode 100644 placement-import/templates/placementapi/config/placement.conf create mode 100644 placement-import/templates/placementapi/config/ssl.conf create mode 100644 placement-import/test/functional/base_test.go create mode 100644 placement-import/test/functional/placementapi_controller_test.go create mode 100644 placement-import/test/functional/placementapi_webhook_test.go create mode 100644 placement-import/test/functional/suite_test.go create mode 100644 placement-import/test/kuttl/common/assert_sample_deployment.yaml create mode 100644 placement-import/test/kuttl/common/cleanup-placement.yaml create mode 100644 placement-import/test/kuttl/common/errors_cleanup_placement.yaml create mode 100644 placement-import/test/kuttl/common/patch_placement_deploy.yaml create mode 120000 placement-import/test/kuttl/common/placementapi_deploy.yaml create mode 100644 placement-import/test/kuttl/common/tls_certificates.yaml create mode 100644 placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate create mode 100755 placement-import/test/kuttl/lib/helper_functions.sh create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml create mode 100644 placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml create mode 100644 placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml create mode 120000 placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/02-assert.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/03-assert.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/04-assert.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/05-assert.yaml create mode 100644 placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml create mode 120000 placement-import/test/kuttl/tests/placement_scale/06-errors.yaml diff --git a/placement-import/.ci-operator.yaml b/placement-import/.ci-operator.yaml new file mode 100644 index 000000000..df7900c64 --- /dev/null +++ b/placement-import/.ci-operator.yaml @@ -0,0 +1,4 @@ +build_root_image: + name: tools + namespace: openstack-k8s-operators + tag: ci-build-root-golang-1.24-sdk-1.41.1 diff --git a/placement-import/.dockerignore b/placement-import/.dockerignore new file mode 100644 index 000000000..0f046820f --- /dev/null +++ b/placement-import/.dockerignore @@ -0,0 +1,4 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ diff --git a/placement-import/.github/workflows/build-placement-operator.yaml b/placement-import/.github/workflows/build-placement-operator.yaml new file mode 100644 index 000000000..01d1f0107 --- /dev/null +++ b/placement-import/.github/workflows/build-placement-operator.yaml @@ -0,0 +1,26 @@ +name: placement operator image builder + +on: + push: + branches: + - '*' + workflow_dispatch: # This allows manual triggering from the Actions tab if needed + +env: + imageregistry: 'quay.io' + imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} + latesttag: latest + +jobs: + call-build-workflow: + uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/reusable-build-operator.yaml@main + with: + operator_name: placement + go_version: 1.24.x + operator_sdk_version: 1.41.1 + secrets: + IMAGENAMESPACE: ${{ secrets.IMAGENAMESPACE }} + QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} + QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} + REDHATIO_USERNAME: ${{ secrets.REDHATIO_USERNAME }} + REDHATIO_PASSWORD: ${{ secrets.REDHATIO_PASSWORD }} diff --git a/placement-import/.github/workflows/force-bump-pr-manual.yaml b/placement-import/.github/workflows/force-bump-pr-manual.yaml new file mode 100644 index 000000000..45e1b740d --- /dev/null +++ b/placement-import/.github/workflows/force-bump-pr-manual.yaml @@ -0,0 +1,14 @@ +name: Manually Trigger a Force Bump PR + +on: + workflow_dispatch: + +jobs: + call-build-workflow: + uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-pull-request.yaml@main + with: + operator_name: placement + branch_name: ${{ github.ref_name }} + custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 + secrets: + FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} diff --git a/placement-import/.github/workflows/force-bump-pr-scheduled.yaml b/placement-import/.github/workflows/force-bump-pr-scheduled.yaml new file mode 100644 index 000000000..dfc208411 --- /dev/null +++ b/placement-import/.github/workflows/force-bump-pr-scheduled.yaml @@ -0,0 +1,15 @@ +name: Scheduled Force Bump PR + +on: + schedule: + - cron: '0 6 * * 6' # 6AM UTC Saturday + +jobs: + call-build-workflow: + if: github.ref == 'refs/heads/main' && github.repository_owner == 'openstack-k8s-operators' + uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-branches.yaml@main + with: + operator_name: placement + custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 + secrets: + FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} diff --git a/placement-import/.github/workflows/lints.yaml b/placement-import/.github/workflows/lints.yaml new file mode 100644 index 000000000..11281ae8c --- /dev/null +++ b/placement-import/.github/workflows/lints.yaml @@ -0,0 +1,14 @@ +name: Lints + +on: [pull_request] + +jobs: + check-go-mod-replace-lines: + name: check for replace lines in go.mod files + runs-on: ubuntu-latest + steps: + - name: Checkout project code + uses: actions/checkout@v3 + - name: check for replace lines in go.mod files + run: | + ! egrep --invert-match -e '^replace.*/api => \./api|^replace.*//allow-merging$' `find . -name 'go.mod'` | egrep -e 'go.mod:replace' diff --git a/placement-import/.github/workflows/release-branch-sync.yaml b/placement-import/.github/workflows/release-branch-sync.yaml new file mode 100644 index 000000000..db63bb7a8 --- /dev/null +++ b/placement-import/.github/workflows/release-branch-sync.yaml @@ -0,0 +1,12 @@ +name: Release Branch sync + +on: + push: + branches: + - '*' + schedule: + - cron: '0 * * * *' + +jobs: + call-build-workflow: + uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/release-branch-sync.yaml@main diff --git a/placement-import/.github/workflows/release-placement-operator.yaml b/placement-import/.github/workflows/release-placement-operator.yaml new file mode 100644 index 000000000..62d9aaa24 --- /dev/null +++ b/placement-import/.github/workflows/release-placement-operator.yaml @@ -0,0 +1,48 @@ +name: Release Placement Operator + +on: + release: + types: + - released + - prereleased + +env: + imageregistry: 'quay.io' + imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Tag image + uses: tinact/docker.image-retag@1.0.2 + with: + image_name: ${{ env.imagenamespace }}/ + image_old_tag: ${{ github.sha }} + image_new_tag: ${{ github.event.release.tag_name }} + registry: ${{ env.imageregistry }} + registry_username: ${{ secrets.QUAY_USERNAME }} + registry_password: ${{ secrets.QUAY_PASSWORD }} + + - name: Tag -bundle image + uses: tinact/docker.image-retag@1.0.2 + with: + image_name: ${{ env.imagenamespace }}/-bundle + image_old_tag: ${{ github.sha }} + image_new_tag: ${{ github.event.release.tag_name }} + registry: ${{ env.imageregistry }} + registry_username: ${{ secrets.QUAY_USERNAME }} + registry_password: ${{ secrets.QUAY_PASSWORD }} + + - name: Tag -index image + uses: tinact/docker.image-retag@1.0.2 + with: + image_name: ${{ env.imagenamespace }}/-index + image_old_tag: ${{ github.sha }} + image_new_tag: ${{ github.event.release.tag_name }} + registry: ${{ env.imageregistry }} + registry_username: ${{ secrets.QUAY_USERNAME }} + registry_password: ${{ secrets.QUAY_PASSWORD }} diff --git a/placement-import/.gitignore b/placement-import/.gitignore new file mode 100644 index 000000000..1098b4058 --- /dev/null +++ b/placement-import/.gitignore @@ -0,0 +1,37 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +/bin +testbin/* + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Kubernetes Generated files - skip generated files, except for vendored files + +!vendor/**/zz_generated.* + +#Operator SDK generated files +/bundle/ +bundle.Dockerfile +config/manager/kustomization.yaml + +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ + +# Common CI tools repository +CI_TOOLS_REPO + +# generated workspace file +go.work +go.work.sum diff --git a/placement-import/.golangci.yaml b/placement-import/.golangci.yaml new file mode 100644 index 000000000..bfdf6541c --- /dev/null +++ b/placement-import/.golangci.yaml @@ -0,0 +1,20 @@ +version: 2 + +linters: + # Enable specific linter + # https://golangci-lint.run/usage/linters/#enabled-by-default + enable: + - errorlint + - revive + - ginkgolinter + - govet + - gosec + - errname + - err113 + +formatters: + enable: + - gofmt + +run: + timeout: 5m diff --git a/placement-import/.pre-commit-config.yaml b/placement-import/.pre-commit-config.yaml new file mode 100644 index 000000000..5f6e53833 --- /dev/null +++ b/placement-import/.pre-commit-config.yaml @@ -0,0 +1,75 @@ +repos: +- repo: local + hooks: + - id: make-manifests + name: make-manifests + language: system + entry: make + args: ['manifests'] + pass_filenames: false + - id: make-generate + name: make-generate + language: system + entry: make + args: ['generate'] + pass_filenames: false + - id: make-operator-lint + name: make-operator-lint + language: system + entry: make + args: ['operator-lint'] + pass_filenames: false + - id: make-bundle + name: make-bundle + language: system + entry: make + args: ['bundle', 'VERSION=0.0.1'] + pass_filenames: false + - id: make-crd-schema-check + name: make-crd-schema-check + language: system + entry: make + args: ['crd-schema-check'] + pass_filenames: false + +- repo: https://github.com/dnephin/pre-commit-golang + rev: v0.5.1 + hooks: + - id: go-mod-tidy + +- repo: https://github.com/golangci/golangci-lint + rev: v2.7.2 + hooks: + - id: golangci-lint-full + args: ["--verbose"] + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-added-large-files + - id: fix-byte-order-marker + - id: check-case-conflict + - id: check-executables-have-shebangs + exclude: ^vendor + - id: check-shebang-scripts-are-executable + exclude: ^vendor + - id: check-merge-conflict + - id: check-symlinks + - id: destroyed-symlinks + - id: check-yaml + args: [-m] + - id: check-json + - id: detect-private-key + - id: end-of-file-fixer + exclude: ^vendor + - id: no-commit-to-branch + - id: trailing-whitespace + exclude: ^vendor + +- repo: https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci + # NOTE(gibi): we cannot automatically track main here + # see https://pre-commit.com/#using-the-latest-version-for-a-repository + rev: e30d72fcbced0ab8a7b6d23be1dee129e2a7b849 + hooks: + - id: kuttl-single-test-assert + args: ["test/kuttl"] diff --git a/placement-import/.prow_ci.env b/placement-import/.prow_ci.env new file mode 100644 index 000000000..ce722ac77 --- /dev/null +++ b/placement-import/.prow_ci.env @@ -0,0 +1,2 @@ +export USE_IMAGE_DIGESTS=true +export FAIL_FIPS_CHECK=true diff --git a/placement-import/.pull_request_pipeline b/placement-import/.pull_request_pipeline new file mode 100644 index 000000000..7f7c0ed30 --- /dev/null +++ b/placement-import/.pull_request_pipeline @@ -0,0 +1,39 @@ +pipeline { + agent none + stages { + stage('Pull request proposed jobs') { + when { + branch 'PR-*' + } + parallel { + stage('DFG-converged-openstack-k8s-placement-operator-distgit-pr') { + steps { + build job: 'DFG-converged-openstack-k8s-placement-operator-distgit-pr', + parameters: [ + string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) + ] + } + } + stage('DFG-converged-openstack-k8s-placement-operator-buildah') { + steps { + build job: 'DFG-converged-openstack-k8s-placement-operator-buildah', + parameters: [ + string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) + ] + } + } + } + } + stage('Pull request merged jobs') { + when { + allOf { + environment name: 'CHANGE_ID', value: '' + branch 'main' + } + } + steps { + build job: 'DFG-converged-openstack-k8s-placement-operator-merge' + } + } + } +} diff --git a/placement-import/.zuul.yaml b/placement-import/.zuul.yaml new file mode 100644 index 000000000..f06cdb96c --- /dev/null +++ b/placement-import/.zuul.yaml @@ -0,0 +1,9 @@ +--- + +- project: + name: openstack-k8s-operators/placement-operator + default-branch: main + merge-mode: rebase + github-check: + jobs: + - noop diff --git a/placement-import/Dockerfile b/placement-import/Dockerfile new file mode 100644 index 000000000..cbc768ab6 --- /dev/null +++ b/placement-import/Dockerfile @@ -0,0 +1,75 @@ +ARG GOLANG_BUILDER=registry.access.redhat.com/ubi9/go-toolset:1.24 +ARG OPERATOR_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:9.6 + +# Build the manager binary +FROM $GOLANG_BUILDER AS builder + +#Arguments required by OSBS build system +ARG CACHITO_ENV_FILE=/remote-source/cachito.env + +ARG REMOTE_SOURCE=. +ARG REMOTE_SOURCE_DIR=/remote-source +ARG REMOTE_SOURCE_SUBDIR= +ARG DEST_ROOT=/dest-root +ARG GO_BUILD_EXTRA_ARGS="-tags strictfipsruntime" +ARG GO_BUILD_EXTRA_ENV_ARGS="CGO_ENABLED=1 GO111MODULE=on" + +COPY $REMOTE_SOURCE $REMOTE_SOURCE_DIR +WORKDIR $REMOTE_SOURCE_DIR/$REMOTE_SOURCE_SUBDIR +USER root +RUN mkdir -p ${DEST_ROOT}/usr/local/bin/ + +# 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 if [ ! -f $CACHITO_ENV_FILE ]; then go mod download ; fi + +# Build manager +RUN if [ -f $CACHITO_ENV_FILE ] ; then source $CACHITO_ENV_FILE ; fi ; env ${GO_BUILD_EXTRA_ENV_ARGS} go build ${GO_BUILD_EXTRA_ARGS} -a -o ${DEST_ROOT}/manager cmd/main.go + +RUN cp -r templates ${DEST_ROOT}/templates + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM $OPERATOR_BASE_IMAGE + +ARG DEST_ROOT=/dest-root +# NONROOT default id https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8= +ARG USER_ID=65532 + +ARG IMAGE_COMPONENT="placement-operator-container" +ARG IMAGE_NAME="placement-operator" +ARG IMAGE_VERSION="1.0.0" +ARG IMAGE_SUMMARY="Placement Operator" +ARG IMAGE_DESC="This image includes the placement-operator" +ARG IMAGE_TAGS="cn-openstack openstack" + +### DO NOT EDIT LINES BELOW +# Auto generated using CI tools from +# https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci + +# Labels required by upstream and osbs build system +LABEL com.redhat.component="${IMAGE_COMPONENT}" \ + name="${IMAGE_NAME}" \ + version="${IMAGE_VERSION}" \ + summary="${IMAGE_SUMMARY}" \ + io.k8s.name="${IMAGE_NAME}" \ + io.k8s.description="${IMAGE_DESC}" \ + io.openshift.tags="${IMAGE_TAGS}" +### DO NOT EDIT LINES ABOVE + +ENV USER_UID=$USER_ID \ + OPERATOR_TEMPLATES=/usr/share/placement-operator/templates/ + +WORKDIR / + +# Install operator binary to WORKDIR +COPY --from=builder ${DEST_ROOT}/manager . + +# Install templates +COPY --from=builder ${DEST_ROOT}/templates ${OPERATOR_TEMPLATES} + +USER $USER_ID + +ENV PATH="/:${PATH}" + +ENTRYPOINT ["/manager"] diff --git a/placement-import/LICENSE.txt b/placement-import/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/placement-import/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/placement-import/Makefile b/placement-import/Makefile new file mode 100644 index 000000000..0e4645783 --- /dev/null +++ b/placement-import/Makefile @@ -0,0 +1,399 @@ +# VERSION defines the project version for the bundle. +# Update this value when you upgrade the version of your project. +# To re-generate a bundle for another specific version without changing the standard setup, you can: +# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) +# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) +VERSION ?= 0.0.1 + +# CHANNELS define the bundle channels used in the bundle. +# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") +# To re-generate a bundle for other specific channels without changing the standard setup, you can: +# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) +# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif + +# DEFAULT_CHANNEL defines the default channel used in the bundle. +# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") +# To re-generate a bundle for any other default channel without changing the default setup, you can: +# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) +# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. +# This variable is used to construct full image tags for bundle and catalog images. +# +# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both +# openstack.org/placement-operator-bundle:$VERSION and openstack.org/placement-operator-catalog:$VERSION. +IMAGE_TAG_BASE ?= quay.io/$(USER)/placement-operator + +# BUNDLE_IMG defines the image:tag used for the bundle. +# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) +BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + +# Set the Operator SDK version to use. By default, what is installed on the system is used. +# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. +OPERATOR_SDK_VERSION ?= v1.41.1 + +# Image URL to use all building/pushing image targets +DEFAULT_IMG ?= quay.io/openstack-k8s-operators/placement-operator:latest +IMG ?= $(DEFAULT_IMG) +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.31 + +SETUP_ENVTEST_VERSION ?= release-0.22 + +# 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 + +# 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 + +GOTOOLCHAIN_VERSION ?= go1.24.0 + +.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 commands 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 && \ + rm -f api/bases/* && cp -a config/crd/bases api/ + +.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: gowork ## Run go vet against code. + go vet ./... + go vet ./api/... + +.PHONY: test +test: manifests generate gowork fmt vet envtest ginkgo ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" $(GINKGO) --trace --cover --coverpkg=../../internal/...,../../api/v1beta1/... --coverprofile cover.out --covermode=atomic --randomize-all $(GINKGO_ARGS) ./test/... ./internal/... + +##@ Build + +.PHONY: build +build: generate fmt vet ## Build manager binary. + go build -o bin/manager cmd/main.go + +.PHONY: run +run: export METRICS_PORT?=8080 +run: export HEALTH_PORT?=8081 +run: export PPROF_PORT?=8082 +run: export ENABLE_WEBHOOKS?=false +run: manifests generate fmt vet ## Run a controller from your host. + /bin/bash hack/clean_local_webhook.sh + go run ./cmd/main.go -metrics-bind-address ":$(METRICS_PORT)" -health-probe-bind-address ":$(HEALTH_PORT)" -pprof-bind-address ":$(PPROF_PORT)" + + +# Extra vars which will be passed to the Docker-build +DOCKER_BUILD_ARGS ?= + +.PHONY: docker-build +docker-build: ## Build docker image with the manager. + podman build -t ${IMG} . ${DOCKER_BUILD_ARGS} + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + podman push ${IMG} + +# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: +# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ +# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ +# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> than the export will fail) +# To properly provided solutions that supports more than one platform you should use this option. +PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le +.PHONY: docker-buildx +docker-buildx: test ## 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 + - docker buildx create --name project-v3-builder + docker buildx use project-v3-builder + - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross + - docker buildx rm project-v3-builder + rm Dockerfile.cross + +##@ 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: ## 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 - + +##@ Build Dependencies + +## Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +## Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize +CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +ENVTEST ?= $(LOCALBIN)/setup-envtest +GINKGO ?= $(LOCALBIN)/ginkgo + +## Tool Versions +KUSTOMIZE_VERSION ?= v5.6.0 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 + +KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" +.PHONY: kustomize +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +$(KUSTOMIZE): $(LOCALBIN) + @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ + echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ + rm -rf $(LOCALBIN)/kustomize; \ + fi + test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } + +.PHONY: controller-gen +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. +$(CONTROLLER_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ + GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + +.PHONY: envtest +envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. +$(ENVTEST): $(LOCALBIN) + test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(SETUP_ENVTEST_VERSION) + +.PHONY: ginkgo +ginkgo: $(GINKGO) ## Download ginkgo locally if necessary. +$(GINKGO): $(LOCALBIN) + test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo + +.PHONY: operator-sdk +OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk +operator-sdk: ## Download operator-sdk locally if necessary. +ifeq (,$(wildcard $(OPERATOR_SDK))) +ifeq (, $(shell which operator-sdk 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPERATOR_SDK)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ + chmod +x $(OPERATOR_SDK) ;\ + } +else +OPERATOR_SDK = $(shell which operator-sdk) +endif +endif + +.PHONY: bundle +bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. + $(OPERATOR_SDK) generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) + $(OPERATOR_SDK) bundle validate ./bundle --verbose + +.PHONY: bundle-build +bundle-build: ## Build the bundle image. + podman build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +.PHONY: bundle-push +bundle-push: ## Push the bundle image. + $(MAKE) docker-push IMG=$(BUNDLE_IMG) + +.PHONY: opm +OPM = ./bin/opm +opm: ## Download opm locally if necessary. +ifeq (,$(wildcard $(OPM))) +ifeq (,$(shell which opm 2>/dev/null)) + @{ \ + set -e ;\ + mkdir -p $(dir $(OPM)) ;\ + OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.29.0/$${OS}-$${ARCH}-opm ;\ + chmod +x $(OPM) ;\ + } +else +OPM = $(shell which opm) +endif +endif + +# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). +# These images MUST exist in a registry and be pull-able. +BUNDLE_IMGS ?= $(BUNDLE_IMG) + +# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). +CATALOG_IMG ?= $(IMAGE_TAG_BASE)-index:v$(VERSION) + +# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. +ifneq ($(origin CATALOG_BASE_IMG), undefined) +FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) +endif + +# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. +# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: +# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator +.PHONY: catalog-build +catalog-build: opm ## Build a catalog image. + $(OPM) index add --container-tool podman --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + +# Push the catalog image. +.PHONY: catalog-push +catalog-push: ## Push a catalog image. + $(MAKE) docker-push IMG=$(CATALOG_IMG) + + +# CI tools repo for running tests +CI_TOOLS_REPO := https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci +CI_TOOLS_REPO_DIR = $(shell pwd)/CI_TOOLS_REPO +.PHONY: get-ci-tools +get-ci-tools: + if [ -d "$(CI_TOOLS_REPO_DIR)" ]; then \ + echo "Ci tools exists"; \ + pushd "$(CI_TOOLS_REPO_DIR)"; \ + git pull --rebase; \ + popd; \ + else \ + git clone $(CI_TOOLS_REPO) "$(CI_TOOLS_REPO_DIR)"; \ + fi + +# Run go fmt against code +gofmt: get-ci-tools + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gofmt.sh + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gofmt.sh ./api + +# Run go vet against code +govet: get-ci-tools + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh ./api + +# Run go test against code +gotest: test + +# Run golangci-lint test against code +golangci: get-ci-tools + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/golangci.sh + GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/golangci.sh ./api + +# Run go lint against code +golint: get-ci-tools + GOWORK=off PATH=$(GOBIN):$(PATH); $(CI_TOOLS_REPO_DIR)/test-runner/golint.sh + GOWORK=off PATH=$(GOBIN):$(PATH); $(CI_TOOLS_REPO_DIR)/test-runner/golint.sh ./api + +.PHONY: gowork +gowork: ## Generate go.work file to support our multi module repository + test -f go.work || GOTOOLCHAIN=$(GOTOOLCHAIN_VERSION) go work init + go work use . + go work use ./api + go work sync + +.PHONY: operator-lint +operator-lint: gowork ## Runs operator-lint + GOBIN=$(LOCALBIN) go install github.com/gibizer/operator-lint@v0.3.0 + go vet -vettool=$(LOCALBIN)/operator-lint ./... ./api/... + +# Used for webhook testing +# The configure_local_webhook.sh script below will remove any OLM webhooks +# for the operator and also scale its deployment replicas down to 0 so that +# the operator can run locally. +# We will attempt to catch SIGINT/SIGTERM and clean up the local webhooks, +# but it may be necessary to manually run ./hack/clean_local_webhook.sh +# before deploying with OLM again for other untrappable signals. +SKIP_CERT ?=false +.PHONY: run-with-webhook +run-with-webhook: export METRICS_PORT?=8080 +run-with-webhook: export HEALTH_PORT?=8081 +run-with-webhook: export PPROF_PORT?=8082 +run-with-webhook: export WEBHOOK_PORT?=9443 +run-with-webhook: manifests generate fmt vet ## Run a controller from your host. + /bin/bash hack/clean_local_webhook.sh + /bin/bash hack/run_with_local_webhook.sh + +.PHONY: tidy +tidy: ## Run go mod tidy on every mod file in the repo + go mod tidy + cd ./api && go mod tidy + +OPERATOR_NAMESPACE ?= openstack-operators + +scale-down-placement-controller-csv: + @echo "Scaling placement-controller-manager to 0 in CSV" + oc patch csv -n $(OPERATOR_NAMESPACE) placement-operator.v0.0.1 --type json -p='[{"op": "replace", "path": "/spec/install/spec/deployments/0/spec/replicas", "value": 0}]' + @echo "Removing olm installed webhooks from CSV" + $(eval has_webhooks=$(shell oc get -o json csv placement-operator.v0.0.1 | jq ".spec.webhookdefinitions")) + if [ "$(has_webhooks)" != "null" ]; then \ + oc patch csv -n $(OPERATOR_NAMESPACE) placement-operator.v0.0.1 --type json -p='[{"op": "remove", "path": "/spec/webhookdefinitions"}]'; \ + fi + +CRD_SCHEMA_CHECKER_VERSION ?= release-4.16 +BRANCH ?= main +.PHONY: force-bump +force-bump: ## Force bump operator and lib-common dependencies + for dep in $$(cat go.mod | grep openstack-k8s-operators | grep -vE -- 'indirect|placement-operator|^replace' | awk '{print $$1}'); do \ + go get $$dep@$(BRANCH) ; \ + done + for dep in $$(cat api/go.mod | grep openstack-k8s-operators | grep -vE -- 'indirect|placement-operator|^replace' | awk '{print $$1}'); do \ + cd ./api && go get $$dep@$(BRANCH) && cd .. ; \ + done + +PHONY: crd-schema-check +crd-schema-check: manifests + INSTALL_DIR=$(LOCALBIN) CRD_SCHEMA_CHECKER_VERSION=$(CRD_SCHEMA_CHECKER_VERSION) hack/build-crd-schema-checker.sh + INSTALL_DIR=$(LOCALBIN) BASE_REF="$${PULL_BASE_SHA:-$(BRANCH)}" hack/crd-schema-checker.sh diff --git a/placement-import/OWNERS b/placement-import/OWNERS new file mode 100644 index 000000000..09bfbb548 --- /dev/null +++ b/placement-import/OWNERS @@ -0,0 +1,8 @@ +# See the OWNERS docs at https://go.k8s.io/owners +approvers: + - openstack-approvers + - compute-approvers + +reviewers: + - openstack-approvers + - compute-approvers diff --git a/placement-import/OWNERS_ALIASES b/placement-import/OWNERS_ALIASES new file mode 100644 index 000000000..5fb1e16da --- /dev/null +++ b/placement-import/OWNERS_ALIASES @@ -0,0 +1,15 @@ +# See the OWNERS_ALIASES docs: https://git.k8s.io/community/contributors/guide/owners.md#owners_aliases + +aliases: + openstack-approvers: + - abays + - dprince + - stuggi + compute-approvers: + - gibizer + - SeanMooney + - bogdando + - kk7ds + - mrKisaoLamb + - auniyal61 + - ratailor diff --git a/placement-import/PROJECT b/placement-import/PROJECT new file mode 100644 index 000000000..2294a3bac --- /dev/null +++ b/placement-import/PROJECT @@ -0,0 +1,27 @@ +# 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: openstack.org +layout: +- go.kubebuilder.io/v4 +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} +projectName: placement-operator +repo: github.com/openstack-k8s-operators/placement-operator +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: openstack.org + group: placement + kind: PlacementAPI + path: github.com/openstack-k8s-operators/placement-operator/api/v1beta1 + version: v1beta1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 +version: "3" diff --git a/placement-import/README.md b/placement-import/README.md new file mode 100644 index 000000000..9b525a483 --- /dev/null +++ b/placement-import/README.md @@ -0,0 +1,178 @@ +# placement-operator + +A Kubernetes Operator built using the [Operator Framework](https://github.com/operator-framework) for Go. +The Operator provides a way to easily install and manage an OpenStack Placement installation on Kubernetes. +This Operator was developed using [TCIB](https://github.com/openstack-k8s-operators/tcib/blob/main/container-images/containers.yaml) containers for OpenStack. + +# Deployment + +The operator is intended to be deployed via OLM [Operator Lifecycle Manager](https://github.com/operator-framework/operator-lifecycle-manager) + +# API Example + +The Operator creates a custom PlacementAPI resource that can be used to create Placement API +instances within the cluster. Example CR to create a Placement API in your cluster: + +```yaml +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + containerImage: quay.io/podified-antelope-centos9/openstack-placement-api:current-podified + databaseInstance: openstack + secret: placement-secret +``` + +## Example: configure Placement with additional networks + +The Placement spec can be used to configure Placement to have the pods +being attached to additional networks. + +Create a network-attachement-definition which then can be referenced +from the Placement API CR. + +``` +--- +apiVersion: k8s.cni.cncf.io/v1 +kind: NetworkAttachmentDefinition +metadata: + name: storage + namespace: openstack +spec: + config: | + { + "cniVersion": "0.3.1", + "name": "storage", + "type": "macvlan", + "master": "enp7s0.21", + "ipam": { + "type": "whereabouts", + "range": "172.18.0.0/24", + "range_start": "172.18.0.50", + "range_end": "172.18.0.100" + } + } +``` + +The following represents an example of Placement resource that can be used +to trigger the service deployment, and have the service pods attached to +the storage network using the above NetworkAttachmentDefinition. + +``` +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + ... + networkAttachents: + - storage +... +``` + +When the service is up and running, it will now have an additional nic +configured for the storage network: + +``` +# oc rsh placement-75f5cd6595-kpfr2 +sh-5.1# ip a +1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +3: eth0@if298: mtu 1450 qdisc noqueue state UP group default + link/ether 0a:58:0a:82:01:18 brd ff:ff:ff:ff:ff:ff link-netnsid 0 + inet 10.130.1.24/23 brd 10.130.1.255 scope global eth0 + valid_lft forever preferred_lft forever + inet6 fe80::4cf2:a3ff:feb0:932/64 scope link + valid_lft forever preferred_lft forever +4: net1@if26: mtu 1500 qdisc noqueue state UP group default + link/ether a2:f1:3b:12:fd:be brd ff:ff:ff:ff:ff:ff link-netnsid 0 + inet 172.18.0.52/24 brd 172.18.0.255 scope global net1 + valid_lft forever preferred_lft forever + inet6 fe80::a0f1:3bff:fe12:fdbe/64 scope link + valid_lft forever preferred_lft forever +``` + +## Example: expose Placement to an isolated network + +The Placement spec can be used to configure Placement to register e.g. +the internal endpoint to an isolated network. MetalLB is used for this +scenario. + +As a pre requisite, MetalLB needs to be installed and worker nodes +prepared to work as MetalLB nodes to serve the LoadBalancer service. + +In this example the following MetalLB IPAddressPool is used: + +``` +--- +apiVersion: metallb.io/v1beta1 +kind: IPAddressPool +metadata: + name: osp-internalapi + namespace: metallb-system +spec: + addresses: + - 172.17.0.200-172.17.0.210 + autoAssign: false +``` + +The following represents an example of Placement resource that can be used +to trigger the service deployment, and have the internal placementAPI endpoint +registerd as a MetalLB service using the IPAddressPool `osp-internal`, +request to use the IP `172.17.0.202` as the VIP and the IP is shared with +other services. + +``` +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + ... + override: + service: + internal: + metadata: + annotations: + metallb.universe.tf/address-pool: osp-internalapi + metallb.universe.tf/allow-shared-ip: internalapi + metallb.universe.tf/loadBalancerIPs: 172.17.0.202 + spec: + type: LoadBalancer + ... +... +``` + +The internal placement endpoint gets registered with its service name. This +service name needs to resolve to the `LoadBalancerIP` on the isolated network +either by DNS or via /etc/hosts: + +``` +# openstack endpoint list -c 'Service Name' -c Interface -c URL --service placement ++--------------+-----------+------------------------------------------------------------------+ +| Service Name | Interface | URL | ++--------------+-----------+------------------------------------------------------------------+ +| placement | internal | http://placement-internal.openstack.svc:8778 | +| placement | public | http://placement-public-openstack.apps.ostest.test.metalkube.org | ++--------------+-----------+------------------------------------------------------------------+ +``` + +# Testing +The repository uses [EnvTest](https://book.kubebuilder.io/reference/envtest.html) to validate the operator in a self +contained environment. + +The test can be run in the terminal with: +```shell +make test +``` +or in Visual Studio Code by defining the following in your settings.json: +```json +"go.testEnvVars": { + "KUBEBUILDER_ASSETS":"" +}, +``` diff --git a/placement-import/api/bases/placement.openstack.org_placementapis.yaml b/placement-import/api/bases/placement.openstack.org_placementapis.yaml new file mode 100644 index 000000000..73ef0098b --- /dev/null +++ b/placement-import/api/bases/placement.openstack.org_placementapis.yaml @@ -0,0 +1,502 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: placementapis.placement.openstack.org +spec: + group: placement.openstack.org + names: + kind: PlacementAPI + listKind: PlacementAPIList + plural: placementapis + singular: placementapi + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: NetworkAttachments + jsonPath: .spec.networkAttachments + name: NetworkAttachments + type: string + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: PlacementAPI is the Schema for the placementapis API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PlacementAPISpec defines the desired state of PlacementAPI + properties: + apiTimeout: + default: 60 + description: APITimeout for HAProxy, Apache + minimum: 10 + type: integer + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object + containerImage: + description: PlacementAPI Container Image URL (will be set to environmental + default if empty) + type: string + customServiceConfig: + description: |- + CustomServiceConfig - customize the service config using this parameter to change service defaults, + or overwrite rendered information using raw OpenStack config format. The content gets added to + to /etc//.conf.d directory as custom.conf file. + type: string + databaseAccount: + default: placement + description: DatabaseAccount - name of MariaDBAccount which will be + used to connect. + type: string + databaseInstance: + description: |- + MariaDB instance name + Right now required by the maridb-operator to get the credentials from the instance to create the DB + Might not be required in future + type: string + defaultConfigOverwrite: + additionalProperties: + type: string + description: DefaultConfigOverwrite - interface to overwrite default + config files like policy.yaml. + type: object + networkAttachments: + description: NetworkAttachments is a list of NetworkAttachment resource + names to expose the services to the given network + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + description: NodeSelector to target subset of worker nodes running + this service + type: object + override: + description: Override, provides the ability to override the generated + manifest of several child resources. + properties: + service: + additionalProperties: + description: |- + RoutedOverrideSpec - a routed service override configuration for the Service created to serve traffic + to the cluster. Allows for the manifest of the created Service to be overwritten with custom configuration. + properties: + endpointURL: + type: string + metadata: + description: |- + EmbeddedLabelsAnnotations is an embedded subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. + Only labels and annotations are included. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and services. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + type: object + type: object + spec: + description: |- + OverrideServiceSpec is a subset of the fields included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec + Limited to Type, SessionAffinity, LoadBalancerSourceRanges, ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, + IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy + properties: + externalName: + description: |- + externalName is the external reference that discovery mechanisms will + return as an alias for this service (e.g. a DNS CNAME record). No + proxying will be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + type: string + externalTrafficPolicy: + description: |- + externalTrafficPolicy describes how nodes distribute service traffic they + receive on one of the Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure + the service in a way that assumes that external load balancers will take care + of balancing the service traffic between nodes, and so each node will deliver + traffic only to the node-local endpoints of the service, without masquerading + the client source IP. (Traffic mistakenly sent to a node with no endpoints will + be dropped.) The default value, "Cluster", uses the standard behavior of + routing to all endpoints evenly (possibly modified by topology and other + features). Note that traffic sent to an External IP or LoadBalancer IP from + within the cluster will always get "Cluster" semantics, but clients sending to + a NodePort from within the cluster may need to take traffic policy into account + when picking a node. + type: string + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilyPolicy: + description: |- + IPFamilyPolicy represents the dual-stack-ness requested or required by + this Service. If there is no value provided, then this field will be set + to SingleStack. Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured clusters or + a single IP family on single-stack clusters), or "RequireDualStack" + (two IP families on dual-stack configured clusters, otherwise fail). The + ipFamilies and clusterIPs fields depend on the value of this field. This + field will be wiped when updating a service to type ExternalName. + type: string + loadBalancerClass: + description: |- + loadBalancerClass is the class of the load balancer implementation this Service belongs to. + If specified, the value of this field must be a label-style identifier, with an optional prefix, + e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + balancer implementation is used, today this is typically done through the cloud provider integration, + but should apply for any default implementation. If set, it is assumed that a load balancer + implementation is watching for Services with a matching class. Any default load balancer + implementation (e.g. cloud providers) should ignore Services that set this field. + This field can only be set when creating or updating a Service to type 'LoadBalancer'. + Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + type: string + loadBalancerSourceRanges: + description: |- + If specified and supported by the platform, this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client IPs. This field will be ignored if the + cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + items: + type: string + type: array + x-kubernetes-list-type: atomic + sessionAffinity: + description: |- + Supports "ClientIP" and "None". Used to maintain session affinity. + Enable client IP based session affinity. + Must be ClientIP or None. + Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + type: + description: |- + type determines how the Service is exposed. Defaults to ClusterIP. Valid + options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that is not + specified, by manual construction of an Endpoints object or + EndpointSlice objects. If clusterIP is "None", no virtual IP is + allocated and the endpoints are published as a set of endpoints rather + than a virtual IP. + "NodePort" builds on ClusterIP and allocates a port on every node which + routes to the same endpoints as the clusterIP. + "LoadBalancer" builds on NodePort and creates an external load-balancer + (if supported in the current cloud) which routes to the same endpoints + as the clusterIP. + "ExternalName" aliases this service to the specified externalName. + Several other fields do not apply to ExternalName services. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: string + type: object + type: object + description: |- + Override configuration for the Service created to serve traffic to the cluster. + The key must be the endpoint type (public, internal) + type: object + type: object + passwordSelectors: + default: + service: PlacementPassword + description: PasswordSelectors - Selectors to identify the DB and + ServiceUser password from the Secret + properties: + service: + default: PlacementPassword + description: Service - Selector to get the service user password + from the Secret + type: string + type: object + preserveJobs: + default: false + description: PreserveJobs - do not delete jobs after they finished + e.g. to check logs + type: boolean + replicas: + default: 1 + description: Replicas of placement API to run + format: int32 + maximum: 32 + minimum: 0 + type: integer + resources: + description: |- + Resources - Compute Resources required by this service (Limits/Requests). + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + secret: + description: Secret containing OpenStack password information for + placement PlacementPassword + type: string + serviceUser: + default: placement + description: ServiceUser - optional username used for this service + to register in keystone + type: string + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret for + the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + public: + description: Public GenericService - holds the secret for + the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object + topologyRef: + description: |- + TopologyRef to apply the Topology defined by the associated CR referenced + by name + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + required: + - containerImage + - databaseInstance + - secret + type: object + status: + description: PlacementAPIStatus defines the observed state of PlacementAPI + properties: + conditions: + description: Conditions + items: + description: Condition defines an observation of a API resource + operational state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. + type: string + severity: + description: |- + Severity provides a classification of Reason code, so the current situation is immediately + understandable and could act accordingly. + It is meant for situations where Status=False and it should be indicated if it is just + informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue + and no actions to automatically resolve the issue can/should be done). + For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + databaseHostname: + description: Placement Database Hostname + type: string + hash: + additionalProperties: + type: string + description: Map of hashes to track e.g. job status + type: object + lastAppliedTopology: + description: LastAppliedTopology - the last applied Topology + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + networkAttachments: + additionalProperties: + items: + type: string + type: array + description: NetworkAttachments status of the deployment pods + type: object + observedGeneration: + description: ObservedGeneration - the most recent generation observed + for this service. If the observed generation is less than the spec + generation, then the controller has not processed the latest changes. + format: int64 + type: integer + readyCount: + description: ReadyCount of placement API instances + format: int32 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/placement-import/api/go.mod b/placement-import/api/go.mod new file mode 100644 index 000000000..ebd069649 --- /dev/null +++ b/placement-import/api/go.mod @@ -0,0 +1,95 @@ +module github.com/openstack-k8s-operators/placement-operator/api + +go 1.24.4 + +require ( + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 + k8s.io/api v0.31.14 + k8s.io/apimachinery v0.31.14 + sigs.k8s.io/controller-runtime v0.19.7 +) + +require ( + github.com/beorn7/perks v1.0.1 // 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.12.2 // indirect + github.com/evanphx/json-patch v5.9.11+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 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.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.12.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect + k8s.io/client-go v0.31.14 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // 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.6.0 // indirect +) + +// mschuppert: map to latest commit from release-4.18 tag +// must consistent within modules and service operators +replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e //allow-merging + +// pin these to avoid later versions pulled by rabbitmq +replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging + +replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging + +replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging + +replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging + +replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.14 //allow-merging + +replace k8s.io/code-generator => k8s.io/code-generator v0.31.14 //allow-merging + +replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging + +// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) +replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging + +replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging diff --git a/placement-import/api/go.sum b/placement-import/api/go.sum new file mode 100644 index 000000000..91045d7eb --- /dev/null +++ b/placement-import/api/go.sum @@ -0,0 +1,203 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +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/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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af h1:Ow12j/PVbEtul1bZ7s/ZenVnKPIHK2q+0VgTp+j/wro= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af/go.mod h1:nC/Jf3OYJRML8UEzJ/mn/TQcSCv/nhqO6x6LGkdDt60= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 h1:P1Nx+i6aH7rFy+T4nrB2AlQzzoLMrMwtnZPEsFvC5cM= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= +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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +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/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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/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.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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +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/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= +k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= +k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= +k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= +k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= +k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= +k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= +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-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= +sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/placement-import/api/v1beta1/groupversion_info.go b/placement-import/api/v1beta1/groupversion_info.go new file mode 100644 index 000000000..4f0c92c13 --- /dev/null +++ b/placement-import/api/v1beta1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2022. + +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 v1beta1 contains API Schema definitions for the placement v1beta1 API group +// +kubebuilder:object:generate=true +// +groupName=placement.openstack.org +package v1beta1 + +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: "placement.openstack.org", Version: "v1beta1"} + + // 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/placement-import/api/v1beta1/placementapi_types.go b/placement-import/api/v1beta1/placementapi_types.go new file mode 100644 index 000000000..3354e3b50 --- /dev/null +++ b/placement-import/api/v1beta1/placementapi_types.go @@ -0,0 +1,259 @@ +/* +Copyright 2022. + +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 v1beta1 + +import ( + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" + "github.com/openstack-k8s-operators/lib-common/modules/common/util" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +const ( + // DbSyncHash hash + DbSyncHash = "dbsync" + + // DeploymentHash hash used to detect changes + DeploymentHash = "deployment" + + // Container image fall-back defaults + + // PlacementAPIContainerImage is the fall-back container image for PlacementAPI + PlacementAPIContainerImage = "quay.io/podified-antelope-centos9/openstack-placement-api:current-podified" +) + +// PlacementAPISpec defines the desired state of PlacementAPI +type PlacementAPISpec struct { + PlacementAPISpecCore `json:",inline"` + + // +kubebuilder:validation:Required + // PlacementAPI Container Image URL (will be set to environmental default if empty) + ContainerImage string `json:"containerImage"` +} + +// PlacementAPISpecCore - +type PlacementAPISpecCore struct { + // +kubebuilder:validation:Optional + // +kubebuilder:default=60 + // +kubebuilder:validation:Minimum=10 + // APITimeout for HAProxy, Apache + APITimeout int `json:"apiTimeout"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=placement + // ServiceUser - optional username used for this service to register in keystone + ServiceUser string `json:"serviceUser"` + + // +kubebuilder:validation:Required + // MariaDB instance name + // Right now required by the maridb-operator to get the credentials from the instance to create the DB + // Might not be required in future + DatabaseInstance string `json:"databaseInstance"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=placement + // DatabaseAccount - name of MariaDBAccount which will be used to connect. + DatabaseAccount string `json:"databaseAccount"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=1 + // +kubebuilder:validation:Maximum=32 + // +kubebuilder:validation:Minimum=0 + // Replicas of placement API to run + Replicas *int32 `json:"replicas"` + + // +kubebuilder:validation:Required + // Secret containing OpenStack password information for placement PlacementPassword + Secret string `json:"secret"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default={service: PlacementPassword} + // PasswordSelectors - Selectors to identify the DB and ServiceUser password from the Secret + PasswordSelectors PasswordSelector `json:"passwordSelectors"` + + // +kubebuilder:validation:Optional + // NodeSelector to target subset of worker nodes running this service + NodeSelector *map[string]string `json:"nodeSelector,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:default=false + // PreserveJobs - do not delete jobs after they finished e.g. to check logs + PreserveJobs bool `json:"preserveJobs"` + + // +kubebuilder:validation:Optional + // CustomServiceConfig - customize the service config using this parameter to change service defaults, + // or overwrite rendered information using raw OpenStack config format. The content gets added to + // to /etc//.conf.d directory as custom.conf file. + CustomServiceConfig string `json:"customServiceConfig"` + + // +kubebuilder:validation:Optional + // DefaultConfigOverwrite - interface to overwrite default config files like policy.yaml. + DefaultConfigOverwrite map[string]string `json:"defaultConfigOverwrite,omitempty"` + + // +kubebuilder:validation:Optional + // Resources - Compute Resources required by this service (Limits/Requests). + // https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + Resources corev1.ResourceRequirements `json:"resources,omitempty"` + + // +kubebuilder:validation:Optional + // NetworkAttachments is a list of NetworkAttachment resource names to expose the services to the given network + NetworkAttachments []string `json:"networkAttachments,omitempty"` + + // +kubebuilder:validation:Optional + // Override, provides the ability to override the generated manifest of several child resources. + Override APIOverrideSpec `json:"override,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // TLS - Parameters related to the TLS + TLS tls.API `json:"tls,omitempty"` + + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // Auth - Parameters related to authentication + Auth AuthSpec `json:"auth,omitempty"` + + // +kubebuilder:validation:Optional + // TopologyRef to apply the Topology defined by the associated CR referenced + // by name + TopologyRef *topologyv1.TopoRef `json:"topologyRef,omitempty"` +} + +// APIOverrideSpec to override the generated manifest of several child resources. +type APIOverrideSpec struct { + // Override configuration for the Service created to serve traffic to the cluster. + // The key must be the endpoint type (public, internal) + Service map[service.Endpoint]service.RoutedOverrideSpec `json:"service,omitempty"` +} + +// AuthSpec defines authentication parameters +type AuthSpec struct { + // +kubebuilder:validation:Optional + // +operator-sdk:csv:customresourcedefinitions:type=spec + // ApplicationCredentialSecret - Secret containing Application Credential ID and Secret + ApplicationCredentialSecret string `json:"applicationCredentialSecret,omitempty"` +} + +// PasswordSelector to identify the DB and AdminUser password from the Secret +type PasswordSelector struct { + // +kubebuilder:validation:Optional + // +kubebuilder:default="PlacementPassword" + // Service - Selector to get the service user password from the Secret + Service string `json:"service"` +} + +// PlacementAPIStatus defines the observed state of PlacementAPI +type PlacementAPIStatus struct { + // ReadyCount of placement API instances + ReadyCount int32 `json:"readyCount,omitempty"` + + // Map of hashes to track e.g. job status + Hash map[string]string `json:"hash,omitempty"` + + // Conditions + Conditions condition.Conditions `json:"conditions,omitempty" optional:"true"` + + // Placement Database Hostname + DatabaseHostname string `json:"databaseHostname,omitempty"` + + // NetworkAttachments status of the deployment pods + NetworkAttachments map[string][]string `json:"networkAttachments,omitempty"` + + //ObservedGeneration - the most recent generation observed for this service. If the observed generation is less than the spec generation, then the controller has not processed the latest changes. + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // LastAppliedTopology - the last applied Topology + LastAppliedTopology *topologyv1.TopoRef `json:"lastAppliedTopology,omitempty"` +} + +// PlacementAPI is the Schema for the placementapis API +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:printcolumn:name="NetworkAttachments",type="string",JSONPath=".spec.networkAttachments",description="NetworkAttachments" +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[0].status",description="Status" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.conditions[0].message",description="Message" +type PlacementAPI struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PlacementAPISpec `json:"spec,omitempty"` + Status PlacementAPIStatus `json:"status,omitempty"` +} + +// PlacementAPIList contains a list of PlacementAPI +// +kubebuilder:object:root=true +type PlacementAPIList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []PlacementAPI `json:"items"` +} + +func init() { + SchemeBuilder.Register(&PlacementAPI{}, &PlacementAPIList{}) +} + +// IsReady - returns true if PlacementAPI is reconciled successfully +func (instance PlacementAPI) IsReady() bool { + return instance.Status.Conditions.IsTrue(condition.ReadyCondition) +} + +// RbacConditionsSet - set the conditions for the rbac object +func (instance PlacementAPI) RbacConditionsSet(c *condition.Condition) { + instance.Status.Conditions.Set(c) +} + +// RbacNamespace - return the namespace +func (instance PlacementAPI) RbacNamespace() string { + return instance.Namespace +} + +// RbacResourceName - return the name to be used for rbac objects (serviceaccount, role, rolebinding) +func (instance PlacementAPI) RbacResourceName() string { + return "placement-" + instance.Name +} + +// SetupDefaults - initializes any CRD field defaults based on environment variables (the defaulting mechanism itself is implemented via webhooks) +func SetupDefaults() { + // Acquire environmental defaults and initialize Placement defaults with them + placementDefaults := PlacementAPIDefaults{ + ContainerImageURL: util.GetEnvVar("RELATED_IMAGE_PLACEMENT_API_IMAGE_URL_DEFAULT", PlacementAPIContainerImage), + APITimeout: 60, + } + + SetupPlacementAPIDefaults(placementDefaults) +} + +// GetSecret returns the value of the Nova.Spec.Secret +func (instance PlacementAPI) GetSecret() string { + return instance.Spec.Secret +} + +// ValidateTopology - +func (instance *PlacementAPISpecCore) ValidateTopology( + basePath *field.Path, + namespace string, +) field.ErrorList { + var allErrs field.ErrorList + allErrs = append(allErrs, topologyv1.ValidateTopologyRef( + instance.TopologyRef, + *basePath.Child("topologyRef"), namespace)...) + return allErrs +} diff --git a/placement-import/api/v1beta1/placementapi_webhook.go b/placement-import/api/v1beta1/placementapi_webhook.go new file mode 100644 index 000000000..c14982277 --- /dev/null +++ b/placement-import/api/v1beta1/placementapi_webhook.go @@ -0,0 +1,199 @@ +/* +Copyright 2022. + +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. +*/ + +// Generated by: +// +// operator-sdk create webhook --group placement --version v1beta1 --kind PlacementAPI --programmatic-validation --defaulting +// + +package v1beta1 + +import ( + "fmt" + + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// PlacementAPIDefaults - +type PlacementAPIDefaults struct { + ContainerImageURL string + APITimeout int +} + +var placementAPIDefaults PlacementAPIDefaults + +// log is for logging in this package. +var placementapilog = logf.Log.WithName("placementapi-resource") + +// SetupPlacementAPIDefaults - initialize PlacementAPI spec defaults for use with either internal or external webhooks +func SetupPlacementAPIDefaults(defaults PlacementAPIDefaults) { + placementAPIDefaults = defaults + placementapilog.Info("PlacementAPI defaults initialized", "defaults", defaults) +} + +var _ webhook.Defaulter = &PlacementAPI{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *PlacementAPI) Default() { + placementapilog.Info("default", "name", r.Name) + + r.Spec.Default() +} + +// Default - set defaults for this PlacementAPI spec +func (spec *PlacementAPISpec) Default() { + if spec.ContainerImage == "" { + spec.ContainerImage = placementAPIDefaults.ContainerImageURL + } + if spec.APITimeout == 0 { + spec.APITimeout = placementAPIDefaults.APITimeout + } +} + +// Default - set defaults for this PlacementAPI core spec (this version is used by the OpenStackControlplane webhook) +func (spec *PlacementAPISpecCore) Default() { + // nothing here yet +} + +var _ webhook.Validator = &PlacementAPI{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *PlacementAPI) ValidateCreate() (admission.Warnings, error) { + placementapilog.Info("validate create", "name", r.Name) + + errors := r.Spec.ValidateCreate(field.NewPath("spec"), r.Namespace) + if len(errors) != 0 { + placementapilog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "placement.openstack.org", Kind: "PlacementAPI"}, + r.Name, errors) + } + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *PlacementAPI) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + placementapilog.Info("validate update", "name", r.Name) + oldPlacement, ok := old.(*PlacementAPI) + if !ok || oldPlacement == nil { + return nil, apierrors.NewInternalError(fmt.Errorf("unable to convert existing object")) + } + + errors := r.Spec.ValidateUpdate(oldPlacement.Spec, field.NewPath("spec"), r.Namespace) + if len(errors) != 0 { + placementapilog.Info("validation failed", "name", r.Name) + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: "placement.openstack.org", Kind: "PlacementAPI"}, + r.Name, errors) + } + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *PlacementAPI) ValidateDelete() (admission.Warnings, error) { + placementapilog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} + +func (r PlacementAPISpec) ValidateCreate(basePath *field.Path, namespace string) field.ErrorList { + return r.PlacementAPISpecCore.ValidateCreate(basePath, namespace) +} + +func (r PlacementAPISpec) ValidateUpdate(old PlacementAPISpec, basePath *field.Path, namespace string) field.ErrorList { + return r.PlacementAPISpecCore.ValidateCreate(basePath, namespace) +} + +func (r PlacementAPISpecCore) ValidateCreate(basePath *field.Path, namespace string) field.ErrorList { + var allErrs field.ErrorList + + // validate the service override key is valid + allErrs = append(allErrs, service.ValidateRoutedOverrides(basePath.Child("override").Child("service"), r.Override.Service)...) + + allErrs = append(allErrs, ValidateDefaultConfigOverwrite(basePath, r.DefaultConfigOverwrite)...) + + // When a TopologyRef CR is referenced, fail if a different Namespace is + // referenced because is not supported + allErrs = append(allErrs, r.ValidateTopology(basePath, namespace)...) + + return allErrs +} + +func (r PlacementAPISpecCore) ValidateUpdate(old PlacementAPISpecCore, basePath *field.Path, namespace string) field.ErrorList { + var allErrs field.ErrorList + + // validate the service override key is valid + allErrs = append(allErrs, service.ValidateRoutedOverrides(basePath.Child("override").Child("service"), r.Override.Service)...) + + allErrs = append(allErrs, ValidateDefaultConfigOverwrite(basePath, r.DefaultConfigOverwrite)...) + + // When a TopologyRef CR is referenced, fail if a different Namespace is + // referenced because is not supported + allErrs = append(allErrs, r.ValidateTopology(basePath, namespace)...) + + return allErrs +} + +func ValidateDefaultConfigOverwrite( + basePath *field.Path, + validateConfigOverwrite map[string]string, +) field.ErrorList { + var errors field.ErrorList + for requested := range validateConfigOverwrite { + if requested != "policy.yaml" { + errors = append( + errors, + field.Invalid( + basePath.Child("defaultConfigOverwrite"), + requested, + "Only the following keys are valid: policy.yaml", + ), + ) + } + } + return errors +} + +// SetDefaultRouteAnnotations sets HAProxy timeout values of the route +func (spec *PlacementAPISpecCore) SetDefaultRouteAnnotations(annotations map[string]string) { + const haProxyAnno = "haproxy.router.openshift.io/timeout" + // Use a custom annotation to flag when the operator has set the default HAProxy timeout + // With the annotation func determines when to overwrite existing HAProxy timeout with the APITimeout + const placementAnno = "api.placement.openstack.org/timeout" + valPlacementAPI, okPlacementAPI := annotations[placementAnno] + valHAProxy, okHAProxy := annotations[haProxyAnno] + // Human operator set the HAProxy timeout manually + if !okPlacementAPI && okHAProxy { + return + } + // Human operator modified the HAProxy timeout manually without removing the Placemen flag + if okPlacementAPI && okHAProxy && valPlacementAPI != valHAProxy { + delete(annotations, placementAnno) + placementapilog.Info("Human operator modified the HAProxy timeout manually without removing the Placement flag. Deleting the Placement flag to ensure proper configuration.") + return + } + timeout := fmt.Sprintf("%ds", spec.APITimeout) + annotations[placementAnno] = timeout + annotations[haProxyAnno] = timeout +} diff --git a/placement-import/api/v1beta1/zz_generated.deepcopy.go b/placement-import/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 000000000..8c4b13be3 --- /dev/null +++ b/placement-import/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,273 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2022. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + topologyv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIOverrideSpec) DeepCopyInto(out *APIOverrideSpec) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = make(map[service.Endpoint]service.RoutedOverrideSpec, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIOverrideSpec. +func (in *APIOverrideSpec) DeepCopy() *APIOverrideSpec { + if in == nil { + return nil + } + out := new(APIOverrideSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. +func (in *AuthSpec) DeepCopy() *AuthSpec { + if in == nil { + return nil + } + out := new(AuthSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswordSelector) DeepCopyInto(out *PasswordSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSelector. +func (in *PasswordSelector) DeepCopy() *PasswordSelector { + if in == nil { + return nil + } + out := new(PasswordSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPI) DeepCopyInto(out *PlacementAPI) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPI. +func (in *PlacementAPI) DeepCopy() *PlacementAPI { + if in == nil { + return nil + } + out := new(PlacementAPI) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlacementAPI) 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 *PlacementAPIDefaults) DeepCopyInto(out *PlacementAPIDefaults) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIDefaults. +func (in *PlacementAPIDefaults) DeepCopy() *PlacementAPIDefaults { + if in == nil { + return nil + } + out := new(PlacementAPIDefaults) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPIList) DeepCopyInto(out *PlacementAPIList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PlacementAPI, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIList. +func (in *PlacementAPIList) DeepCopy() *PlacementAPIList { + if in == nil { + return nil + } + out := new(PlacementAPIList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlacementAPIList) 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 *PlacementAPISpec) DeepCopyInto(out *PlacementAPISpec) { + *out = *in + in.PlacementAPISpecCore.DeepCopyInto(&out.PlacementAPISpecCore) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpec. +func (in *PlacementAPISpec) DeepCopy() *PlacementAPISpec { + if in == nil { + return nil + } + out := new(PlacementAPISpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPISpecCore) DeepCopyInto(out *PlacementAPISpecCore) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + out.PasswordSelectors = in.PasswordSelectors + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + if in.DefaultConfigOverwrite != nil { + in, out := &in.DefaultConfigOverwrite, &out.DefaultConfigOverwrite + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Resources.DeepCopyInto(&out.Resources) + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) + out.Auth = in.Auth + if in.TopologyRef != nil { + in, out := &in.TopologyRef, &out.TopologyRef + *out = new(topologyv1beta1.TopoRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpecCore. +func (in *PlacementAPISpecCore) DeepCopy() *PlacementAPISpecCore { + if in == nil { + return nil + } + out := new(PlacementAPISpecCore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPIStatus) DeepCopyInto(out *PlacementAPIStatus) { + *out = *in + if in.Hash != nil { + in, out := &in.Hash, &out.Hash + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.LastAppliedTopology != nil { + in, out := &in.LastAppliedTopology, &out.LastAppliedTopology + *out = new(topologyv1beta1.TopoRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIStatus. +func (in *PlacementAPIStatus) DeepCopy() *PlacementAPIStatus { + if in == nil { + return nil + } + out := new(PlacementAPIStatus) + in.DeepCopyInto(out) + return out +} diff --git a/placement-import/cmd/main.go b/placement-import/cmd/main.go new file mode 100644 index 000000000..c1c72f1cc --- /dev/null +++ b/placement-import/cmd/main.go @@ -0,0 +1,303 @@ +/* +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 is the entry point for the placement-operator. +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" + + "github.com/openstack-k8s-operators/placement-operator/internal/controller" + webhookv1beta1 "github.com/openstack-k8s-operators/placement-operator/internal/webhook/v1beta1" + + // +kubebuilder:scaffold:imports + + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common/operator" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + utilruntime.Must(placementv1.AddToScheme(scheme)) + utilruntime.Must(mariadbv1.AddToScheme(scheme)) + utilruntime.Must(keystonev1.AddToScheme(scheme)) + utilruntime.Must(networkv1.AddToScheme(scheme)) + utilruntime.Must(topologyv1.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 pprofBindAddress string + var webhookPort int + 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.StringVar(&pprofBindAddress, "pprof-bind-address", "", "The address the pprof endpoint binds to. Set to empty to disable pprof.") + flag.IntVar(&webhookPort, "webhook-bind-address", 9443, "The port the webhook server binds to.") + 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{ + Port: webhookPort, + 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.21.0/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.21.0/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 + }) + } + + // 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, + options := ctrl.Options{ + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, + HealthProbeBindAddress: probeAddr, + PprofBindAddress: pprofBindAddress, + LeaderElection: enableLeaderElection, + LeaderElectionID: "73d6b7ce.openstack.org", + } + + // apply common openstack operator manager options + err := operator.SetManagerOptions(&options, setupLog) + if err != nil { + setupLog.Error(err, "unable to set manager options") + os.Exit(1) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + cfg, err := config.GetConfig() + if err != nil { + setupLog.Error(err, "") + os.Exit(1) + } + kclient, err := kubernetes.NewForConfig(cfg) + if err != nil { + setupLog.Error(err, "") + os.Exit(1) + } + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + if err := (&controller.PlacementAPIReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Kclient: kclient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PlacementAPI") + os.Exit(1) + } + + // Acquire environmental defaults and initialize operator defaults with them + placementv1.SetupDefaults() + + // nolint:goconst + checker := healthz.Ping + if os.Getenv("ENABLE_WEBHOOKS") != "false" { + if err := webhookv1beta1.SetupPlacementAPIWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "PlacementAPI") + os.Exit(1) + } + checker = mgr.GetWebhookServer().StartedChecker() + } + // +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", checker); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", checker); 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/placement-import/config/certmanager/certificate-metrics.yaml b/placement-import/config/certmanager/certificate-metrics.yaml new file mode 100644 index 000000000..915d753d2 --- /dev/null +++ b/placement-import/config/certmanager/certificate-metrics.yaml @@ -0,0 +1,20 @@ +# The following manifests contain a self-signed issuer CR and a metrics certificate CR. +# More document can be found at https://docs.cert-manager.io +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: metrics-certs # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + dnsNames: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + # replacements in the config/default/kustomization.yaml file. + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: metrics-server-cert diff --git a/placement-import/config/certmanager/certificate-webhook.yaml b/placement-import/config/certmanager/certificate-webhook.yaml new file mode 100644 index 000000000..e6d8ce29e --- /dev/null +++ b/placement-import/config/certmanager/certificate-webhook.yaml @@ -0,0 +1,20 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize + # replacements in the config/default/kustomization.yaml file. + dnsNames: + - SERVICE_NAME.SERVICE_NAMESPACE.svc + - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert diff --git a/placement-import/config/certmanager/issuer.yaml b/placement-import/config/certmanager/issuer.yaml new file mode 100644 index 000000000..834d52465 --- /dev/null +++ b/placement-import/config/certmanager/issuer.yaml @@ -0,0 +1,13 @@ +# The following manifest contains a self-signed issuer CR. +# More information can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} diff --git a/placement-import/config/certmanager/kustomization.yaml b/placement-import/config/certmanager/kustomization.yaml new file mode 100644 index 000000000..fcb7498e4 --- /dev/null +++ b/placement-import/config/certmanager/kustomization.yaml @@ -0,0 +1,7 @@ +resources: +- issuer.yaml +- certificate-webhook.yaml +- certificate-metrics.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/placement-import/config/certmanager/kustomizeconfig.yaml b/placement-import/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 000000000..cf6f89e88 --- /dev/null +++ b/placement-import/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,8 @@ +# This configuration is for teaching kustomize how to update name ref substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name diff --git a/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml b/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml new file mode 100644 index 000000000..73ef0098b --- /dev/null +++ b/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml @@ -0,0 +1,502 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: placementapis.placement.openstack.org +spec: + group: placement.openstack.org + names: + kind: PlacementAPI + listKind: PlacementAPIList + plural: placementapis + singular: placementapi + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: NetworkAttachments + jsonPath: .spec.networkAttachments + name: NetworkAttachments + type: string + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: PlacementAPI is the Schema for the placementapis API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PlacementAPISpec defines the desired state of PlacementAPI + properties: + apiTimeout: + default: 60 + description: APITimeout for HAProxy, Apache + minimum: 10 + type: integer + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object + containerImage: + description: PlacementAPI Container Image URL (will be set to environmental + default if empty) + type: string + customServiceConfig: + description: |- + CustomServiceConfig - customize the service config using this parameter to change service defaults, + or overwrite rendered information using raw OpenStack config format. The content gets added to + to /etc//.conf.d directory as custom.conf file. + type: string + databaseAccount: + default: placement + description: DatabaseAccount - name of MariaDBAccount which will be + used to connect. + type: string + databaseInstance: + description: |- + MariaDB instance name + Right now required by the maridb-operator to get the credentials from the instance to create the DB + Might not be required in future + type: string + defaultConfigOverwrite: + additionalProperties: + type: string + description: DefaultConfigOverwrite - interface to overwrite default + config files like policy.yaml. + type: object + networkAttachments: + description: NetworkAttachments is a list of NetworkAttachment resource + names to expose the services to the given network + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + description: NodeSelector to target subset of worker nodes running + this service + type: object + override: + description: Override, provides the ability to override the generated + manifest of several child resources. + properties: + service: + additionalProperties: + description: |- + RoutedOverrideSpec - a routed service override configuration for the Service created to serve traffic + to the cluster. Allows for the manifest of the created Service to be overwritten with custom configuration. + properties: + endpointURL: + type: string + metadata: + description: |- + EmbeddedLabelsAnnotations is an embedded subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. + Only labels and annotations are included. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and services. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + type: object + type: object + spec: + description: |- + OverrideServiceSpec is a subset of the fields included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec + Limited to Type, SessionAffinity, LoadBalancerSourceRanges, ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, + IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy + properties: + externalName: + description: |- + externalName is the external reference that discovery mechanisms will + return as an alias for this service (e.g. a DNS CNAME record). No + proxying will be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + type: string + externalTrafficPolicy: + description: |- + externalTrafficPolicy describes how nodes distribute service traffic they + receive on one of the Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure + the service in a way that assumes that external load balancers will take care + of balancing the service traffic between nodes, and so each node will deliver + traffic only to the node-local endpoints of the service, without masquerading + the client source IP. (Traffic mistakenly sent to a node with no endpoints will + be dropped.) The default value, "Cluster", uses the standard behavior of + routing to all endpoints evenly (possibly modified by topology and other + features). Note that traffic sent to an External IP or LoadBalancer IP from + within the cluster will always get "Cluster" semantics, but clients sending to + a NodePort from within the cluster may need to take traffic policy into account + when picking a node. + type: string + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilyPolicy: + description: |- + IPFamilyPolicy represents the dual-stack-ness requested or required by + this Service. If there is no value provided, then this field will be set + to SingleStack. Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured clusters or + a single IP family on single-stack clusters), or "RequireDualStack" + (two IP families on dual-stack configured clusters, otherwise fail). The + ipFamilies and clusterIPs fields depend on the value of this field. This + field will be wiped when updating a service to type ExternalName. + type: string + loadBalancerClass: + description: |- + loadBalancerClass is the class of the load balancer implementation this Service belongs to. + If specified, the value of this field must be a label-style identifier, with an optional prefix, + e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + balancer implementation is used, today this is typically done through the cloud provider integration, + but should apply for any default implementation. If set, it is assumed that a load balancer + implementation is watching for Services with a matching class. Any default load balancer + implementation (e.g. cloud providers) should ignore Services that set this field. + This field can only be set when creating or updating a Service to type 'LoadBalancer'. + Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + type: string + loadBalancerSourceRanges: + description: |- + If specified and supported by the platform, this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client IPs. This field will be ignored if the + cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + items: + type: string + type: array + x-kubernetes-list-type: atomic + sessionAffinity: + description: |- + Supports "ClientIP" and "None". Used to maintain session affinity. + Enable client IP based session affinity. + Must be ClientIP or None. + Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + type: + description: |- + type determines how the Service is exposed. Defaults to ClusterIP. Valid + options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that is not + specified, by manual construction of an Endpoints object or + EndpointSlice objects. If clusterIP is "None", no virtual IP is + allocated and the endpoints are published as a set of endpoints rather + than a virtual IP. + "NodePort" builds on ClusterIP and allocates a port on every node which + routes to the same endpoints as the clusterIP. + "LoadBalancer" builds on NodePort and creates an external load-balancer + (if supported in the current cloud) which routes to the same endpoints + as the clusterIP. + "ExternalName" aliases this service to the specified externalName. + Several other fields do not apply to ExternalName services. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: string + type: object + type: object + description: |- + Override configuration for the Service created to serve traffic to the cluster. + The key must be the endpoint type (public, internal) + type: object + type: object + passwordSelectors: + default: + service: PlacementPassword + description: PasswordSelectors - Selectors to identify the DB and + ServiceUser password from the Secret + properties: + service: + default: PlacementPassword + description: Service - Selector to get the service user password + from the Secret + type: string + type: object + preserveJobs: + default: false + description: PreserveJobs - do not delete jobs after they finished + e.g. to check logs + type: boolean + replicas: + default: 1 + description: Replicas of placement API to run + format: int32 + maximum: 32 + minimum: 0 + type: integer + resources: + description: |- + Resources - Compute Resources required by this service (Limits/Requests). + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + secret: + description: Secret containing OpenStack password information for + placement PlacementPassword + type: string + serviceUser: + default: placement + description: ServiceUser - optional username used for this service + to register in keystone + type: string + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret for + the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + public: + description: Public GenericService - holds the secret for + the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object + topologyRef: + description: |- + TopologyRef to apply the Topology defined by the associated CR referenced + by name + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + required: + - containerImage + - databaseInstance + - secret + type: object + status: + description: PlacementAPIStatus defines the observed state of PlacementAPI + properties: + conditions: + description: Conditions + items: + description: Condition defines an observation of a API resource + operational state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. + type: string + severity: + description: |- + Severity provides a classification of Reason code, so the current situation is immediately + understandable and could act accordingly. + It is meant for situations where Status=False and it should be indicated if it is just + informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue + and no actions to automatically resolve the issue can/should be done). + For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + databaseHostname: + description: Placement Database Hostname + type: string + hash: + additionalProperties: + type: string + description: Map of hashes to track e.g. job status + type: object + lastAppliedTopology: + description: LastAppliedTopology - the last applied Topology + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + networkAttachments: + additionalProperties: + items: + type: string + type: array + description: NetworkAttachments status of the deployment pods + type: object + observedGeneration: + description: ObservedGeneration - the most recent generation observed + for this service. If the observed generation is less than the spec + generation, then the controller has not processed the latest changes. + format: int64 + type: integer + readyCount: + description: ReadyCount of placement API instances + format: int32 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/placement-import/config/crd/kustomization.yaml b/placement-import/config/crd/kustomization.yaml new file mode 100644 index 000000000..3ac62fa76 --- /dev/null +++ b/placement-import/config/crd/kustomization.yaml @@ -0,0 +1,16 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/placement.openstack.org_placementapis.yaml +# +kubebuilder:scaffold:crdkustomizeresource + +patches: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +# +kubebuilder:scaffold:crdkustomizewebhookpatch + +# [WEBHOOK] To enable webhook, uncomment the following section +# the following config is for teaching kustomize how to do kustomization for CRDs. +#configurations: +#- kustomizeconfig.yaml diff --git a/placement-import/config/crd/kustomizeconfig.yaml b/placement-import/config/crd/kustomizeconfig.yaml new file mode 100644 index 000000000..ec5c150a9 --- /dev/null +++ b/placement-import/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/placement-import/config/default/cert_metrics_manager_patch.yaml b/placement-import/config/default/cert_metrics_manager_patch.yaml new file mode 100644 index 000000000..d97501553 --- /dev/null +++ b/placement-import/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/placement-import/config/default/kustomization.yaml b/placement-import/config/default/kustomization.yaml new file mode 100644 index 000000000..8efb44af9 --- /dev/null +++ b/placement-import/config/default/kustomization.yaml @@ -0,0 +1,236 @@ +# Adds namespace to all resources. +namespace: placement-operator-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: placement-operator- + +# 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: +# Injects our custom images (ENV variable settings) +- path: manager_default_images.yaml +# [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/placement-import/config/default/manager_default_images.yaml b/placement-import/config/default/manager_default_images.yaml new file mode 100644 index 000000000..117d091b4 --- /dev/null +++ b/placement-import/config/default/manager_default_images.yaml @@ -0,0 +1,15 @@ +# This patch inject custom ENV settings to the manager container +# Used to set our default image locations +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + env: + - name: RELATED_IMAGE_PLACEMENT_API_IMAGE_URL_DEFAULT + value: quay.io/podified-antelope-centos9/openstack-placement-api:current-podified diff --git a/placement-import/config/default/manager_metrics_patch.yaml b/placement-import/config/default/manager_metrics_patch.yaml new file mode 100644 index 000000000..2aaef6536 --- /dev/null +++ b/placement-import/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/placement-import/config/default/manager_webhook_patch.yaml b/placement-import/config/default/manager_webhook_patch.yaml new file mode 100644 index 000000000..963c8a4cc --- /dev/null +++ b/placement-import/config/default/manager_webhook_patch.yaml @@ -0,0 +1,31 @@ +# This patch ensures the webhook certificates are properly mounted in the manager container. +# It configures the necessary arguments, volumes, volume mounts, and container ports. + +# Add the --webhook-cert-path argument for configuring the webhook certificate path +- op: add + path: /spec/template/spec/containers/0/args/- + value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs + +# Add the volumeMount for the webhook certificates +- op: add + path: /spec/template/spec/containers/0/volumeMounts/- + value: + mountPath: /tmp/k8s-webhook-server/serving-certs + name: webhook-certs + readOnly: true + +# Add the port configuration for the webhook server +- op: add + path: /spec/template/spec/containers/0/ports/- + value: + containerPort: 9443 + name: webhook-server + protocol: TCP + +# Add the volume configuration for the webhook certificates +- op: add + path: /spec/template/spec/volumes/- + value: + name: webhook-certs + secret: + secretName: webhook-server-cert diff --git a/placement-import/config/default/metrics_service.yaml b/placement-import/config/default/metrics_service.yaml new file mode 100644 index 000000000..a24e557ae --- /dev/null +++ b/placement-import/config/default/metrics_service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + 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: placement-operator diff --git a/placement-import/config/manager/kustomization.yaml b/placement-import/config/manager/kustomization.yaml new file mode 100644 index 000000000..87eac10b7 --- /dev/null +++ b/placement-import/config/manager/kustomization.yaml @@ -0,0 +1,8 @@ +resources: +- manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: quay.io/openstack-k8s-operators/placement-operator + newTag: latest diff --git a/placement-import/config/manager/manager.yaml b/placement-import/config/manager/manager.yaml new file mode 100644 index 000000000..078bc7d35 --- /dev/null +++ b/placement-import/config/manager/manager.yaml @@ -0,0 +1,98 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + 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: placement-operator + app.kubernetes.io/managed-by: kustomize +spec: + selector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + replicas: 1 + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + 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/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml b/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml new file mode 100644 index 000000000..2ae6b216c --- /dev/null +++ b/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml @@ -0,0 +1,63 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: '[]' + capabilities: Basic Install + features.operators.openshift.io/disconnected: "true" + features.operators.openshift.io/fips-compliant: "true" + features.operators.openshift.io/proxy-aware: "false" + features.operators.openshift.io/tls-profiles: "false" + features.operators.openshift.io/token-auth-aws: "false" + features.operators.openshift.io/token-auth-azure: "false" + features.operators.openshift.io/token-auth-gcp: "false" + operatorframework.io/suggested-namespace: openstack + operators.operatorframework.io/operator-type: non-standalone + name: placement-operator.v0.0.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: PlacementAPI is the Schema for the placementapis API + displayName: Placement API + kind: PlacementAPI + name: placementapis.placement.openstack.org + specDescriptors: + - description: Auth - Parameters related to authentication + displayName: Auth + path: auth + - description: ApplicationCredentialSecret - Secret containing Application Credential + ID and Secret + displayName: Application Credential Secret + path: auth.applicationCredentialSecret + - description: TLS - Parameters related to the TLS + displayName: TLS + path: tls + version: v1beta1 + description: Placement Operator + displayName: Placement Operator + install: + spec: + deployments: null + strategy: "" + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: true + type: AllNamespaces + keywords: + - OpenStack + - Placement + links: + - name: Placement Operator + url: https://github.com/openstack-k8s-operators/placement-operator + maturity: beta + provider: + name: Red Hat Inc. + url: https://redhat.com/ + version: 0.0.0 diff --git a/placement-import/config/manifests/kustomization.yaml b/placement-import/config/manifests/kustomization.yaml new file mode 100644 index 000000000..fbce11adc --- /dev/null +++ b/placement-import/config/manifests/kustomization.yaml @@ -0,0 +1,28 @@ +# These resources constitute the fully configured set of manifests +# used to generate the 'manifests/' directory in a bundle. +resources: +- bases/placement-operator.clusterserviceversion.yaml +- ../default +- ../samples +- ../scorecard + +# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. +# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. +# These patches remove the unnecessary "cert" volume and its manager container volumeMount. +#patches: +#- target: +# group: apps +# version: v1 +# kind: Deployment +# name: controller-manager +# namespace: system +# patch: |- +# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. +# - op: remove + +# path: /spec/template/spec/containers/0/volumeMounts/0 +# # Remove the "cert" volume, since OLM will create and mount a set of certs. +# # Update the indices in this path if adding or removing volumes in the manager's Deployment. +# - op: remove +# path: /spec/template/spec/volumes/0 diff --git a/placement-import/config/network-policy/allow-metrics-traffic.yaml b/placement-import/config/network-policy/allow-metrics-traffic.yaml new file mode 100644 index 000000000..ff444541c --- /dev/null +++ b/placement-import/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: placement-operator + app.kubernetes.io/managed-by: kustomize + name: allow-metrics-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + 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/placement-import/config/network-policy/allow-webhook-traffic.yaml b/placement-import/config/network-policy/allow-webhook-traffic.yaml new file mode 100644 index 000000000..9907bc041 --- /dev/null +++ b/placement-import/config/network-policy/allow-webhook-traffic.yaml @@ -0,0 +1,27 @@ +# This NetworkPolicy allows ingress traffic to your webhook server running +# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks +# will only work when applied in namespaces labeled with 'webhook: enabled' +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: allow-webhook-traffic + namespace: system +spec: + podSelector: + matchLabels: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator + policyTypes: + - Ingress + ingress: + # This allows ingress traffic from any namespace with the label webhook: enabled + - from: + - namespaceSelector: + matchLabels: + webhook: enabled # Only from namespaces with this label + ports: + - port: 443 + protocol: TCP diff --git a/placement-import/config/network-policy/kustomization.yaml b/placement-import/config/network-policy/kustomization.yaml new file mode 100644 index 000000000..0872bee12 --- /dev/null +++ b/placement-import/config/network-policy/kustomization.yaml @@ -0,0 +1,3 @@ +resources: +- allow-webhook-traffic.yaml +- allow-metrics-traffic.yaml diff --git a/placement-import/config/prometheus/kustomization.yaml b/placement-import/config/prometheus/kustomization.yaml new file mode 100644 index 000000000..fdc5481b1 --- /dev/null +++ b/placement-import/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/placement-import/config/prometheus/monitor.yaml b/placement-import/config/prometheus/monitor.yaml new file mode 100644 index 000000000..cadf258af --- /dev/null +++ b/placement-import/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: placement-operator + 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: placement-operator diff --git a/placement-import/config/prometheus/monitor_tls_patch.yaml b/placement-import/config/prometheus/monitor_tls_patch.yaml new file mode 100644 index 000000000..5bf84ce0d --- /dev/null +++ b/placement-import/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/placement-import/config/rbac/kustomization.yaml b/placement-import/config/rbac/kustomization.yaml new file mode 100644 index 000000000..8e56bfcfa --- /dev/null +++ b/placement-import/config/rbac/kustomization.yaml @@ -0,0 +1,27 @@ +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 +# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by +# default, aiding admins in cluster management. Those roles are +# not used by the placement-operator itself. You can comment the following lines +# if you do not want those helpers be installed with your Project. +- placementapi_admin_role.yaml +- placementapi_editor_role.yaml +- placementapi_viewer_role.yaml diff --git a/placement-import/config/rbac/leader_election_role.yaml b/placement-import/config/rbac/leader_election_role.yaml new file mode 100644 index 000000000..0827f7bf4 --- /dev/null +++ b/placement-import/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: placement-operator + 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/placement-import/config/rbac/leader_election_role_binding.yaml b/placement-import/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 000000000..309ecad8a --- /dev/null +++ b/placement-import/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: placement-operator + 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/placement-import/config/rbac/metrics_auth_role.yaml b/placement-import/config/rbac/metrics_auth_role.yaml new file mode 100644 index 000000000..32d2e4ec6 --- /dev/null +++ b/placement-import/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/placement-import/config/rbac/metrics_auth_role_binding.yaml b/placement-import/config/rbac/metrics_auth_role_binding.yaml new file mode 100644 index 000000000..e775d67ff --- /dev/null +++ b/placement-import/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/placement-import/config/rbac/metrics_reader_role.yaml b/placement-import/config/rbac/metrics_reader_role.yaml new file mode 100644 index 000000000..51a75db47 --- /dev/null +++ b/placement-import/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/placement-import/config/rbac/placementapi_admin_role.yaml b/placement-import/config/rbac/placementapi_admin_role.yaml new file mode 100644 index 000000000..773fa5881 --- /dev/null +++ b/placement-import/config/rbac/placementapi_admin_role.yaml @@ -0,0 +1,27 @@ +# This rule is not used by the project placement-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants full permissions ('*') over placement.openstack.org. +# This role is intended for users authorized to modify roles and bindings within the cluster, +# enabling them to delegate specific permissions to other users or groups as needed. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: placementapi-admin-role +rules: +- apiGroups: + - placement.openstack.org + resources: + - placementapis + verbs: + - '*' +- apiGroups: + - placement.openstack.org + resources: + - placementapis/status + verbs: + - get diff --git a/placement-import/config/rbac/placementapi_editor_role.yaml b/placement-import/config/rbac/placementapi_editor_role.yaml new file mode 100644 index 000000000..55eb6a17d --- /dev/null +++ b/placement-import/config/rbac/placementapi_editor_role.yaml @@ -0,0 +1,33 @@ +# This rule is not used by the project placement-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants permissions to create, update, and delete resources within the placement.openstack.org. +# This role is intended for users who need to manage these resources +# but should not control RBAC or manage permissions for others. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: placementapi-editor-role +rules: +- apiGroups: + - placement.openstack.org + resources: + - placementapis + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - placement.openstack.org + resources: + - placementapis/status + verbs: + - get diff --git a/placement-import/config/rbac/placementapi_viewer_role.yaml b/placement-import/config/rbac/placementapi_viewer_role.yaml new file mode 100644 index 000000000..857792af3 --- /dev/null +++ b/placement-import/config/rbac/placementapi_viewer_role.yaml @@ -0,0 +1,29 @@ +# This rule is not used by the project placement-operator itself. +# It is provided to allow the cluster admin to help manage permissions for users. +# +# Grants read-only access to placement.openstack.org resources. +# This role is intended for users who need visibility into these resources +# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: placementapi-viewer-role +rules: +- apiGroups: + - placement.openstack.org + resources: + - placementapis + verbs: + - get + - list + - watch +- apiGroups: + - placement.openstack.org + resources: + - placementapis/status + verbs: + - get diff --git a/placement-import/config/rbac/role.yaml b/placement-import/config/rbac/role.yaml new file mode 100644 index 000000000..869d5bace --- /dev/null +++ b/placement-import/config/rbac/role.yaml @@ -0,0 +1,162 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - services + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - get + - list + - patch + - update + - watch +- apiGroups: + - apps + resources: + - deployments + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - k8s.cni.cncf.io + resources: + - network-attachment-definitions + verbs: + - get + - list + - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneapis + verbs: + - get + - list + - watch +- apiGroups: + - keystone.openstack.org + resources: + - keystoneendpoints + - keystoneservices + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mariadb.openstack.org + resources: + - mariadbaccounts + - mariadbdatabases + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mariadb.openstack.org + resources: + - mariadbaccounts/finalizers + - mariadbdatabases/finalizers + verbs: + - patch + - update +- apiGroups: + - placement.openstack.org + resources: + - placementapis + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - placement.openstack.org + resources: + - placementapis/finalizers + verbs: + - patch + - update +- apiGroups: + - placement.openstack.org + resources: + - placementapis/status + verbs: + - get + - patch + - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + - roles + verbs: + - create + - get + - list + - patch + - update + - watch +- apiGroups: + - security.openshift.io + resourceNames: + - anyuid + resources: + - securitycontextconstraints + verbs: + - use +- apiGroups: + - topology.openstack.org + resources: + - topologies + verbs: + - get + - list + - update + - watch diff --git a/placement-import/config/rbac/role_binding.yaml b/placement-import/config/rbac/role_binding.yaml new file mode 100644 index 000000000..b776ff31d --- /dev/null +++ b/placement-import/config/rbac/role_binding.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/name: placement-operator + 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/placement-import/config/rbac/service_account.yaml b/placement-import/config/rbac/service_account.yaml new file mode 100644 index 000000000..f1ed5daec --- /dev/null +++ b/placement-import/config/rbac/service_account.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: controller-manager + namespace: system diff --git a/placement-import/config/samples/kustomization.yaml b/placement-import/config/samples/kustomization.yaml new file mode 100644 index 000000000..ac2e8f1e2 --- /dev/null +++ b/placement-import/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples you want in your CSV to this file as resources ## +resources: +- placement_v1beta1_placementapi.yaml +#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/placement-import/config/samples/placement_v1beta1_placementapi.yaml b/placement-import/config/samples/placement_v1beta1_placementapi.yaml new file mode 100644 index 000000000..2740db960 --- /dev/null +++ b/placement-import/config/samples/placement_v1beta1_placementapi.yaml @@ -0,0 +1,18 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + serviceUser: placement + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + preserveJobs: false + replicas: 1 + secret: placement-secret + #resources: + # requests: + # memory: "500Mi" + # cpu: "1.0" diff --git a/placement-import/config/samples/placement_v1beta1_placementtls.yaml b/placement-import/config/samples/placement_v1beta1_placementtls.yaml new file mode 100644 index 000000000..76c8c0a90 --- /dev/null +++ b/placement-import/config/samples/placement_v1beta1_placementtls.yaml @@ -0,0 +1,21 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + serviceUser: placement + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + preserveJobs: false + replicas: 1 + secret: placement-secret + tls: + api: + internal: + secretName: cert-internal-svc + public: + secretName: cert-public-svc + caBundleSecretName: combined-ca-bundle diff --git a/placement-import/config/scorecard/bases/config.yaml b/placement-import/config/scorecard/bases/config.yaml new file mode 100644 index 000000000..c77047841 --- /dev/null +++ b/placement-import/config/scorecard/bases/config.yaml @@ -0,0 +1,7 @@ +apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: +- parallel: true + tests: [] diff --git a/placement-import/config/scorecard/kustomization.yaml b/placement-import/config/scorecard/kustomization.yaml new file mode 100644 index 000000000..54e8aa507 --- /dev/null +++ b/placement-import/config/scorecard/kustomization.yaml @@ -0,0 +1,18 @@ +resources: +- bases/config.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +patches: +- path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + kind: Configuration + name: config + version: v1alpha3 +- path: patches/olm.config.yaml + target: + group: scorecard.operatorframework.io + kind: Configuration + name: config + version: v1alpha3 +# +kubebuilder:scaffold:patches diff --git a/placement-import/config/scorecard/patches/basic.config.yaml b/placement-import/config/scorecard/patches/basic.config.yaml new file mode 100644 index 000000000..8237b70d8 --- /dev/null +++ b/placement-import/config/scorecard/patches/basic.config.yaml @@ -0,0 +1,10 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: basic + test: basic-check-spec-test diff --git a/placement-import/config/scorecard/patches/olm.config.yaml b/placement-import/config/scorecard/patches/olm.config.yaml new file mode 100644 index 000000000..416660a77 --- /dev/null +++ b/placement-import/config/scorecard/patches/olm.config.yaml @@ -0,0 +1,50 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: olm + test: olm-bundle-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: olm + test: olm-crds-have-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: olm + test: olm-crds-have-resources-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: olm + test: olm-spec-descriptors-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.41.1 + labels: + suite: olm + test: olm-status-descriptors-test diff --git a/placement-import/config/webhook/kustomization.yaml b/placement-import/config/webhook/kustomization.yaml new file mode 100644 index 000000000..9cf26134e --- /dev/null +++ b/placement-import/config/webhook/kustomization.yaml @@ -0,0 +1,6 @@ +resources: +- manifests.yaml +- service.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/placement-import/config/webhook/kustomizeconfig.yaml b/placement-import/config/webhook/kustomizeconfig.yaml new file mode 100644 index 000000000..206316e54 --- /dev/null +++ b/placement-import/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,22 @@ +# the following config is for teaching kustomize where to look at when substituting nameReference. +# It requires kustomize v2.1.0 or newer to work properly. +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true diff --git a/placement-import/config/webhook/manifests.yaml b/placement-import/config/webhook/manifests.yaml new file mode 100644 index 000000000..c6983dcd5 --- /dev/null +++ b/placement-import/config/webhook/manifests.yaml @@ -0,0 +1,52 @@ +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-placement-openstack-org-v1beta1-placementapi + failurePolicy: Fail + name: mplacementapi-v1beta1.kb.io + rules: + - apiGroups: + - placement.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - placementapis + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-placement-openstack-org-v1beta1-placementapi + failurePolicy: Fail + name: vplacementapi-v1beta1.kb.io + rules: + - apiGroups: + - placement.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - placementapis + sideEffects: None diff --git a/placement-import/config/webhook/service.yaml b/placement-import/config/webhook/service.yaml new file mode 100644 index 000000000..95742c00a --- /dev/null +++ b/placement-import/config/webhook/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: placement-operator + app.kubernetes.io/managed-by: kustomize + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager + app.kubernetes.io/name: placement-operator diff --git a/placement-import/go.mod b/placement-import/go.mod new file mode 100644 index 000000000..703132d18 --- /dev/null +++ b/placement-import/go.mod @@ -0,0 +1,144 @@ +module github.com/openstack-k8s-operators/placement-operator + +go 1.24.4 + +require ( + github.com/go-logr/logr v1.4.3 + github.com/google/uuid v1.6.0 + github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 + github.com/onsi/ginkgo/v2 v2.28.1 + github.com/onsi/gomega v1.39.1 + github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af + github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581 + github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 + github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18 + github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727 + github.com/openstack-k8s-operators/placement-operator/api v0.3.1-0.20240216174613-3d349f26e681 + k8s.io/api v0.31.14 + k8s.io/apimachinery v0.31.14 + k8s.io/client-go v0.31.14 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.19.7 +) + +require ( + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // 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.1 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/cel-go v0.20.1 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect + github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/imdario/mergo v0.3.16 // 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.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/openshift/api v3.9.0+incompatible // indirect + github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e // indirect + github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect + github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/pflag v1.0.7 // indirect + github.com/stoewer/go-strcase v1.2.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.58.0 // indirect + go.opentelemetry.io/otel v1.34.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.34.0 // indirect + go.opentelemetry.io/otel/sdk v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.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.1 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/term v0.39.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/time v0.12.0 // indirect + golang.org/x/tools v0.41.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/grpc v1.71.1 // indirect + google.golang.org/protobuf v1.36.7 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.33.2 // indirect + k8s.io/apiserver v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // 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.6.0 // indirect +) + +replace github.com/openstack-k8s-operators/placement-operator/api => ./api + +// mschuppert: map to latest commit from release-4.18 tag +// must consistent within modules and service operators +replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e //allow-merging + +// pin these to avoid later versions pulled by rabbitmq +replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging + +replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging + +replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging + +replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging + +replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging + +replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.14 //allow-merging + +replace k8s.io/code-generator => k8s.io/code-generator v0.31.14 //allow-merging + +replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging + +// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) +replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging + +replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging diff --git a/placement-import/go.sum b/placement-import/go.sum new file mode 100644 index 000000000..54026ee63 --- /dev/null +++ b/placement-import/go.sum @@ -0,0 +1,305 @@ +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +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-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +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.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +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.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= +github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo= +github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= +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/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +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/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc= +github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= +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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= +github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= +github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af h1:Ow12j/PVbEtul1bZ7s/ZenVnKPIHK2q+0VgTp+j/wro= +github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af/go.mod h1:nC/Jf3OYJRML8UEzJ/mn/TQcSCv/nhqO6x6LGkdDt60= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581 h1:FntUoWPVWx8xaGCwP7kdUVT/N8Z0kfIxLLK4L+oQJ7c= +github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581/go.mod h1:l15wx+Qxi/I9Nlj6u6PheZqkf9dBW7cCxAcjl8zsu+8= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 h1:P1Nx+i6aH7rFy+T4nrB2AlQzzoLMrMwtnZPEsFvC5cM= +github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e h1:42OT26Ak0lwWbJDNwhv/0HsjafVkLyPhfonS5DjDb2g= +github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e/go.mod h1:7yqbVpg0k0vW+kZks+TMU/cd1ovoejyHfVPWcyGYLHI= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e h1:lHsE9kmPzGHfO6o9vXj6f5UTIlQRJNdrvrj8GWy/ct8= +github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18 h1:beEKU5yv6ih8pbIqrU15QP0sGDXyxrKqVMh3zHDscrI= +github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:dEjz8zHRIlP3vnMmWdHytlLeSZ6BHcIiSTPM7xTQxFg= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727 h1:+IVZR4jm1MvVeoxS75cHNOsW7sf6jGdXx7kTnOSOxiw= +github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727/go.mod h1:bUpTGnR9HF19rD2Ow6IU8wJk+xrBtKdeA8PFjpguEh8= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= +github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= +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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/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.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +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/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.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.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +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.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +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.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= +golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +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/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +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-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +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.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= +k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= +k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= +k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= +k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= +k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.14 h1:DORopWIu2qg7gmVyA9UUGGGmO1Rmnq5Oe+GmsKen3yo= +k8s.io/apiserver v0.31.14/go.mod h1:q81QJuh85u/HN74pdw5Ci4EnrRmCOonZj9FvLwf8DWc= +k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= +k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= +k8s.io/component-base v0.31.14 h1:VNjBuEMmvlwL4twRlMmlaVmsodIRaNivXcZoAx1/x7Q= +k8s.io/component-base v0.31.14/go.mod h1:9ogYcJBUdB4VQ/OMgInYVRScC9bguXxSEEZPsInY+uM= +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-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= +k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= +sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= +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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/placement-import/hack/boilerplate.go.txt b/placement-import/hack/boilerplate.go.txt new file mode 100644 index 000000000..b7950f8af --- /dev/null +++ b/placement-import/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2022. + +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. +*/ diff --git a/placement-import/hack/build-crd-schema-checker.sh b/placement-import/hack/build-crd-schema-checker.sh new file mode 100755 index 000000000..12bf3be9a --- /dev/null +++ b/placement-import/hack/build-crd-schema-checker.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -euxo pipefail + +if [ -f "$INSTALL_DIR/crd-schema-checker" ]; then + exit 0 +fi + +mkdir -p "$INSTALL_DIR/git-tmp" +git clone https://github.com/openshift/crd-schema-checker.git \ + -b "$CRD_SCHEMA_CHECKER_VERSION" "$INSTALL_DIR/git-tmp" +pushd "$INSTALL_DIR/git-tmp" +GOWORK=off make +cp crd-schema-checker "$INSTALL_DIR/" +popd +rm -rf "$INSTALL_DIR/git-tmp" diff --git a/placement-import/hack/clean_local_webhook.sh b/placement-import/hack/clean_local_webhook.sh new file mode 100755 index 000000000..35c465091 --- /dev/null +++ b/placement-import/hack/clean_local_webhook.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -ex + +oc delete validatingwebhookconfiguration/vplacementapi.kb.io --ignore-not-found +oc delete mutatingwebhookconfiguration/mplacementapi.kb.io --ignore-not-found diff --git a/placement-import/hack/crd-schema-checker.sh b/placement-import/hack/crd-schema-checker.sh new file mode 100755 index 000000000..3a252acd6 --- /dev/null +++ b/placement-import/hack/crd-schema-checker.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euxo pipefail + +CHECKER=$INSTALL_DIR/crd-schema-checker + +TMP_DIR=$(mktemp -d) + +function cleanup { + rm -rf "$TMP_DIR" +} + +trap cleanup EXIT + + +for crd in config/crd/bases/*.yaml; do + mkdir -p "$(dirname "$TMP_DIR/$crd")" + if git show "$BASE_REF:$crd" > "$TMP_DIR/$crd"; then + $CHECKER check-manifests \ + --existing-crd-filename="$TMP_DIR/$crd" \ + --new-crd-filename="$crd" + fi +done diff --git a/placement-import/hack/run_with_local_webhook.sh b/placement-import/hack/run_with_local_webhook.sh new file mode 100755 index 000000000..48c31dae0 --- /dev/null +++ b/placement-import/hack/run_with_local_webhook.sh @@ -0,0 +1,139 @@ +#!/bin/bash +set -ex + +# Define a cleanup function +cleanup() { + echo "Caught signal, cleaning up local webhooks..." + ./hack/clean_local_webhook.sh + exit 0 +} + +# Set trap to catch SIGINT and SIGTERM +trap cleanup SIGINT SIGTERM + +TMPDIR=${TMPDIR:-"/tmp/k8s-webhook-server/serving-certs"} +SKIP_CERT=${SKIP_CERT:-false} +CRC_IP=${CRC_IP:-$(/sbin/ip -o -4 addr list crc | awk '{print $4}' | cut -d/ -f1)} +FIREWALL_ZONE=${FIREWALL_ZONE:-"libvirt"} +WEBHOOK_PORT=${WEBHOOK_PORT:-${WEBHOOK_PORT}} +SKIP_FIREWALL=${SKIP_FIREWALL:-false} + +if [ "$SKIP_FIREWALL" = false ] ; then + #Open ${WEBHOOK_PORT} + sudo firewall-cmd --zone=${FIREWALL_ZONE} --add-port=${WEBHOOK_PORT}/tcp + sudo firewall-cmd --runtime-to-permanent +fi + +# Generate the certs and the ca bundle +if [ "$SKIP_CERT" = false ] ; then + mkdir -p ${TMPDIR} + rm -rf ${TMPDIR}/* || true + + openssl req -newkey rsa:2048 -days 3650 -nodes -x509 \ + -subj "/CN=${HOSTNAME}" \ + -addext "subjectAltName = IP:${CRC_IP}" \ + -keyout ${TMPDIR}/tls.key \ + -out ${TMPDIR}/tls.crt + + cat ${TMPDIR}/tls.crt ${TMPDIR}/tls.key | base64 -w 0 > ${TMPDIR}/bundle.pem + +fi + +CA_BUNDLE=`cat ${TMPDIR}/bundle.pem` + +# Patch the webhook(s) +cat >> ${TMPDIR}/patch_webhook_configurations.yaml < "${CSV_FILE}" + + printf \ + "\n\tNow patching operator CSV to remove its OLM deployment and associated webhooks. + The original OLM version of the operator's CSV has been copied to %s. To restore it, use: + oc patch -n openstack-operators %s --type=merge --patch-file=%s\n\n" "${CSV_FILE}" "${CSV_NAME}" "${CSV_FILE}" + fi + + oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" + oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/webhookdefinitions', 'value': []}]" +else + # Handle operator deployed by Openstack Initialization resource + CSV_NAME="$(oc get csv -n openstack-operators -l operators.coreos.com/openstack-operator.openstack-operators -o name)" + + printf \ + "\n\tNow patching openstack operator CSV to scale down deployment resource. + To restore it, use: + oc patch "${CSV_NAME}" -n openstack-operators --type=json -p=\"[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 1}]\"" + + oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" + oc scale --replicas=0 -n openstack-operators deploy/placement-operator-controller-manager +fi + +go run ./cmd/main.go -metrics-bind-address ":${METRICS_PORT}" -health-probe-bind-address ":${HEALTH_PORT}" -pprof-bind-address ":${PPROF_PORT}" -webhook-bind-address "${WEBHOOK_PORT}" diff --git a/placement-import/internal/controller/placementapi_controller.go b/placement-import/internal/controller/placementapi_controller.go new file mode 100644 index 000000000..e4fe1990b --- /dev/null +++ b/placement-import/internal/controller/placementapi_controller.go @@ -0,0 +1,1493 @@ +/* +Copyright 2022. + +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 controller contains the Kubernetes controllers for managing Placement API components +package controller + +import ( + "context" + "errors" + "fmt" + "maps" + "time" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/utils/ptr" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/go-logr/logr" + networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + common "github.com/openstack-k8s-operators/lib-common/modules/common" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + deployment "github.com/openstack-k8s-operators/lib-common/modules/common/deployment" + endpoint "github.com/openstack-k8s-operators/lib-common/modules/common/endpoint" + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + helper "github.com/openstack-k8s-operators/lib-common/modules/common/helper" + job "github.com/openstack-k8s-operators/lib-common/modules/common/job" + labels "github.com/openstack-k8s-operators/lib-common/modules/common/labels" + nad "github.com/openstack-k8s-operators/lib-common/modules/common/networkattachment" + common_rbac "github.com/openstack-k8s-operators/lib-common/modules/common/rbac" + "github.com/openstack-k8s-operators/lib-common/modules/common/secret" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" + util "github.com/openstack-k8s-operators/lib-common/modules/common/util" + + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + placement "github.com/openstack-k8s-operators/placement-operator/internal/placement" + + appsv1 "k8s.io/api/apps/v1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" +) + +// Static errors for Application Credential handling +var ( + ErrACSecretNotFound = errors.New("ApplicationCredential secret not found") + ErrACSecretMissingKeys = errors.New("ApplicationCredential secret missing required keys") +) + +type conditionUpdater interface { + Set(c *condition.Condition) + MarkTrue(t condition.Type, messageFormat string, messageArgs ...any) +} + +// GetSecret interface defines methods for objects that can provide secret names +type GetSecret interface { + GetSecret() string + client.Object +} + +// ensureSecret - ensures that the Secret object exists and the expected fields +// are in the Secret. It returns a hash of the values of the expected fields. +func ensureSecret( + ctx context.Context, + secretName types.NamespacedName, + expectedFields []string, + reader client.Reader, + conditionUpdater conditionUpdater, +) (string, ctrl.Result, corev1.Secret, error) { + secret := &corev1.Secret{} + err := reader.Get(ctx, secretName, secret) + if err != nil { + if k8s_errors.IsNotFound(err) { + // This is currently only used to acquire the password secret, which should have been + // manually created by the user and referenced in the spec, so we treat this as a + // warning because it means that the service will not be able to start. + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + "%s", fmt.Sprintf("Input data resources missing: %s", "secret/"+secretName.Name))) + return "", + ctrl.Result{}, + *secret, + fmt.Errorf("%w: Secret %s not found", err, secretName) + } + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + + // collect the secret values the caller expects to exist + values := [][]byte{} + for _, field := range expectedFields { + val, ok := secret.Data[field] + if !ok { + err := fmt.Errorf("%w: field '%s' not found in secret/%s", util.ErrFieldNotFound, field, secretName.Name) + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + values = append(values, val) + } + + // TODO(gibi): Do we need to watch the Secret for changes? + + hash, err := util.ObjectHash(values) + if err != nil { + conditionUpdater.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return "", ctrl.Result{}, *secret, err + } + + return hash, ctrl.Result{}, *secret, nil +} + +// GetLogger returns a logger object with a prefix of "controller.name" and additional controller context fields +func (r *PlacementAPIReconciler) GetLogger(ctx context.Context) logr.Logger { + return log.FromContext(ctx).WithName("Controllers").WithName("PlacementAPI") +} + +// PlacementAPIReconciler reconciles a PlacementAPI object +type PlacementAPIReconciler struct { + client.Client + Kclient kubernetes.Interface + Scheme *runtime.Scheme +} + +// +kubebuilder:rbac:groups=placement.openstack.org,resources=placementapis,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=placement.openstack.org,resources=placementapis/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=placement.openstack.org,resources=placementapis/finalizers,verbs=update;patch +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=core,resources=services,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list; +// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=mariadb.openstack.org,resources=mariadbdatabases,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=mariadb.openstack.org,resources=mariadbdatabases/finalizers,verbs=update;patch +// +kubebuilder:rbac:groups=mariadb.openstack.org,resources=mariadbaccounts,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=mariadb.openstack.org,resources=mariadbaccounts/finalizers,verbs=update;patch +// +kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneapis,verbs=get;list;watch; +// +kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneservices,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=keystone.openstack.org,resources=keystoneendpoints,verbs=get;list;watch;create;update;patch;delete; +// +kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch + +// service account, role, rolebinding +// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=roles,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups="rbac.authorization.k8s.io",resources=rolebindings,verbs=get;list;watch;create;update;patch +// service account permissions that are needed to grant permission to the above +// +kubebuilder:rbac:groups="security.openshift.io",resourceNames=anyuid,resources=securitycontextconstraints,verbs=use +// +kubebuilder:rbac:groups="",resources=pods,verbs=create;delete;get;list;patch;update;watch +// +kubebuilder:rbac:groups=topology.openstack.org,resources=topologies,verbs=get;list;watch;update + +// Reconcile reconcile placement API requests +func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, _err error) { + Log := r.GetLogger(ctx) + + // Fetch the PlacementAPI instance + instance := &placementv1.PlacementAPI{} + err := r.Get(ctx, req.NamespacedName, instance) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Request object not found, could have been deleted after reconcile request. + // Owned objects are automatically garbage collected. + // For additional cleanup logic use finalizers. Return and don't requeue. + Log.Info("Placement instance not found, probably deleted before reconciled. Nothing to do.") + return ctrl.Result{}, nil + } + // Error reading the object - requeue the request. + Log.Error(err, "Failed to read the Placement instance.") + return ctrl.Result{}, err + } + + h, err := helper.NewHelper( + instance, + r.Client, + r.Kclient, + r.Scheme, + Log, + ) + if err != nil { + Log.Error(err, "Failed to create lib-common Helper") + return ctrl.Result{}, err + } + + // Save a copy of the condtions so that we can restore the LastTransitionTime + // when a condition's state doesn't change. + savedConditions := instance.Status.Conditions.DeepCopy() + // initialize status fields + if err = r.initStatus(instance); err != nil { + return ctrl.Result{}, err + } + instance.Status.ObservedGeneration = instance.Generation + + // Always patch the instance status when exiting this function so we can persist any changes. + defer func() { + // Don't update the status, if reconciler Panics + if r := recover(); r != nil { + Log.Info(fmt.Sprintf("panic during reconcile %v\n", r)) + panic(r) + } + // update the Ready condition based on the sub conditions + if instance.Status.Conditions.AllSubConditionIsTrue() { + instance.Status.Conditions.MarkTrue( + condition.ReadyCondition, condition.ReadyMessage) + } else { + // something is not ready so reset the Ready condition + instance.Status.Conditions.MarkUnknown( + condition.ReadyCondition, condition.InitReason, condition.ReadyInitMessage) + // and recalculate it based on the state of the rest of the conditions + instance.Status.Conditions.Set( + instance.Status.Conditions.Mirror(condition.ReadyCondition)) + } + condition.RestoreLastTransitionTimes(&instance.Status.Conditions, savedConditions) + err := h.PatchInstance(ctx, instance) + if err != nil { + _err = err + return + } + }() + + // Handle service delete + if !instance.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, instance, h) + } + + // We create a KeystoneService CR later and that will automatically get the + // Nova finalizer. So we need a finalizer on the ourselves too so that + // during Nova CR delete we can have a chance to remove the finalizer from + // the our KeystoneService so that is also deleted. + updated := controllerutil.AddFinalizer(instance, h.GetFinalizer()) + if updated { + Log.Info("Added finalizer to ourselves") + // we intentionally return immediately to force the deferred function + // to persist the Instance with the finalizer. We need to have our own + // finalizer persisted before we try to create the KeystoneService with + // our finalizer to avoid orphaning the KeystoneService. + return ctrl.Result{}, nil + } + // Service account, role, binding + rbacRules := []rbacv1.PolicyRule{ + { + APIGroups: []string{"security.openshift.io"}, + ResourceNames: []string{"anyuid"}, + Resources: []string{"securitycontextconstraints"}, + Verbs: []string{"use"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"pods"}, + Verbs: []string{"create", "get", "list", "watch", "update", "patch", "delete"}, + }, + } + rbacResult, err := common_rbac.ReconcileRbac(ctx, h, instance, rbacRules) + if err != nil { + return rbacResult, err + } else if (rbacResult != ctrl.Result{}) { + return rbacResult, nil + } + + // ConfigMap + configMapVars := make(map[string]env.Setter) + + // + // check for required OpenStack secret holding passwords for service/admin user and add hash to the vars map + // + hash, result, secret, err := ensureSecret( + ctx, + types.NamespacedName{Namespace: instance.Namespace, Name: instance.Spec.Secret}, + []string{ + instance.Spec.PasswordSelectors.Service, + }, + h.GetClient(), + &instance.Status.Conditions) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Since the service secret should have been manually created by the user and referenced in the spec, + // we treat this as a warning because it means that the service will not be able to start. + Log.Info(fmt.Sprintf("OpenStack secret %s not found", instance.Spec.Secret)) + instance.Status.Conditions.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyWaitingMessage)) + return ctrl.Result{RequeueAfter: time.Second * 10}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + condition.InputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.InputReadyErrorMessage, + err.Error())) + return result, err + } + configMapVars[instance.Spec.Secret] = env.SetValue(hash) + + // all our input checks out so report InputReady + instance.Status.Conditions.MarkTrue(condition.InputReadyCondition, condition.InputReadyMessage) + + // ensure MariaDBAccount exists. This account record may be created by + // openstack-operator or the cloud operator up front without a specific + // MariaDBDatabase configured yet. Otherwise, a MariaDBAccount CR is + // created here with a generated username as well as a secret with + // generated password. The MariaDBAccount is created without being + // yet associated with any MariaDBDatabase. + _, _, err = mariadbv1.EnsureMariaDBAccount( + ctx, h, instance.Spec.DatabaseAccount, + instance.Namespace, false, placement.DatabaseName, + ) + + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + mariadbv1.MariaDBAccountReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + mariadbv1.MariaDBAccountNotReadyMessage, + err.Error())) + + return ctrl.Result{}, err + } + instance.Status.Conditions.MarkTrue( + mariadbv1.MariaDBAccountReadyCondition, + mariadbv1.MariaDBAccountReadyMessage, + ) + + db, result, err := r.ensureDB(ctx, h, instance) + if err != nil { + return ctrl.Result{}, err + } else if (result != ctrl.Result{}) { + return result, nil + } + + err = r.generateServiceConfigMaps(ctx, h, instance, secret, &configMapVars, db) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.ServiceConfigReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.ServiceConfigReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + + // TLS input validation + // + // Validate the CA cert secret if provided + if instance.Spec.TLS.CaBundleSecretName != "" { + hash, err := tls.ValidateCACertSecret( + ctx, + h.GetClient(), + types.NamespacedName{ + Name: instance.Spec.TLS.CaBundleSecretName, + Namespace: instance.Namespace, + }, + ) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Since the CA cert secret should have been manually created by the user and provided in the spec, + // we treat this as a warning because it means that the service will not be able to start. + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputReadyWaitingMessage, instance.Spec.TLS.CaBundleSecretName)) + return ctrl.Result{}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + + if hash != "" { + configMapVars[tls.CABundleKey] = env.SetValue(hash) + } + } + + // Validate API service certs secrets + certsHash, err := instance.Spec.TLS.API.ValidateCertSecrets(ctx, h, instance.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.TLSInputReadyWaitingMessage, err.Error())) + return ctrl.Result{}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TLSInputReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TLSInputErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + configMapVars[tls.TLSHashName] = env.SetValue(certsHash) + + instance.Status.Conditions.MarkTrue(condition.TLSInputReadyCondition, condition.InputReadyMessage) + + // create hash over all the different input resources to identify if any those changed + // and a restart/recreate is required. + // + inputHash, hashChanged, err := r.createHashOfInputHashes(ctx, instance, configMapVars) + if err != nil { + return ctrl.Result{}, err + } else if hashChanged { + // Hash changed and instance status should be updated (which will be done by main defer func), + // so we need to return and reconcile again + return ctrl.Result{}, nil + } + + instance.Status.Conditions.MarkTrue(condition.ServiceConfigReadyCondition, condition.ServiceConfigReadyMessage) + + serviceAnnotations, result, err := r.ensureNetworkAttachments(ctx, h, instance) + if (err != nil || result != ctrl.Result{}) { + return result, err + } + + apiEndpoints, result, err := r.ensureServiceExposed(ctx, h, instance) + + if (err != nil || result != ctrl.Result{}) { + // We can ignore RequeueAfter as we are watching the Service resource + // but we have to return while waiting for the service to be exposed + return ctrl.Result{}, err + } + + result, err = r.ensureDbSync(ctx, instance, h, serviceAnnotations) + if (err != nil || result != ctrl.Result{}) { + return result, err + } + + result, err = r.ensureDeployment(ctx, h, instance, inputHash, serviceAnnotations) + if (err != nil || result != ctrl.Result{}) { + return result, err + } + + // Only expose the service is the deployment succeeded + if !instance.Status.Conditions.IsTrue(condition.DeploymentReadyCondition) { + Log.Info("Waiting for the Deployment to become Ready before exposing the sevice in Keystone") + return ctrl.Result{}, nil + } + err = r.ensureKeystoneServiceUser(ctx, h, instance) + if err != nil { + return ctrl.Result{}, err + } + + result, err = r.ensureKeystoneEndpoint(ctx, h, instance, apiEndpoints) + if (err != nil || result != ctrl.Result{}) { + // We can ignore RequeueAfter as we are watching the KeystoneEndpoint resource + return ctrl.Result{}, err + } + + // remove finalizers from unused MariaDBAccount records + err = mariadbv1.DeleteUnusedMariaDBAccountFinalizers(ctx, h, placement.DatabaseName, instance.Spec.DatabaseAccount, instance.Namespace) + if err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func getServiceLabels(instance *placementv1.PlacementAPI) map[string]string { + return map[string]string{ + common.AppSelector: placement.ServiceName, + common.OwnerSelector: instance.Name, + } +} + +func (r *PlacementAPIReconciler) ensureServiceExposed( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, +) (map[string]string, ctrl.Result, error) { + placementEndpoints := map[service.Endpoint]endpoint.Data{ + service.EndpointPublic: {Port: placement.PlacementPublicPort}, + service.EndpointInternal: {Port: placement.PlacementInternalPort}, + } + apiEndpoints := make(map[string]string) + + serviceLabels := getServiceLabels(instance) + for endpointType, data := range placementEndpoints { + endpointTypeStr := string(endpointType) + endpointName := placement.ServiceName + "-" + endpointTypeStr + + svcOverride := instance.Spec.Override.Service[endpointType] + if svcOverride.EmbeddedLabelsAnnotations == nil { + svcOverride.EmbeddedLabelsAnnotations = &service.EmbeddedLabelsAnnotations{} + } + + exportLabels := util.MergeStringMaps( + serviceLabels, + map[string]string{ + service.AnnotationEndpointKey: endpointTypeStr, + }, + ) + + // Create the service + svc, err := service.NewService( + service.GenericService(&service.GenericServiceDetails{ + Name: endpointName, + Namespace: instance.Namespace, + Labels: exportLabels, + Selector: serviceLabels, + Port: service.GenericServicePort{ + Name: endpointName, + Port: data.Port, + Protocol: corev1.ProtocolTCP, + }, + }), + 5, + &svcOverride.OverrideSpec, + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.CreateServiceReadyErrorMessage, + err.Error())) + + return apiEndpoints, ctrl.Result{}, err + } + + svc.AddAnnotation(map[string]string{ + service.AnnotationEndpointKey: endpointTypeStr, + }) + + // add Annotation to whether creating an ingress is required or not + if endpointType == service.EndpointPublic && svc.GetServiceType() == corev1.ServiceTypeClusterIP { + svc.AddAnnotation(map[string]string{ + service.AnnotationIngressCreateKey: "true", + }) + } else { + svc.AddAnnotation(map[string]string{ + service.AnnotationIngressCreateKey: "false", + }) + if svc.GetServiceType() == corev1.ServiceTypeLoadBalancer { + svc.AddAnnotation(map[string]string{ + service.AnnotationHostnameKey: svc.GetServiceHostname(), // add annotation to register service name in dnsmasq + }) + } + } + + ctrlResult, err := svc.CreateOrPatch(ctx, h) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.CreateServiceReadyErrorMessage, + err.Error())) + + return apiEndpoints, ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.CreateServiceReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.CreateServiceReadyRunningMessage)) + return apiEndpoints, ctrlResult, nil + } + // create service - end + + // if TLS is enabled + if instance.Spec.TLS.API.Enabled(endpointType) { + // set endpoint protocol to https + data.Protocol = ptr.To(service.ProtocolHTTPS) + } + + apiEndpoints[string(endpointType)], err = svc.GetAPIEndpoint( + svcOverride.EndpointURL, data.Protocol, data.Path) + if err != nil { + return apiEndpoints, ctrl.Result{}, err + } + } + + instance.Status.Conditions.MarkTrue(condition.CreateServiceReadyCondition, condition.CreateServiceReadyMessage) + return apiEndpoints, ctrl.Result{}, nil +} + +func (r *PlacementAPIReconciler) ensureNetworkAttachments( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, +) (map[string]string, ctrl.Result, error) { + var nadAnnotations map[string]string + var err error + + Log := r.GetLogger(ctx) + // networks to attach to + nadList := []networkv1.NetworkAttachmentDefinition{} + for _, netAtt := range instance.Spec.NetworkAttachments { + nad, err := nad.GetNADWithName(ctx, h, netAtt, instance.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + // Since the net-attach-def CR should have been manually created by the user and referenced in the spec, + // we treat this as a warning because it means that the service will not be able to start. + Log.Info(fmt.Sprintf("network-attachment-definition %s not found", netAtt)) + instance.Status.Conditions.Set(condition.FalseCondition( + condition.NetworkAttachmentsReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.NetworkAttachmentsReadyWaitingMessage, + netAtt)) + return nadAnnotations, ctrl.Result{RequeueAfter: time.Second * 10}, nil + } + instance.Status.Conditions.Set(condition.FalseCondition( + condition.NetworkAttachmentsReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.NetworkAttachmentsReadyErrorMessage, + err.Error())) + return nadAnnotations, ctrl.Result{}, err + } + + if nad != nil { + nadList = append(nadList, *nad) + } + } + + nadAnnotations, err = nad.EnsureNetworksAnnotation(nadList) + if err != nil { + return nadAnnotations, ctrl.Result{}, fmt.Errorf("failed create network annotation from %s: %w", + instance.Spec.NetworkAttachments, err) + } + return nadAnnotations, ctrl.Result{}, nil +} + +func (r *PlacementAPIReconciler) ensureKeystoneServiceUser( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, +) error { + // + // create service and user in keystone - https://docs.openstack.org/placement/latest/install/install-rdo.html#configure-user-and-endpoints + // + ksSvcSpec := keystonev1.KeystoneServiceSpec{ + ServiceType: placement.ServiceName, + ServiceName: placement.ServiceName, + ServiceDescription: "Placement Service", + Enabled: true, + ServiceUser: instance.Spec.ServiceUser, + Secret: instance.Spec.Secret, + PasswordSelector: instance.Spec.PasswordSelectors.Service, + } + serviceLabels := getServiceLabels(instance) + ksSvc := keystonev1.NewKeystoneService(ksSvcSpec, instance.Namespace, serviceLabels, time.Duration(10)*time.Second) + _, err := ksSvc.CreateOrPatch(ctx, h) + if err != nil { + return err + } + + // mirror the Status, Reason, Severity and Message of the latest keystoneservice condition + // into a local condition with the type condition.KeystoneServiceReadyCondition + c := ksSvc.GetConditions().Mirror(condition.KeystoneServiceReadyCondition) + if c != nil { + instance.Status.Conditions.Set(c) + } + + return nil +} + +func (r *PlacementAPIReconciler) ensureKeystoneEndpoint( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, + apiEndpoints map[string]string, +) (ctrl.Result, error) { + ksEndptSpec := keystonev1.KeystoneEndpointSpec{ + ServiceName: placement.ServiceName, + Endpoints: apiEndpoints, + } + ksEndpt := keystonev1.NewKeystoneEndpoint( + placement.ServiceName, + instance.Namespace, + ksEndptSpec, + getServiceLabels(instance), + time.Duration(10)*time.Second, + ) + ctrlResult, err := ksEndpt.CreateOrPatch(ctx, h) + if err != nil { + return ctrlResult, err + } + // mirror the Status, Reason, Severity and Message of the latest keystoneendpoint condition + // into a local condition with the type condition.KeystoneEndpointReadyCondition + c := ksEndpt.GetConditions().Mirror(condition.KeystoneEndpointReadyCondition) + if c != nil { + instance.Status.Conditions.Set(c) + } + + if (ctrlResult != ctrl.Result{}) { + return ctrlResult, nil + } + + return ctrlResult, nil +} + +func (r *PlacementAPIReconciler) initStatus( + instance *placementv1.PlacementAPI, +) error { + if err := r.initConditions(instance); err != nil { + return err + } + + // NOTE(gibi): initialize the rest of the status fields here + // so that the reconcile loop later can assume they are not nil. + if instance.Status.Hash == nil { + instance.Status.Hash = map[string]string{} + } + if instance.Status.NetworkAttachments == nil { + instance.Status.NetworkAttachments = map[string][]string{} + } + + return nil +} + +func (r *PlacementAPIReconciler) initConditions( + instance *placementv1.PlacementAPI, +) error { + if instance.Status.Conditions == nil { + instance.Status.Conditions = condition.Conditions{} + } + // initialize conditions used later as Status=Unknown + cl := condition.CreateList( + condition.UnknownCondition( + condition.DBReadyCondition, + condition.InitReason, + condition.DBReadyInitMessage, + ), + condition.UnknownCondition( + condition.DBSyncReadyCondition, + condition.InitReason, + condition.DBSyncReadyInitMessage, + ), + condition.UnknownCondition( + condition.CreateServiceReadyCondition, + condition.InitReason, + condition.CreateServiceReadyInitMessage, + ), + condition.UnknownCondition( + condition.InputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage, + ), + condition.UnknownCondition( + condition.ServiceConfigReadyCondition, + condition.InitReason, + condition.ServiceConfigReadyInitMessage, + ), + condition.UnknownCondition( + condition.DeploymentReadyCondition, + condition.InitReason, + condition.DeploymentReadyInitMessage, + ), + // right now we have no dedicated KeystoneServiceReadyInitMessage and KeystoneEndpointReadyInitMessage + condition.UnknownCondition( + condition.KeystoneServiceReadyCondition, + condition.InitReason, + "Service registration not started", + ), + condition.UnknownCondition( + condition.KeystoneEndpointReadyCondition, + condition.InitReason, + "KeystoneEndpoint not created", + ), + condition.UnknownCondition( + condition.NetworkAttachmentsReadyCondition, + condition.InitReason, + condition.NetworkAttachmentsReadyInitMessage, + ), + // service account, role, rolebinding conditions + condition.UnknownCondition( + condition.ServiceAccountReadyCondition, + condition.InitReason, + condition.ServiceAccountReadyInitMessage, + ), + condition.UnknownCondition( + condition.RoleReadyCondition, + condition.InitReason, + condition.RoleReadyInitMessage, + ), + condition.UnknownCondition( + condition.RoleBindingReadyCondition, + condition.InitReason, + condition.RoleBindingReadyInitMessage), + condition.UnknownCondition( + condition.TLSInputReadyCondition, + condition.InitReason, + condition.InputReadyInitMessage), + ) + + // Init Topology condition if there's a reference + if instance.Spec.TopologyRef != nil { + c := condition.UnknownCondition( + condition.TopologyReadyCondition, + condition.InitReason, + condition.TopologyReadyInitMessage, + ) + cl.Set(c) + } + + instance.Status.Conditions.Init(&cl) + return nil +} + +// fields to index to reconcile when change +const ( + passwordSecretField = ".spec.secret" + caBundleSecretNameField = ".spec.tls.caBundleSecretName" // #nosec G101 + tlsAPIInternalField = ".spec.tls.api.internal.secretName" + tlsAPIPublicField = ".spec.tls.api.public.secretName" + topologyField = ".spec.topologyRef.Name" + authAppCredSecretField = ".spec.auth.applicationCredentialSecret" // #nosec G101 +) + +var allWatchFields = []string{ + passwordSecretField, + caBundleSecretNameField, + tlsAPIInternalField, + tlsAPIPublicField, + topologyField, + authAppCredSecretField, +} + +// SetupWithManager sets up the controller with the Manager. +func (r *PlacementAPIReconciler) SetupWithManager(mgr ctrl.Manager) error { + // index passwordSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, passwordSecretField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.Secret == "" { + return nil + } + return []string{cr.Spec.Secret} + }); err != nil { + return err + } + + // index caBundleSecretNameField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, caBundleSecretNameField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.TLS.CaBundleSecretName == "" { + return nil + } + return []string{cr.Spec.TLS.CaBundleSecretName} + }); err != nil { + return err + } + + // index tlsAPIInternalField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, tlsAPIInternalField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.TLS.API.Internal.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Internal.SecretName} + }); err != nil { + return err + } + + // index tlsAPIPublicField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, tlsAPIPublicField, func(rawObj client.Object) []string { + // Extract the secret name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.TLS.API.Public.SecretName == nil { + return nil + } + return []string{*cr.Spec.TLS.API.Public.SecretName} + }); err != nil { + return err + } + + // index topologyField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, topologyField, func(rawObj client.Object) []string { + // Extract the topology name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.TopologyRef == nil { + return nil + } + return []string{cr.Spec.TopologyRef.Name} + }); err != nil { + return err + } + + // index authAppCredSecretField + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &placementv1.PlacementAPI{}, authAppCredSecretField, func(rawObj client.Object) []string { + // Extract the application credential secret name from the spec, if one is provided + cr := rawObj.(*placementv1.PlacementAPI) + if cr.Spec.Auth.ApplicationCredentialSecret == "" { + return nil + } + return []string{cr.Spec.Auth.ApplicationCredentialSecret} + }); err != nil { + return err + } + + return ctrl.NewControllerManagedBy(mgr). + For(&placementv1.PlacementAPI{}). + Owns(&mariadbv1.MariaDBDatabase{}). + Owns(&mariadbv1.MariaDBAccount{}). + Owns(&keystonev1.KeystoneService{}). + Owns(&keystonev1.KeystoneEndpoint{}). + Owns(&batchv1.Job{}). + Owns(&corev1.Service{}). + Owns(&corev1.Secret{}). + Owns(&corev1.ConfigMap{}). + Owns(&appsv1.Deployment{}). + Owns(&corev1.ServiceAccount{}). + Owns(&rbacv1.Role{}). + Owns(&rbacv1.RoleBinding{}). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches(&topologyv1.Topology{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForSrc), + builder.WithPredicates(predicate.GenerationChangedPredicate{})). + Watches(&keystonev1.KeystoneAPI{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectForSrc), + builder.WithPredicates(keystonev1.KeystoneAPIStatusChangedPredicate)). + Complete(r) +} + +func (r *PlacementAPIReconciler) findObjectsForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + Log := r.GetLogger(context.Background()) + + for _, field := range allWatchFields { + crList := &placementv1.PlacementAPIList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(field, src.GetName()), + Namespace: src.GetNamespace(), + } + err := r.List(ctx, crList, listOps) + if err != nil { + Log.Error(err, fmt.Sprintf("listing %s for field: %s - %s", crList.GroupVersionKind().Kind, field, src.GetNamespace())) + return requests + } + + for _, item := range crList.Items { + Log.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + } + + return requests +} + +func (r *PlacementAPIReconciler) findObjectForSrc(ctx context.Context, src client.Object) []reconcile.Request { + requests := []reconcile.Request{} + + Log := r.GetLogger(ctx) + + crList := &placementv1.PlacementAPIList{} + listOps := &client.ListOptions{ + Namespace: src.GetNamespace(), + } + err := r.List(ctx, crList, listOps) + if err != nil { + Log.Error(err, fmt.Sprintf("listing %s for namespace: %s", crList.GroupVersionKind().Kind, src.GetNamespace())) + return requests + } + + for _, item := range crList.Items { + Log.Info(fmt.Sprintf("input source %s changed, reconcile: %s - %s", src.GetName(), item.GetName(), item.GetNamespace())) + + requests = append(requests, + reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + }, + ) + } + + return requests +} + +func (r *PlacementAPIReconciler) reconcileDelete(ctx context.Context, instance *placementv1.PlacementAPI, helper *helper.Helper) (ctrl.Result, error) { + Log := r.GetLogger(ctx) + Log.Info("Reconciling Service delete") + + // Remove finalizer from the referenced Topology CR + if ctrlResult, err := topologyv1.EnsureDeletedTopologyRef( + ctx, + helper, + instance.Status.LastAppliedTopology, + instance.Name, + ); err != nil { + return ctrlResult, err + } + + // remove db finalizer before the placement one + db, err := mariadbv1.GetDatabaseByNameAndAccount(ctx, helper, placement.DatabaseName, instance.Spec.DatabaseAccount, instance.Namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + if !k8s_errors.IsNotFound(err) { + if err := db.DeleteFinalizer(ctx, helper); err != nil { + return ctrl.Result{}, err + } + } + + // Remove the finalizer from our KeystoneEndpoint CR + keystoneEndpoint, err := keystonev1.GetKeystoneEndpointWithName(ctx, helper, placement.ServiceName, instance.Namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + if err == nil { + if controllerutil.RemoveFinalizer(keystoneEndpoint, helper.GetFinalizer()) { + err = r.Update(ctx, keystoneEndpoint) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + Log.Info("Removed finalizer from our KeystoneEndpoint") + } + } + + // Remove the finalizer from our KeystoneService CR + keystoneService, err := keystonev1.GetKeystoneServiceWithName(ctx, helper, placement.ServiceName, instance.Namespace) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + + if err == nil { + if controllerutil.RemoveFinalizer(keystoneService, helper.GetFinalizer()) { + err = r.Update(ctx, keystoneService) + if err != nil && !k8s_errors.IsNotFound(err) { + return ctrl.Result{}, err + } + Log.Info("Removed finalizer from our KeystoneService") + } + } + + // We did all the cleanup on the objects we created so we can remove the + // finalizer from ourselves to allow the deletion + controllerutil.RemoveFinalizer(instance, helper.GetFinalizer()) + Log.Info("Reconciled Service delete successfully") + return ctrl.Result{}, nil +} + +func (r *PlacementAPIReconciler) ensureDB( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, +) (*mariadbv1.Database, ctrl.Result, error) { + db := mariadbv1.NewDatabaseForAccount( + instance.Spec.DatabaseInstance, // mariadb/galera service to target + placement.DatabaseName, // name used in CREATE DATABASE in mariadb + placement.DatabaseName, // CR name for MariaDBDatabase + instance.Spec.DatabaseAccount, // CR name for MariaDBAccount + instance.Namespace, // namespace + ) + + // create or patch the DB + ctrlResult, err := db.CreateOrPatchAll(ctx, h) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DBReadyErrorMessage, + err.Error())) + return db, ctrl.Result{}, err + } + if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.DBReadyRunningMessage)) + return db, ctrlResult, nil + } + // wait for the DB to be setup + // (ksambor) should we use WaitForDBCreatedWithTimeout instead? + ctrlResult, err = db.WaitForDBCreated(ctx, h) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DBReadyErrorMessage, + err.Error())) + return db, ctrlResult, err + } + if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.DBReadyRunningMessage)) + return db, ctrlResult, nil + } + + // update Status.DatabaseHostname, used to config the service + instance.Status.DatabaseHostname = db.GetDatabaseHostname() + instance.Status.Conditions.MarkTrue(condition.DBReadyCondition, condition.DBReadyMessage) + return db, ctrlResult, nil +} + +func (r *PlacementAPIReconciler) ensureDbSync( + ctx context.Context, + instance *placementv1.PlacementAPI, + helper *helper.Helper, + serviceAnnotations map[string]string, +) (ctrl.Result, error) { + Log := r.GetLogger(ctx) + serviceLabels := getServiceLabels(instance) + dbSyncHash := instance.Status.Hash[placementv1.DbSyncHash] + jobDef := placement.DbSyncJob(instance, serviceLabels, serviceAnnotations) + dbSyncjob := job.NewJob( + jobDef, + placementv1.DbSyncHash, + instance.Spec.PreserveJobs, + time.Duration(5)*time.Second, + dbSyncHash, + ) + ctrlResult, err := dbSyncjob.DoJob( + ctx, + helper, + ) + if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBSyncReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.DBSyncReadyRunningMessage)) + return ctrlResult, nil + } + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DBSyncReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DBSyncReadyErrorMessage, + err.Error())) + return ctrl.Result{}, err + } + if dbSyncjob.HasChanged() { + instance.Status.Hash[placementv1.DbSyncHash] = dbSyncjob.GetHash() + Log.Info(fmt.Sprintf("Job %s hash added - %s", jobDef.Name, instance.Status.Hash[placementv1.DbSyncHash])) + } + instance.Status.Conditions.MarkTrue(condition.DBSyncReadyCondition, condition.DBSyncReadyMessage) + + return ctrl.Result{}, nil +} + +func (r *PlacementAPIReconciler) ensureDeployment( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, + inputHash string, + serviceAnnotations map[string]string, +) (ctrl.Result, error) { + Log := r.GetLogger(ctx) + Log.Info("Reconciling Service") + + serviceLabels := getServiceLabels(instance) + + // + // Handle Topology + // + topology, err := topologyv1.EnsureServiceTopology( + ctx, + h, + instance.Spec.TopologyRef, + instance.Status.LastAppliedTopology, + instance.Name, + labels.GetLabelSelector(serviceLabels), + ) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.TopologyReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.TopologyReadyErrorMessage, + err.Error())) + return ctrl.Result{}, fmt.Errorf("waiting for Topology requirements: %w", err) + } + + // If TopologyRef is present and ensureServiceTopology returned a valid + // topology object, set .Status.LastAppliedTopology to the referenced one + // and mark the condition as true + if instance.Spec.TopologyRef != nil { + // update the Status with the last retrieved Topology name + instance.Status.LastAppliedTopology = instance.Spec.TopologyRef + // update the TopologyRef associated condition + instance.Status.Conditions.MarkTrue(condition.TopologyReadyCondition, condition.TopologyReadyMessage) + } else { + // remove LastAppliedTopology from the .Status + instance.Status.LastAppliedTopology = nil + } + + // Define a new Deployment object + deplDef, err := placement.Deployment(instance, inputHash, serviceLabels, serviceAnnotations, topology) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error())) + } + + depl := deployment.NewDeployment( + deplDef, + time.Duration(5)*time.Second, + ) + + ctrlResult, err := depl.CreateOrPatch(ctx, h) + if err != nil { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.DeploymentReadyErrorMessage, + err.Error())) + return ctrlResult, err + } else if (ctrlResult != ctrl.Result{}) { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.DeploymentReadyRunningMessage)) + return ctrl.Result{}, nil + } + + deploy := depl.GetDeployment() + if deploy.Generation == deploy.Status.ObservedGeneration { + instance.Status.ReadyCount = deploy.Status.ReadyReplicas + } + + // verify if network attachment matches expectations + networkReady, networkAttachmentStatus, err := nad.VerifyNetworkStatusFromAnnotation(ctx, h, instance.Spec.NetworkAttachments, serviceLabels, instance.Status.ReadyCount) + if err != nil { + return ctrl.Result{}, err + } + + instance.Status.NetworkAttachments = networkAttachmentStatus + if networkReady { + instance.Status.Conditions.MarkTrue(condition.NetworkAttachmentsReadyCondition, condition.NetworkAttachmentsReadyMessage) + } else { + instance.Status.Conditions.Set(condition.FalseCondition( + condition.NetworkAttachmentsReadyCondition, + condition.ErrorReason, + condition.SeverityWarning, + condition.NetworkAttachmentsErrorMessage, + instance.Spec.NetworkAttachments)) + + return ctrl.Result{}, err + } + + // Mark the Deployment as Ready only if the number of Replicas is equals + // to the Deployed instances (ReadyCount), and the the Status.Replicas + // match Status.ReadyReplicas. If a deployment update is in progress, + // Replicas > ReadyReplicas. + // In addition, make sure the controller sees the last Generation + // by comparing it with the ObservedGeneration. + if deployment.IsReady(deploy) { + instance.Status.Conditions.MarkTrue(condition.DeploymentReadyCondition, condition.DeploymentReadyMessage) + } else { + Log.Info("Deployment is not ready") + instance.Status.Conditions.Set(condition.FalseCondition( + condition.DeploymentReadyCondition, + condition.RequestedReason, + condition.SeverityInfo, + condition.DeploymentReadyRunningMessage)) + // It is OK to return success as we are watching for StatefulSet changes + return ctrl.Result{}, nil + } + // create Deployment - end + + Log.Info("Reconciled Service successfully") + return ctrl.Result{}, nil + +} + +// generateServiceConfigMaps - create create configmaps which hold scripts and service configuration +func (r *PlacementAPIReconciler) generateServiceConfigMaps( + ctx context.Context, + h *helper.Helper, + instance *placementv1.PlacementAPI, + ospSecret corev1.Secret, + envVars *map[string]env.Setter, + db *mariadbv1.Database, +) error { + // + // create Secret required for placement input + // - %-scripts secret holding scripts to e.g. bootstrap the service + // - %-config secret holding minimal placement config required to get the service up, user can add additional files to be added to the service + // - parameters which has passwords gets added from the ospSecret via the init container + // + + cmLabels := labels.GetLabels(instance, labels.GetGroupLabel(placement.ServiceName), map[string]string{}) + + var tlsCfg *tls.Service + if instance.Spec.TLS.CaBundleSecretName != "" { + tlsCfg = &tls.Service{} + } + + // customData hold any customization for the service. + // custom.conf is going to /etc//.conf.d + // my.cnf is going to /etc/my.cnf + // all other files get placed into /etc/ to allow overwrite of e.g. policy.json + customData := map[string]string{ + common.CustomServiceConfigFileName: instance.Spec.CustomServiceConfig, + "my.cnf": db.GetDatabaseClientConfig(tlsCfg), //(mschuppert) for now just get the default my.cnf + } + maps.Copy(customData, instance.Spec.DefaultConfigOverwrite) + + keystoneAPI, err := keystonev1.GetKeystoneAPI(ctx, h, instance.Namespace, map[string]string{}) + if err != nil { + return err + } + keystoneInternalURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointInternal) + if err != nil { + return err + } + keystonePublicURL, err := keystoneAPI.GetEndpoint(endpoint.EndpointPublic) + if err != nil { + return err + } + + databaseAccount := db.GetAccount() + dbSecret := db.GetSecret() + + templateParameters := map[string]any{ + "ServiceUser": instance.Spec.ServiceUser, + "KeystoneInternalURL": keystoneInternalURL, + "KeystonePublicURL": keystonePublicURL, + "PlacementPassword": string(ospSecret.Data[instance.Spec.PasswordSelectors.Service]), + "log_file": "/var/log/placement/placement-api.log", + "Region": keystoneAPI.GetRegion(), + "DatabaseConnection": fmt.Sprintf("mysql+pymysql://%s:%s@%s/%s?read_default_file=/etc/my.cnf", + databaseAccount.Spec.UserName, + string(dbSecret.Data[mariadbv1.DatabasePasswordSelector]), + instance.Status.DatabaseHostname, + placement.DatabaseName, + ), + } + + templateParameters["UseApplicationCredentials"] = false + // Try to get Application Credential for this service + if instance.Spec.Auth.ApplicationCredentialSecret != "" { + acSecretObj, _, err := secret.GetSecret(ctx, h, instance.Spec.Auth.ApplicationCredentialSecret, instance.Namespace) + if err != nil { + if k8s_errors.IsNotFound(err) { + h.GetLogger().Info("ApplicationCredential secret not found, waiting", "secret", instance.Spec.Auth.ApplicationCredentialSecret) + return fmt.Errorf("%w: %s", ErrACSecretNotFound, instance.Spec.Auth.ApplicationCredentialSecret) + } + h.GetLogger().Error(err, "Failed to get ApplicationCredential secret", "secret", instance.Spec.Auth.ApplicationCredentialSecret) + return err + } + acID, okID := acSecretObj.Data[keystonev1.ACIDSecretKey] + acSecretData, okSecret := acSecretObj.Data[keystonev1.ACSecretSecretKey] + if !okID || len(acID) == 0 || !okSecret || len(acSecretData) == 0 { + h.GetLogger().Info("ApplicationCredential secret missing required keys", "secret", instance.Spec.Auth.ApplicationCredentialSecret) + return fmt.Errorf("%w: %s", ErrACSecretMissingKeys, instance.Spec.Auth.ApplicationCredentialSecret) + } + templateParameters["UseApplicationCredentials"] = true + templateParameters["ACID"] = string(acID) + templateParameters["ACSecret"] = string(acSecretData) + h.GetLogger().Info("Using ApplicationCredentials auth", "secret", instance.Spec.Auth.ApplicationCredentialSecret) + } + + // create httpd vhost template parameters + httpdVhostConfig := map[string]any{} + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + endptConfig := map[string]any{} + endptConfig["ServerName"] = fmt.Sprintf("placement-%s.%s.svc", endpt.String(), instance.Namespace) + endptConfig["TLS"] = false // default TLS to false, and set it bellow to true if enabled + if instance.Spec.TLS.API.Enabled(endpt) { + endptConfig["TLS"] = true + endptConfig["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String()) + endptConfig["SSLCertificateKeyFile"] = fmt.Sprintf("/etc/pki/tls/private/%s.key", endpt.String()) + } + httpdVhostConfig[endpt.String()] = endptConfig + } + templateParameters["VHosts"] = httpdVhostConfig + templateParameters["TimeOut"] = instance.Spec.APITimeout + + extraTemplates := map[string]string{ + "placement.conf": "placementapi/config/placement.conf", + } + + cms := []util.Template{ + // ScriptsConfigMap + { + Name: fmt.Sprintf("%s-scripts", instance.Name), + Namespace: instance.Namespace, + Type: util.TemplateTypeScripts, + InstanceType: instance.Kind, + Labels: cmLabels, + }, + // ConfigMap + { + Name: fmt.Sprintf("%s-config-data", instance.Name), + Namespace: instance.Namespace, + Type: util.TemplateTypeConfig, + InstanceType: instance.Kind, + CustomData: customData, + ConfigOptions: templateParameters, + Labels: cmLabels, + AdditionalTemplate: extraTemplates, + }, + } + return secret.EnsureSecrets(ctx, h, instance, cms, envVars) +} + +// createHashOfInputHashes - creates a hash of hashes which gets added to the resources which requires a restart +// if any of the input resources change, like configs, passwords, ... +// +// returns the hash, whether the hash changed (as a bool) and any error +func (r *PlacementAPIReconciler) createHashOfInputHashes( + ctx context.Context, + instance *placementv1.PlacementAPI, + envVars map[string]env.Setter, +) (string, bool, error) { + Log := r.GetLogger(ctx) + var hashMap map[string]string + changed := false + mergedMapVars := env.MergeEnvs([]corev1.EnvVar{}, envVars) + hash, err := util.ObjectHash(mergedMapVars) + if err != nil { + return hash, changed, err + } + if hashMap, changed = util.SetHash(instance.Status.Hash, common.InputHashName, hash); changed { + instance.Status.Hash = hashMap + Log.Info(fmt.Sprintf("Input maps hash %s - %s", common.InputHashName, hash)) + } + return hash, changed, nil +} diff --git a/placement-import/internal/placement/const.go b/placement-import/internal/placement/const.go new file mode 100644 index 000000000..f058387ef --- /dev/null +++ b/placement-import/internal/placement/const.go @@ -0,0 +1,39 @@ +/* + +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 placement provides constants and utilities for managing OpenStack Placement service +package placement + +const ( + // ServiceName - + ServiceName = "placement" + // DatabaseName - + DatabaseName = "placement" + + // ConfigSecretName is the name of the secret containing placement configuration + ConfigSecretName = "placement-config-data" + + // PlacementPublicPort - + PlacementPublicPort int32 = 8778 + // PlacementInternalPort - + PlacementInternalPort int32 = 8778 + + // KollaServiceCommand is the command used to start the placement service in Kolla containers + KollaServiceCommand = "/usr/local/bin/kolla_start" + + // PlacementUserID is the linux user ID used by Kolla for the placement + // user in the service containers + PlacementUserID int64 = 42482 +) diff --git a/placement-import/internal/placement/dbsync.go b/placement-import/internal/placement/dbsync.go new file mode 100644 index 000000000..667b1fc80 --- /dev/null +++ b/placement-import/internal/placement/dbsync.go @@ -0,0 +1,91 @@ +/* + +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 placement + +import ( + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +// DbSyncJob func +func DbSyncJob( + instance *placementv1.PlacementAPI, + labels map[string]string, + annotations map[string]string, +) *batchv1.Job { + args := []string{"-c", KollaServiceCommand} + + envVars := map[string]env.Setter{} + envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") + envVars["KOLLA_BOOTSTRAP"] = env.SetValue("true") + + // create Volume and VolumeMounts + volumes := getVolumes(instance.Name) + volumeMounts := getVolumeMounts("dbsync") + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name + "-db-sync", + Namespace: instance.Namespace, + Labels: labels, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: annotations, + }, + Spec: corev1.PodSpec{ + RestartPolicy: corev1.RestartPolicyOnFailure, + ServiceAccountName: instance.RbacResourceName(), + Containers: []corev1.Container{ + { + Name: instance.Name + "-db-sync", + Command: []string{ + "/bin/bash", + }, + Args: args, + Image: instance.Spec.ContainerImage, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To(PlacementUserID), + }, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: volumeMounts, + }, + }, + Volumes: volumes, + }, + }, + }, + } + + if instance.Spec.NodeSelector != nil { + job.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector + } + + return job +} diff --git a/placement-import/internal/placement/deployment.go b/placement-import/internal/placement/deployment.go new file mode 100644 index 000000000..b09236390 --- /dev/null +++ b/placement-import/internal/placement/deployment.go @@ -0,0 +1,186 @@ +/* + +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 placement + +import ( + common "github.com/openstack-k8s-operators/lib-common/modules/common" + affinity "github.com/openstack-k8s-operators/lib-common/modules/common/affinity" + env "github.com/openstack-k8s-operators/lib-common/modules/common/env" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "github.com/openstack-k8s-operators/lib-common/modules/common/tls" + + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/utils/ptr" +) + +// Deployment func +func Deployment( + instance *placementv1.PlacementAPI, + configHash string, + labels map[string]string, + annotations map[string]string, + topology *topologyv1.Topology, +) (*appsv1.Deployment, error) { + livenessProbe := &corev1.Probe{ + // TODO might need tuning + TimeoutSeconds: 30, + PeriodSeconds: 30, + InitialDelaySeconds: 5, + } + readinessProbe := &corev1.Probe{ + // TODO might need tuning + TimeoutSeconds: 30, + PeriodSeconds: 30, + InitialDelaySeconds: 5, + } + + args := []string{"-c", KollaServiceCommand} + // + // https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + // + livenessProbe.HTTPGet = &corev1.HTTPGetAction{ + Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(PlacementPublicPort)}, + } + readinessProbe.HTTPGet = &corev1.HTTPGetAction{ + Port: intstr.IntOrString{Type: intstr.Int, IntVal: int32(PlacementPublicPort)}, + } + + if instance.Spec.TLS.API.Enabled(service.EndpointPublic) { + livenessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + readinessProbe.HTTPGet.Scheme = corev1.URISchemeHTTPS + } + + envVars := map[string]env.Setter{} + envVars["KOLLA_CONFIG_STRATEGY"] = env.SetValue("COPY_ALWAYS") + envVars["CONFIG_HASH"] = env.SetValue(configHash) + + // create Volume and VolumeMounts + volumes := getVolumes(instance.Name) + volumeMounts := getVolumeMounts("api") + + // add CA cert if defined + if instance.Spec.TLS.CaBundleSecretName != "" { + volumes = append(volumes, instance.Spec.TLS.CreateVolume()) + volumeMounts = append(volumeMounts, instance.Spec.TLS.CreateVolumeMounts(nil)...) + } + + for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { + if instance.Spec.TLS.API.Enabled(endpt) { + var tlsEndptCfg tls.GenericService + switch endpt { + case service.EndpointPublic: + tlsEndptCfg = instance.Spec.TLS.API.Public + case service.EndpointInternal: + tlsEndptCfg = instance.Spec.TLS.API.Internal + } + + svc, err := tlsEndptCfg.ToService() + if err != nil { + return nil, err + } + volumes = append(volumes, svc.CreateVolume(endpt.String())) + volumeMounts = append(volumeMounts, svc.CreateVolumeMounts(endpt.String())...) + } + } + + deployment := &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Name, + Namespace: instance.Namespace, + }, + Spec: appsv1.DeploymentSpec{ + Selector: &metav1.LabelSelector{ + MatchLabels: labels, + }, + Replicas: instance.Spec.Replicas, + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: annotations, + Labels: labels, + }, + Spec: corev1.PodSpec{ + ServiceAccountName: instance.RbacResourceName(), + Volumes: volumes, + Containers: []corev1.Container{ + { + Name: instance.Name + "-log", + Command: []string{ + "/usr/bin/dumb-init", + }, + Args: []string{ + "--single-child", + "--", + "/usr/bin/tail", + "-n+1", + "-F", + "/var/log/placement/placement-api.log", + }, + Image: instance.Spec.ContainerImage, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To(PlacementUserID), + }, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: volumeMounts, + Resources: instance.Spec.Resources, + ReadinessProbe: readinessProbe, + LivenessProbe: livenessProbe, + }, + { + Name: instance.Name + "-api", + Command: []string{ + "/bin/bash", + }, + Args: args, + Image: instance.Spec.ContainerImage, + SecurityContext: &corev1.SecurityContext{ + RunAsUser: ptr.To(PlacementUserID), + }, + Env: env.MergeEnvs([]corev1.EnvVar{}, envVars), + VolumeMounts: volumeMounts, + Resources: instance.Spec.Resources, + ReadinessProbe: readinessProbe, + LivenessProbe: livenessProbe, + }, + }, + }, + }, + }, + } + if instance.Spec.NodeSelector != nil { + deployment.Spec.Template.Spec.NodeSelector = *instance.Spec.NodeSelector + } + if topology != nil { + topology.ApplyTo(&deployment.Spec.Template) + } else { + // If possible two pods of the same service should not + // run on the same worker node. If this is not possible + // the get still created on the same worker node. + deployment.Spec.Template.Spec.Affinity = affinity.DistributePods( + common.AppSelector, + []string{ + ServiceName, + }, + corev1.LabelHostname, + ) + } + return deployment, nil +} diff --git a/placement-import/internal/placement/volumes.go b/placement-import/internal/placement/volumes.go new file mode 100644 index 000000000..fa60ef15e --- /dev/null +++ b/placement-import/internal/placement/volumes.go @@ -0,0 +1,81 @@ +/* + +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 placement + +import ( + corev1 "k8s.io/api/core/v1" +) + +// getVolumes - service volumes +func getVolumes(name string) []corev1.Volume { + var scriptsVolumeDefaultMode int32 = 0755 + var configMode int32 = 0640 + + return []corev1.Volume{ + { + Name: "scripts", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + DefaultMode: &scriptsVolumeDefaultMode, + SecretName: name + "-scripts", + }, + }, + }, + { + Name: "config-data", + VolumeSource: corev1.VolumeSource{ + Secret: &corev1.SecretVolumeSource{ + DefaultMode: &configMode, + SecretName: name + "-config-data", + }, + }, + }, + { + Name: "logs", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{Medium: ""}, + }, + }, + } + +} + +// getVolumeMounts - general VolumeMounts +func getVolumeMounts(serviceName string) []corev1.VolumeMount { + return []corev1.VolumeMount{ + { + Name: "scripts", + MountPath: "/usr/local/bin/container-scripts", + ReadOnly: true, + }, + { + Name: "logs", + MountPath: "/var/log/placement", + ReadOnly: false, + }, + { + Name: "config-data", + MountPath: "/var/lib/openstack/config", + ReadOnly: false, + }, + { + Name: "config-data", + MountPath: "/var/lib/kolla/config_files/config.json", + SubPath: "placement-" + serviceName + "-config.json", + ReadOnly: true, + }, + } +} diff --git a/placement-import/internal/webhook/v1beta1/placementapi_webhook.go b/placement-import/internal/webhook/v1beta1/placementapi_webhook.go new file mode 100644 index 000000000..042fb6eaf --- /dev/null +++ b/placement-import/internal/webhook/v1beta1/placementapi_webhook.go @@ -0,0 +1,129 @@ +/* +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 v1beta1 implements webhook handlers for Placement v1beta1 API resources. +package v1beta1 + +import ( + "context" + "errors" + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + placementv1beta1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" +) + +var ( + // ErrInvalidObjectType is returned when an unexpected object type is provided + ErrInvalidObjectType = errors.New("invalid object type") +) + +// nolint:unused +// log is for logging in this package. +var placementapilog = logf.Log.WithName("placementapi-resource") + +// SetupPlacementAPIWebhookWithManager registers the webhook for PlacementAPI in the manager. +func SetupPlacementAPIWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr).For(&placementv1beta1.PlacementAPI{}). + WithValidator(&PlacementAPICustomValidator{}). + WithDefaulter(&PlacementAPICustomDefaulter{}). + Complete() +} + +// +kubebuilder:webhook:path=/mutate-placement-openstack-org-v1beta1-placementapi,mutating=true,failurePolicy=fail,sideEffects=None,groups=placement.openstack.org,resources=placementapis,verbs=create;update,versions=v1beta1,name=mplacementapi-v1beta1.kb.io,admissionReviewVersions=v1 + +// PlacementAPICustomDefaulter struct is responsible for setting default values on the custom resource of the +// Kind PlacementAPI when those are created or updated. +// +// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, +// as it is used only for temporary operations and does not need to be deeply copied. +type PlacementAPICustomDefaulter struct { + // TODO(user): Add more fields as needed for defaulting +} + +var _ webhook.CustomDefaulter = &PlacementAPICustomDefaulter{} + +// Default implements webhook.CustomDefaulter so a webhook will be registered for the Kind PlacementAPI. +func (d *PlacementAPICustomDefaulter) Default(_ context.Context, obj runtime.Object) error { + placementapi, ok := obj.(*placementv1beta1.PlacementAPI) + + if !ok { + return fmt.Errorf("expected an PlacementAPI object but got %T: %w", obj, ErrInvalidObjectType) + } + placementapilog.Info("Defaulting for PlacementAPI", "name", placementapi.GetName()) + + // Call the Default method on the PlacementAPI type + placementapi.Default() + + return nil +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here. +// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook. +// +kubebuilder:webhook:path=/validate-placement-openstack-org-v1beta1-placementapi,mutating=false,failurePolicy=fail,sideEffects=None,groups=placement.openstack.org,resources=placementapis,verbs=create;update,versions=v1beta1,name=vplacementapi-v1beta1.kb.io,admissionReviewVersions=v1 + +// PlacementAPICustomValidator struct is responsible for validating the PlacementAPI resource +// when it is created, updated, or deleted. +// +// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods, +// as this struct is used only for temporary operations and does not need to be deeply copied. +type PlacementAPICustomValidator struct { + // TODO(user): Add more fields as needed for validation +} + +var _ webhook.CustomValidator = &PlacementAPICustomValidator{} + +// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type PlacementAPI. +func (v *PlacementAPICustomValidator) ValidateCreate(_ context.Context, obj runtime.Object) (admission.Warnings, error) { + placementapi, ok := obj.(*placementv1beta1.PlacementAPI) + if !ok { + return nil, fmt.Errorf("expected a PlacementAPI object but got %T: %w", obj, ErrInvalidObjectType) + } + placementapilog.Info("Validation for PlacementAPI upon creation", "name", placementapi.GetName()) + + // Call the ValidateCreate method on the PlacementAPI type + return placementapi.ValidateCreate() +} + +// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type PlacementAPI. +func (v *PlacementAPICustomValidator) ValidateUpdate(_ context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { + placementapi, ok := newObj.(*placementv1beta1.PlacementAPI) + if !ok { + return nil, fmt.Errorf("expected a PlacementAPI object for the newObj but got %T: %w", newObj, ErrInvalidObjectType) + } + placementapilog.Info("Validation for PlacementAPI upon update", "name", placementapi.GetName()) + + // Call the ValidateUpdate method on the PlacementAPI type + return placementapi.ValidateUpdate(oldObj) +} + +// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type PlacementAPI. +func (v *PlacementAPICustomValidator) ValidateDelete(_ context.Context, obj runtime.Object) (admission.Warnings, error) { + placementapi, ok := obj.(*placementv1beta1.PlacementAPI) + if !ok { + return nil, fmt.Errorf("expected a PlacementAPI object but got %T: %w", obj, ErrInvalidObjectType) + } + placementapilog.Info("Validation for PlacementAPI upon deletion", "name", placementapi.GetName()) + + // Call the ValidateDelete method on the PlacementAPI type + return placementapi.ValidateDelete() +} diff --git a/placement-import/kuttl-test.yaml b/placement-import/kuttl-test.yaml new file mode 100644 index 000000000..369895d95 --- /dev/null +++ b/placement-import/kuttl-test.yaml @@ -0,0 +1,40 @@ +# +# EXECUTION (from placement-operator repo root directory): +# +# kubectl-kuttl test --config ${PLACEMENT_KUTTL_CONF} ${PLACEMENT_KUTTL_DIR} +# +# PLACEMENT_KUTTL_CONF: path to 'kuttl-test.yaml' there is a default kuttl-test.yaml in the top directory +# of placement +# PLACEMENT_KUTTL_DIR: the test directory containing kuttl tests, relative to the placement repo +# that can be found in '////placement-operator/test/kuttl/tests' +# +# ASSUMPTIONS: +# +# 1. Latest version of kuttl is installed at /usr/local/bin/kubectl-kuttl +# - wget https://github.com/kudobuilder/kuttl/releases/download/v0.11.1/kubectl-kuttl_0.11.1_linux_x86_64 +# - mv kubectl-kuttl_0.11.1_linux_x86_64 /usr/local/bin/kubectl-kuttl +# - chmod 755 /usr/local/bin/kubectl-kuttl +# 2. An OCP 4.10+ CRC cluster with Mariadb and Keystone operators are deployed (details below using devsetup +# and install_yamls) +# 2a. CPUS=12 MEMORY=12016 make crc +# 2b. oc login -u kubeadmin -p 12345678 https://api.crc.testing:6443 +# 2c. eval $(crc oc-env) +# 2d. make crc_storage +# 2e. make input +# 2f. make keystone +# 2g. make mariadb +# 2h. make keystone_deploy +# 2i. make mariadb_deploy +# 2j. start placement-operator either through install_yamls via 'make placement' or in another +# terminal execute 'OPERATOR_TEMPLATES=./templates make run' within root of placement repo +# 3. The above environment variables PLACEMENT_KUTTL_CONF and PLACEMENT_KUTTL_DIR are set + +apiVersion: kuttl.dev/v1beta1 +kind: TestSuite +reportFormat: JSON +reportName: kuttl-test-placement +namespace: placement-kuttl-tests +timeout: 300 +parallel: 1 +suppress: + - events diff --git a/placement-import/renovate.json b/placement-import/renovate.json new file mode 100644 index 000000000..2a602aa99 --- /dev/null +++ b/placement-import/renovate.json @@ -0,0 +1,16 @@ +{ + "extends": [ + "github>openstack-k8s-operators/renovate-config:default.json5" + ], + "packageRules": [ + { + "matchPackageNames": ["github.com/openstack-k8s-operators/placement-operator/api"], + "enabled": false + } + ], + "postUpgradeTasks": { + "commands": ["make gowork", "make tidy", "make manifests generate"], + "fileFilters": ["**/go.mod", "**/go.sum", "**/*.go", "**/*.yaml"], + "executionMode": "update" + } +} diff --git a/placement-import/templates/placementapi/config/httpd.conf b/placement-import/templates/placementapi/config/httpd.conf new file mode 100644 index 000000000..7c53ffd7c --- /dev/null +++ b/placement-import/templates/placementapi/config/httpd.conf @@ -0,0 +1,81 @@ +ServerTokens Prod +ServerSignature Off +TraceEnable Off +PidFile run/httpd.pid +ServerRoot "/etc/httpd" +ServerName "localhost.localdomain" + +User apache +Group apache + +Listen 8778 + +TypesConfig /etc/mime.types + +Include conf.modules.d/*.conf +# XXX: To disable SSL +#+ exec /usr/sbin/httpd +#AH00526: Syntax error on line 85 of /etc/httpd/conf.d/ssl.conf: +#SSLCertificateFile: file '/etc/pki/tls/certs/localhost.crt' does not exist or is empty +#Include conf.d/*.conf + +LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined +LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" proxy + +SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded +ErrorLog /dev/stderr +TransferLog /dev/stdout +CustomLog /dev/stdout combined env=!forwarded +CustomLog /dev/stdout proxy env=forwarded + +{{ range $endpt, $vhost := .VHosts }} +# {{ $endpt }} vhost {{ $vhost.ServerName }} configuration + + = 2.4> + ErrorLogFormat "%M" + + ServerName {{ $vhost.ServerName }} + TimeOut {{ $.TimeOut }} + + ## Vhost docroot + ErrorLog /dev/stdout + SetEnvIf X-Forwarded-For "^.*\..*\..*\..*" forwarded + CustomLog /dev/stdout combined env=!forwarded + CustomLog /dev/stdout proxy env=forwarded + ServerSignature Off + CustomLog /dev/stdout combined + +{{- if $vhost.TLS }} + SetEnvIf X-Forwarded-Proto https HTTPS=1 + + ## SSL directives + SSLEngine on + SSLCertificateFile "{{ $vhost.SSLCertificateFile }}" + SSLCertificateKeyFile "{{ $vhost.SSLCertificateKeyFile }}" +{{- end }} + + ## Directories, there should at least be a declaration for /var/www/cgi-bin/placement + + Options -Indexes +FollowSymLinks +MultiViews + AllowOverride None + Require all granted + + + ## WSGI configuration + WSGIProcessGroup placement-api + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + WSGIDaemonProcess {{ $endpt }} display-name={{ $endpt }} group=placement processes=3 threads=1 user=placement + WSGIProcessGroup {{ $endpt }} + WSGIScriptAlias / /usr/bin/placement-api + +{{ end }} + +Alias /placement-api /usr/bin/placement-api + + SetHandler wsgi-script + Options +ExecCGI + WSGIProcessGroup placement-api + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + diff --git a/placement-import/templates/placementapi/config/placement-api-config.json b/placement-import/templates/placementapi/config/placement-api-config.json new file mode 100644 index 000000000..e7d222f5f --- /dev/null +++ b/placement-import/templates/placementapi/config/placement-api-config.json @@ -0,0 +1,70 @@ +{ + "command": "/usr/sbin/httpd -DFOREGROUND", + "config_files": [ + { + "source": "/var/lib/openstack/config/placement.conf", + "dest": "/etc/placement/placement.conf", + "owner": "placement", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/httpd.conf", + "dest": "/etc/httpd/conf/httpd.conf", + "owner": "apache", + "perm": "0644" + }, + { + "source": "/var/lib/openstack/config/custom.conf", + "dest": "/etc/placement/placement.conf.d/custom.conf", + "owner": "placement", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/ssl.conf", + "dest": "/etc/httpd/conf.d/ssl.conf", + "owner": "apache", + "perm": "0644" + }, + { + "source": "/var/lib/config-data/tls/certs/*", + "dest": "/etc/pki/tls/certs/", + "owner": "placement", + "perm": "0440", + "optional": true, + "merge": true + }, + { + "source": "/var/lib/config-data/tls/private/*", + "dest": "/etc/pki/tls/private/", + "owner": "placement", + "perm": "0400", + "optional": true, + "merge": true + }, + { + "source": "/var/lib/openstack/config/policy.yaml", + "dest": "/etc/placement/policy.yaml", + "owner": "placement", + "perm": "0600", + "optional": true + }, + { + "source": "/var/lib/openstack/config/my.cnf", + "dest": "/etc/my.cnf", + "owner": "placement", + "perm": "0644" + } + ], + "permissions": [ + { + "path": "/var/log/placement", + "owner": "placement:apache", + "recurse": true + }, + { + "path": "/etc/httpd/run/", + "owner": "placement:apache", + "recurse": true + } + ] +} diff --git a/placement-import/templates/placementapi/config/placement-dbsync-config.json b/placement-import/templates/placementapi/config/placement-dbsync-config.json new file mode 100644 index 000000000..de04aa6db --- /dev/null +++ b/placement-import/templates/placementapi/config/placement-dbsync-config.json @@ -0,0 +1,23 @@ +{ + "command": "placement-manage db sync", + "config_files": [ + { + "source": "/var/lib/openstack/config/placement.conf", + "dest": "/etc/placement/placement.conf", + "owner": "placement", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/custom.conf", + "dest": "/etc/placement/placement.conf.d/custom.conf", + "owner": "placement", + "perm": "0600" + }, + { + "source": "/var/lib/openstack/config/my.cnf", + "dest": "/etc/my.cnf", + "owner": "placement", + "perm": "0644" + } + ] +} diff --git a/placement-import/templates/placementapi/config/placement.conf b/placement-import/templates/placementapi/config/placement.conf new file mode 100644 index 000000000..114e956a2 --- /dev/null +++ b/placement-import/templates/placementapi/config/placement.conf @@ -0,0 +1,38 @@ +[DEFAULT] +# enable log rotation in oslo config by default +max_logfile_count=5 +max_logfile_size_mb=50 +log_rotation_type=size +{{if (index . "log_file") }} +log_file = {{ .log_file }} +{{end}} +debug = true + +[placement_database] +connection = {{ .DatabaseConnection }} + +[api] +auth_strategy = keystone + +[keystone_authtoken] +www_authenticate_uri = {{ .KeystonePublicURL }} +auth_url = {{ .KeystoneInternalURL }} +{{ if .UseApplicationCredentials -}} +auth_type = v3applicationcredential +application_credential_id = {{ .ACID }} +application_credential_secret = {{ .ACSecret }} +{{ else -}} +auth_type = password +username = {{ .ServiceUser }} +password = {{ .PlacementPassword }} +user_domain_name = Default +project_domain_name = Default +project_name = service +{{- end }} +{{ if (index . "Region") -}} +region_name = {{ .Region }} +{{ end -}} +interface = internal + +[oslo_policy] +policy_file=/etc/placement/policy.yaml diff --git a/placement-import/templates/placementapi/config/ssl.conf b/placement-import/templates/placementapi/config/ssl.conf new file mode 100644 index 000000000..e3da4ecb2 --- /dev/null +++ b/placement-import/templates/placementapi/config/ssl.conf @@ -0,0 +1,21 @@ + + SSLRandomSeed startup builtin + SSLRandomSeed startup file:/dev/urandom 512 + SSLRandomSeed connect builtin + SSLRandomSeed connect file:/dev/urandom 512 + + AddType application/x-x509-ca-cert .crt + AddType application/x-pkcs7-crl .crl + + SSLPassPhraseDialog builtin + SSLSessionCache "shmcb:/var/cache/mod_ssl/scache(512000)" + SSLSessionCacheTimeout 300 + Mutex default + SSLCryptoDevice builtin + SSLHonorCipherOrder On + SSLUseStapling Off + SSLStaplingCache "shmcb:/run/httpd/ssl_stapling(32768)" + SSLCipherSuite HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES + SSLProtocol all -SSLv2 -SSLv3 -TLSv1 + SSLOptions StdEnvVars + diff --git a/placement-import/test/functional/base_test.go b/placement-import/test/functional/base_test.go new file mode 100644 index 000000000..653c5605c --- /dev/null +++ b/placement-import/test/functional/base_test.go @@ -0,0 +1,221 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "fmt" + + . "github.com/onsi/gomega" //revive:disable:dot-imports + + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + "github.com/openstack-k8s-operators/placement-operator/internal/placement" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Names struct { + Namespace string + PlacementAPIName types.NamespacedName + ConfigMapName types.NamespacedName + DBSyncJobName types.NamespacedName + MariaDBDatabaseName types.NamespacedName + MariaDBAccount types.NamespacedName + DeploymentName types.NamespacedName + PublicServiceName types.NamespacedName + InternalServiceName types.NamespacedName + KeystoneServiceName types.NamespacedName + KeystoneEndpointName types.NamespacedName + ServiceAccountName types.NamespacedName + RoleName types.NamespacedName + RoleBindingName types.NamespacedName + CaBundleSecretName types.NamespacedName + InternalCertSecretName types.NamespacedName + PublicCertSecretName types.NamespacedName + PlacementAPITopologies []types.NamespacedName +} + +func CreateNames(placementAPIName types.NamespacedName) Names { + return Names{ + Namespace: placementAPIName.Namespace, + PlacementAPIName: placementAPIName, + ConfigMapName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: placementAPIName.Name + "-config-data"}, + DBSyncJobName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: placementAPIName.Name + "-db-sync"}, + MariaDBDatabaseName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: placement.DatabaseName}, + MariaDBAccount: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: AccountName}, + DeploymentName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: placementAPIName.Name}, + PublicServiceName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement-public"}, + InternalServiceName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement-internal"}, + KeystoneServiceName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement"}, + KeystoneEndpointName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement"}, + ServiceAccountName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement-" + placementAPIName.Name}, + RoleName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement-" + placementAPIName.Name + "-role"}, + RoleBindingName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: "placement-" + placementAPIName.Name + "-rolebinding"}, + CaBundleSecretName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: CABundleSecretName}, + InternalCertSecretName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: InternalCertSecretName}, + PublicCertSecretName: types.NamespacedName{ + Namespace: placementAPIName.Namespace, + Name: PublicCertSecretName}, + PlacementAPITopologies: []types.NamespacedName{ + { + Namespace: namespace, + Name: fmt.Sprintf("%s-topology", placementAPIName.Name), + }, + { + Namespace: namespace, + Name: fmt.Sprintf("%s-topology-alt", placementAPIName.Name), + }, + }, + } +} + +func GetDefaultPlacementAPISpec() map[string]any { + return map[string]any{ + "databaseInstance": "openstack", + "secret": SecretName, + "databaseAccount": AccountName, + } +} + +func GetTLSPlacementAPISpec(names Names) map[string]any { + return map[string]any{ + "databaseInstance": "openstack", + "replicas": 1, + "secret": SecretName, + "databaseAccount": AccountName, + "tls": map[string]any{ + "api": map[string]any{ + "internal": map[string]any{ + "secretName": names.InternalCertSecretName.Name, + }, + "public": map[string]any{ + "secretName": names.PublicCertSecretName.Name, + }, + }, + "caBundleSecretName": names.CaBundleSecretName.Name, + }, + } +} + +func CreatePlacementAPI(name types.NamespacedName, spec map[string]any) client.Object { + + raw := map[string]any{ + "apiVersion": "placement.openstack.org/v1beta1", + "kind": "PlacementAPI", + "metadata": map[string]any{ + "name": name.Name, + "namespace": name.Namespace, + }, + "spec": spec, + } + return th.CreateUnstructured(raw) +} + +func GetPlacementAPI(name types.NamespacedName) *placementv1.PlacementAPI { + instance := &placementv1.PlacementAPI{} + Eventually(func(g Gomega) { + g.Expect(k8sClient.Get(ctx, name, instance)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + return instance +} + +func CreatePlacementAPISecret(namespace string, name string) *corev1.Secret { + return th.CreateSecret( + types.NamespacedName{Namespace: namespace, Name: name}, + map[string][]byte{ + "PlacementPassword": []byte("12345678"), + "PlacementDatabasePassword": []byte("12345678"), + }, + ) +} + +func PlacementConditionGetter(name types.NamespacedName) condition.Conditions { + instance := GetPlacementAPI(name) + return instance.Status.Conditions +} + +// GetSampleTopologySpec - A sample (and opinionated) Topology Spec used to +// test PlacementAPI +// Note this is just an example that should not be used in production for +// multiple reasons: +// 1. It uses ScheduleAnyway as strategy, which is something we might +// want to avoid by default +// 2. Usually a topologySpreadConstraints is used to take care about +// multi AZ, which is not applicable in this context +func GetSampleTopologySpec(label string) (map[string]any, []corev1.TopologySpreadConstraint) { + // Build the topology Spec + topologySpec := map[string]any{ + "topologySpreadConstraints": []map[string]any{ + { + "maxSkew": 1, + "topologyKey": corev1.LabelHostname, + "whenUnsatisfiable": "ScheduleAnyway", + "labelSelector": map[string]any{ + "matchLabels": map[string]any{ + "service": placement.ServiceName, + "topology": label, + }, + }, + }, + }, + } + // Build the topologyObj representation + topologySpecObj := []corev1.TopologySpreadConstraint{ + { + MaxSkew: 1, + TopologyKey: corev1.LabelHostname, + WhenUnsatisfiable: corev1.ScheduleAnyway, + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "service": placement.ServiceName, + "topology": label, + }, + }, + }, + } + return topologySpec, topologySpecObj +} diff --git a/placement-import/test/functional/placementapi_controller_test.go b/placement-import/test/functional/placementapi_controller_test.go new file mode 100644 index 000000000..93da1e907 --- /dev/null +++ b/placement-import/test/functional/placementapi_controller_test.go @@ -0,0 +1,1529 @@ +/* +Copyright 2022. + +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 functional_test + +import ( + "fmt" + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + + //revive:disable-next-line:dot-imports + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + "github.com/openstack-k8s-operators/placement-operator/internal/placement" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("PlacementAPI controller", func() { + + BeforeEach(func() { + // lib-common uses OPERATOR_TEMPLATES env var to locate the "templates" + // directory of the operator. We need to set them othervise lib-common + // will fail to generate the ConfigMap as it does not find common.sh + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A PlacementAPI instance is created", func() { + BeforeEach(func() { + DeferCleanup( + th.DeleteInstance, + CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec()), + ) + }) + + It("should have the Spec fields defaulted", func() { + Placement := GetPlacementAPI(names.PlacementAPIName) + Expect(Placement.Spec.DatabaseInstance).Should(Equal("openstack")) + Expect(Placement.Spec.DatabaseAccount).Should(Equal(AccountName)) + Expect(Placement.Spec.ServiceUser).Should(Equal("placement")) + Expect(*(Placement.Spec.Replicas)).Should(Equal(int32(1))) + }) + + It("should have the Status fields initialized", func() { + Placement := GetPlacementAPI(names.PlacementAPIName) + Expect(Placement.Status.Hash).To(BeEmpty()) + Expect(Placement.Status.DatabaseHostname).To(Equal("")) + Expect(Placement.Status.ReadyCount).To(Equal(int32(0))) + }) + + It("should have a finalizer", func() { + // the reconciler loop adds the finalizer so we have to wait for + // it to run + Eventually(func() []string { + return GetPlacementAPI(names.PlacementAPIName).Finalizers + }, timeout, interval).Should(ContainElement("openstack.org/placementapi")) + }) + + It("should not create a config map", func() { + th.AssertConfigMapDoesNotExist(names.ConfigMapName) + }) + + It("should have input not ready and unknown Conditions initialized", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.RoleBindingReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.RoleReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceAccountReadyCondition, + corev1.ConditionTrue, + ) + unknownConditions := []condition.Type{ + condition.DBReadyCondition, + condition.DBSyncReadyCondition, + condition.CreateServiceReadyCondition, + condition.ServiceConfigReadyCondition, + condition.DeploymentReadyCondition, + condition.KeystoneServiceReadyCondition, + condition.KeystoneEndpointReadyCondition, + condition.NetworkAttachmentsReadyCondition, + condition.TLSInputReadyCondition, + } + + placement := GetPlacementAPI(names.PlacementAPIName) + // +5 as InputReady, Ready, Service and Role are ready is False asserted above + Expect(placement.Status.Conditions).To(HaveLen(len(unknownConditions) + 5)) + + for _, cond := range unknownConditions { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + cond, + corev1.ConditionUnknown, + ) + } + }) + }) + + When("starts zero replicas", func() { + BeforeEach(func() { + spec := GetDefaultPlacementAPISpec() + spec["replicas"] = 0 + DeferCleanup( + th.DeleteInstance, + CreatePlacementAPI(names.PlacementAPIName, spec), + ) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + }) + + It("and deployment is Ready", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + placement := GetPlacementAPI(names.PlacementAPIName) + Expect(*(placement.Spec.Replicas)).Should(Equal(int32(0))) + Expect(placement.Status.ReadyCount).Should(Equal(int32(0))) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("a secret is provided with missing fields", func() { + BeforeEach(func() { + DeferCleanup( + th.DeleteInstance, + CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec()), + ) + DeferCleanup( + k8sClient.Delete, ctx, + th.CreateSecret( + types.NamespacedName{Namespace: namespace, Name: SecretName}, + map[string][]byte{}), + ) + }) + It("reports that input is not ready", func() { + // FIXME(gibi): This is a bug as placement controller does not + // check the content of the Secret so eventually a dbsync job is + // created with incorrect config + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.InputReadyCondition, + corev1.ConditionFalse, + ) + }) + }) + + When("the proper secret is provided", func() { + BeforeEach(func() { + DeferCleanup( + th.DeleteInstance, + CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec()), + ) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + db := mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Spec.Name).To(Equal(names.MariaDBDatabaseName.Name)) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + }) + + It("should have input ready", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.InputReadyCondition, + corev1.ConditionTrue, + ) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("should not create a config map", func() { + th.AssertConfigMapDoesNotExist(names.ConfigMapName) + }) + }) + + When("keystoneAPI instance is available", func() { + var keystoneAPI *keystonev1.KeystoneAPI + + BeforeEach(func() { + spec := GetDefaultPlacementAPISpec() + spec["customServiceConfig"] = "foo = bar" + spec["defaultConfigOverwrite"] = map[string]any{ + "policy.yaml": "\"placement:resource_providers:list\": \"!\"", + } + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + keystoneAPI = keystone.GetKeystoneAPI(keystoneAPIName) + // Set region on KeystoneAPI to ensure GetRegion() returns a value + Eventually(func(g Gomega) { + ks := keystone.GetKeystoneAPI(keystoneAPIName) + ks.Spec.Region = "RegionOne" + g.Expect(k8sClient.Update(ctx, ks)).To(Succeed()) + ks.Status.Region = "RegionOne" + g.Expect(k8sClient.Status().Update(ctx, ks)).To(Succeed()) + }, timeout, interval).Should(Succeed()) + // Refresh keystoneAPI after setting region + keystoneAPI = keystone.GetKeystoneAPI(keystoneAPIName) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + }) + + It("creates MariaDB database", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DBReadyCondition, + corev1.ConditionFalse, + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + db := mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Spec.Name).To(Equal(names.MariaDBDatabaseName.Name)) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DBReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should have config ready", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + db := mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Spec.Name).To(Equal(names.MariaDBDatabaseName.Name)) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceConfigReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("should create a configuration Secret", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + cm := th.GetSecret(names.ConfigMapName) + + conf := cm.Data["placement.conf"] + Expect(conf).Should( + ContainSubstring("auth_url = %s", keystoneAPI.Status.APIEndpoints["internal"])) + Expect(conf).Should( + ContainSubstring("www_authenticate_uri = %s", keystoneAPI.Status.APIEndpoints["public"])) + Expect(conf).Should( + ContainSubstring("username = placement")) + Expect(conf).Should( + ContainSubstring("password = 12345678")) + + mariadbAccount := mariadb.GetMariaDBAccount(names.MariaDBAccount) + mariadbSecret := th.GetSecret(types.NamespacedName{Name: mariadbAccount.Spec.Secret, Namespace: names.PlacementAPIName.Namespace}) + + Expect(string(conf)).Should( + ContainSubstring(fmt.Sprintf("connection = mysql+pymysql://%s:%s@hostname-for-openstack.%s.svc/placement?read_default_file=/etc/my.cnf", + mariadbAccount.Spec.UserName, mariadbSecret.Data[mariadbv1.DatabasePasswordSelector], namespace))) + + custom := cm.Data["custom.conf"] + Expect(custom).Should(ContainSubstring("foo = bar")) + + policy := cm.Data["policy.yaml"] + Expect(policy).Should( + ContainSubstring("\"placement:resource_providers:list\": \"!\"")) + + myCnf := cm.Data["my.cnf"] + Expect(myCnf).To( + ContainSubstring("[client]\nssl=0")) + configData := cm.Data["httpd.conf"] + Expect(configData).Should( + ContainSubstring("TimeOut 60")) + }) + + It("should set region_name in keystone_authtoken section", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + cm := th.GetSecret(names.ConfigMapName) + conf := string(cm.Data["placement.conf"]) + + // Verify region_name is set in [keystone_authtoken] section + // GetRegion() returns Status.Region, so check that + Expect(keystoneAPI.Status.Region).ToNot(BeEmpty(), "KeystoneAPI should have a region set in status") + // The region_name should appear in the [keystone_authtoken] section (before [oslo_policy]) + Expect(conf).Should( + MatchRegexp(fmt.Sprintf( + `\[keystone_authtoken\][\s\S]*region_name = %s[\s\S]*\[oslo_policy\]`, keystoneAPI.Status.Region))) + }) + + It("creates service account, role and rolebindig", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ServiceAccountReadyCondition, + corev1.ConditionTrue, + ) + sa := th.GetServiceAccount(names.ServiceAccountName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.RoleReadyCondition, + corev1.ConditionTrue, + ) + role := th.GetRole(names.RoleName) + Expect(role.Rules).To(HaveLen(2)) + Expect(role.Rules[0].Resources).To(Equal([]string{"securitycontextconstraints"})) + Expect(role.Rules[1].Resources).To(Equal([]string{"pods"})) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.RoleBindingReadyCondition, + corev1.ConditionTrue, + ) + binding := th.GetRoleBinding(names.RoleBindingName) + Expect(binding.RoleRef.Name).To(Equal(role.Name)) + Expect(binding.Subjects).To(HaveLen(1)) + Expect(binding.Subjects[0].Name).To(Equal(sa.Name)) + }) + + It("creates keystone service", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneServiceReadyCondition, + corev1.ConditionUnknown, + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneServiceReadyCondition, + corev1.ConditionTrue, + ) + }) + It("creates keystone endpoint", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionUnknown, + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + }) + It("runs db sync", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DBSyncReadyCondition, + corev1.ConditionFalse, + ) + + job := th.GetJob(names.DBSyncJobName) + Expect(job.Spec.Template.Spec.Volumes).To(HaveLen(3)) + Expect(job.Spec.Template.Spec.Containers).To(HaveLen(1)) + + container := job.Spec.Template.Spec.Containers[0] + Expect(container.VolumeMounts).To(HaveLen(4)) + Expect(container.Image).To(Equal("quay.io/podified-antelope-centos9/openstack-placement-api:current-podified")) + + th.SimulateJobSuccess(names.DBSyncJobName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DBSyncReadyCondition, + corev1.ConditionTrue, + ) + }) + It("creates deployment", func() { + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateJobSuccess(names.DBSyncJobName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionUnknown, + ) + + deployment := th.GetDeployment(names.DeploymentName) + Expect(int(*deployment.Spec.Replicas)).To(Equal(1)) + Expect(deployment.Spec.Selector.MatchLabels).To(Equal(map[string]string{"service": "placement", "owner": names.PlacementAPIName.Name})) + Expect(deployment.Spec.Template.Spec.ServiceAccountName).To(Equal(names.ServiceAccountName.Name)) + Expect(deployment.Spec.Template.Spec.Containers).To(HaveLen(2)) + + th.SimulateDeploymentReplicaReady(names.DeploymentName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + }) + It("exposes the service", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.CreateServiceReadyCondition, + corev1.ConditionUnknown, + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + + public := th.GetService(names.PublicServiceName) + Expect(public.Labels["service"]).To(Equal("placement")) + internal := th.GetService(names.InternalServiceName) + Expect(internal.Labels["service"]).To(Equal("placement")) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.CreateServiceReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("reports ready when successfully deployed", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("Deployment rollout is progressing", func() { + BeforeEach(func() { + spec := GetDefaultPlacementAPISpec() + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentProgressing(names.DeploymentName) + }) + + It("shows the deployment progressing in DeploymentReadyCondition", func() { + th.ExpectConditionWithDetails( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + condition.RequestedReason, + condition.DeploymentReadyRunningMessage, + ) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("still shows the deployment progressing in DeploymentReadyCondition when rollout hits ProgressDeadlineExceeded", func() { + th.SimulateDeploymentProgressDeadlineExceeded(names.DeploymentName) + th.ExpectConditionWithDetails( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + condition.RequestedReason, + condition.DeploymentReadyRunningMessage, + ) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + ) + }) + + It("reaches Ready when deployment rollout finished", func() { + th.ExpectConditionWithDetails( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + condition.RequestedReason, + condition.DeploymentReadyRunningMessage, + ) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionFalse, + ) + + th.SimulateDeploymentReplicaReady(names.DeploymentName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.DeploymentReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("A PlacementAPI is created with service override", func() { + BeforeEach(func() { + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + + spec := GetDefaultPlacementAPISpec() + serviceOverride := map[string]any{} + serviceOverride["internal"] = map[string]any{ + "metadata": map[string]map[string]string{ + "annotations": { + "dnsmasq.network.openstack.org/hostname": "placement-internal.openstack.svc", + "metallb.universe.tf/address-pool": "osp-internalapi", + "metallb.universe.tf/allow-shared-ip": "osp-internalapi", + "metallb.universe.tf/loadBalancerIPs": "internal-lb-ip-1,internal-lb-ip-2", + }, + "labels": { + "internal": "true", + "service": "placement", + }, + }, + "spec": map[string]any{ + "type": "LoadBalancer", + }, + } + + spec["override"] = map[string]any{ + "service": serviceOverride, + } + + placementAPI := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetPlacementAPI(names.PlacementAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateLoadBalancerServiceIP(types.NamespacedName{Namespace: namespace, Name: "placement-internal"}) + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placementAPI) + }) + + It("creates KeystoneEndpoint", func() { + keystoneEndpoint := keystone.GetKeystoneEndpoint(names.KeystoneEndpointName) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", "http://placement-public."+namespace+".svc:8778")) + Expect(endpoints).To(HaveKeyWithValue("internal", "http://placement-internal."+namespace+".svc:8778")) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("creates LoadBalancer service", func() { + // As the internal endpoint is configured in ExternalEndpoints it + // gets a LoadBalancer Service with MetalLB annotations + service := th.GetService(types.NamespacedName{Namespace: namespace, Name: "placement-internal"}) + Expect(service.Annotations).To( + HaveKeyWithValue("dnsmasq.network.openstack.org/hostname", "placement-internal.openstack.svc")) + Expect(service.Annotations).To( + HaveKeyWithValue("metallb.universe.tf/address-pool", "osp-internalapi")) + Expect(service.Annotations).To( + HaveKeyWithValue("metallb.universe.tf/allow-shared-ip", "osp-internalapi")) + Expect(service.Annotations).To( + HaveKeyWithValue("metallb.universe.tf/loadBalancerIPs", "internal-lb-ip-1,internal-lb-ip-2")) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + When("A PlacementAPI is created with service override endpointURL set", func() { + BeforeEach(func() { + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + + spec := GetDefaultPlacementAPISpec() + serviceOverride := map[string]any{} + serviceOverride["public"] = map[string]any{ + "endpointURL": "http://placement-openstack.apps-crc.testing", + } + + spec["override"] = map[string]any{ + "service": serviceOverride, + } + + placementAPI := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetPlacementAPI(names.PlacementAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placementAPI) + }) + + It("creates KeystoneEndpoint", func() { + keystoneEndpoint := keystone.GetKeystoneEndpoint(names.KeystoneEndpointName) + endpoints := keystoneEndpoint.Spec.Endpoints + Expect(endpoints).To(HaveKeyWithValue("public", "http://placement-openstack.apps-crc.testing")) + Expect(endpoints).To(HaveKeyWithValue("internal", "http://placement-internal."+namespace+".svc:8778")) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.KeystoneEndpointReadyCondition, + corev1.ConditionTrue, + ) + }) + }) + + Context("PlacementAPI is fully deployed", func() { + keystoneAPIName := types.NamespacedName{} + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec())) + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + keystoneAPIName = keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("removes the finalizers when deleted", func() { + placement := GetPlacementAPI(names.PlacementAPIName) + Expect(placement.Finalizers).To(ContainElement("openstack.org/placementapi")) + keystoneService := keystone.GetKeystoneService(names.KeystoneServiceName) + Expect(keystoneService.Finalizers).To(ContainElement("openstack.org/placementapi")) + keystoneEndpoint := keystone.GetKeystoneService(names.KeystoneEndpointName) + Expect(keystoneEndpoint.Finalizers).To(ContainElement("openstack.org/placementapi")) + db := mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Finalizers).To(ContainElement("openstack.org/placementapi")) + acc := mariadb.GetMariaDBAccount(names.MariaDBAccount) + Expect(acc.Finalizers).To(ContainElement("openstack.org/placementapi")) + + th.DeleteInstance(GetPlacementAPI(names.PlacementAPIName)) + + keystoneService = keystone.GetKeystoneService(names.KeystoneServiceName) + Expect(keystoneService.Finalizers).NotTo(ContainElement("openstack.org/placementapi")) + keystoneEndpoint = keystone.GetKeystoneService(names.KeystoneEndpointName) + Expect(keystoneEndpoint.Finalizers).NotTo(ContainElement("openstack.org/placementapi")) + db = mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Finalizers).NotTo(ContainElement("openstack.org/placementapi")) + acc = mariadb.GetMariaDBAccount(names.MariaDBAccount) + Expect(acc.Finalizers).NotTo(ContainElement("openstack.org/placementapi")) + }) + + It("updates the deployment if configuration changes", func() { + deployment := th.GetDeployment(names.DeploymentName) + oldConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + Expect(oldConfigHash).NotTo(Equal("")) + cm := th.GetSecret(names.ConfigMapName) + Expect(cm.Data["custom.conf"]).ShouldNot(ContainSubstring("debug")) + + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + placement.Spec.CustomServiceConfig = "[DEFAULT]/ndebug = true" + + g.Expect(k8sClient.Update(ctx, placement)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + deployment := th.GetDeployment(names.DeploymentName) + newConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + g.Expect(newConfigHash).NotTo(Equal("")) + g.Expect(newConfigHash).NotTo(Equal(oldConfigHash)) + + cm := th.GetSecret(names.ConfigMapName) + g.Expect(cm.Data["custom.conf"]).Should(ContainSubstring("debug = true")) + }, timeout, interval).Should(Succeed()) + }) + + It("updates the deployment if password changes", func() { + deployment := th.GetDeployment(names.DeploymentName) + oldConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + Expect(oldConfigHash).NotTo(Equal("")) + + th.UpdateSecret( + types.NamespacedName{Namespace: namespace, Name: SecretName}, + "PlacementPassword", []byte("foobar")) + + logger.Info("Reconfigured") + + Eventually(func(g Gomega) { + deployment := th.GetDeployment(names.DeploymentName) + newConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + g.Expect(newConfigHash).NotTo(Equal(oldConfigHash)) + // TODO(gibi): once the password is in the generated config + // assert it there + }, timeout, interval).Should(Succeed()) + }) + + It("updates the KeystoneAuthURL if keystone internal endpoint changes", func() { + deployment := th.GetDeployment(names.DeploymentName) + oldConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + Expect(oldConfigHash).NotTo(Equal("")) + + newInternalEndpoint := "https://keystone-internal" + + keystone.UpdateKeystoneAPIEndpoint(keystoneAPIName, "internal", newInternalEndpoint) + logger.Info("Reconfigured") + + Eventually(func(g Gomega) { + deployment := th.GetDeployment(names.DeploymentName) + newConfigHash := GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + g.Expect(newConfigHash).NotTo(Equal(oldConfigHash)) + }, timeout, interval).Should(Succeed()) + + cm := th.GetSecret(names.ConfigMapName) + conf := cm.Data["placement.conf"] + Expect(conf).Should( + ContainSubstring("auth_url = %s", newInternalEndpoint)) + }) + }) + + When("A PlacementAPI is created with TLS", func() { + BeforeEach(func() { + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(names.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(names.InternalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(names.PublicCertSecretName)) + + spec := GetTLSPlacementAPISpec(names) + placement := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup(th.DeleteInstance, placement) + + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBTLSDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placement) + }) + + It("it creates deployment with CA and service certs mounted", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionTrue, + ) + + j := th.GetDeployment(names.DeploymentName) + + container := j.Spec.Template.Spec.Containers[0] + + // CA bundle + th.AssertVolumeExists(names.CaBundleSecretName.Name, j.Spec.Template.Spec.Volumes) + th.AssertVolumeMountExists(names.CaBundleSecretName.Name, "tls-ca-bundle.pem", j.Spec.Template.Spec.Containers[0].VolumeMounts) + + // service certs + th.AssertVolumeExists(names.InternalCertSecretName.Name, j.Spec.Template.Spec.Volumes) + th.AssertVolumeExists(names.PublicCertSecretName.Name, j.Spec.Template.Spec.Volumes) + th.AssertVolumeMountExists(names.PublicCertSecretName.Name, "tls.key", j.Spec.Template.Spec.Containers[0].VolumeMounts) + th.AssertVolumeMountExists(names.PublicCertSecretName.Name, "tls.crt", j.Spec.Template.Spec.Containers[0].VolumeMounts) + th.AssertVolumeMountExists(names.InternalCertSecretName.Name, "tls.key", j.Spec.Template.Spec.Containers[0].VolumeMounts) + th.AssertVolumeMountExists(names.InternalCertSecretName.Name, "tls.crt", j.Spec.Template.Spec.Containers[0].VolumeMounts) + + Expect(container.ReadinessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + Expect(container.LivenessProbe.HTTPGet.Scheme).To(Equal(corev1.URISchemeHTTPS)) + + configDataMap := th.GetSecret(names.ConfigMapName) + Expect(configDataMap).ShouldNot(BeNil()) + Expect(configDataMap.Data).Should(HaveKey("httpd.conf")) + Expect(configDataMap.Data).Should(HaveKey("ssl.conf")) + configData := string(configDataMap.Data["httpd.conf"]) + Expect(configData).Should(ContainSubstring("SSLEngine on")) + Expect(configData).Should(ContainSubstring("SSLCertificateFile \"/etc/pki/tls/certs/internal.crt\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateKeyFile \"/etc/pki/tls/private/internal.key\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateFile \"/etc/pki/tls/certs/public.crt\"")) + Expect(configData).Should(ContainSubstring("SSLCertificateKeyFile \"/etc/pki/tls/private/public.key\"")) + + configData = string(configDataMap.Data["my.cnf"]) + Expect(configData).To( + ContainSubstring("[client]\nssl-ca=/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem\nssl=1")) + }) + }) + + When("A PlacementAPI is created with a wrong topologyref", func() { + BeforeEach(func() { + spec := GetDefaultPlacementAPISpec() + spec["topologyRef"] = map[string]any{ + "name": "foo", + } + placement := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup(th.DeleteInstance, placement) + }) + + It("points to a non existing topology CR", func() { + // Reconciliation does not succeed because TopologyReadyCondition + // is not marked as True + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionFalse, + ) + // TopologyReadyCondition is Unknown as it waits for the Topology + // CR to be available + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.TopologyReadyCondition, + corev1.ConditionUnknown, + ) + }) + }) + When("A PlacementAPI is created with topologyref", func() { + var topologyRef, topologyRefAlt *topologyv1.TopoRef + BeforeEach(func() { + // Define the two topology references used in this test + topologyRef = &topologyv1.TopoRef{ + Name: names.PlacementAPITopologies[0].Name, + Namespace: names.PlacementAPITopologies[0].Namespace, + } + topologyRefAlt = &topologyv1.TopoRef{ + Name: names.PlacementAPITopologies[1].Name, + Namespace: names.PlacementAPITopologies[1].Namespace, + } + // Create Test Topologies + for _, t := range names.PlacementAPITopologies { + // Build the topology Spec + topologySpec, _ := GetSampleTopologySpec(t.Name) + infra.CreateTopology(t, topologySpec) + } + spec := GetDefaultPlacementAPISpec() + spec["topologyRef"] = map[string]any{ + "name": topologyRef.Name, + } + placement := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup(th.DeleteInstance, placement) + + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placement) + }) + + It("sets topology in CR status", func() { + Eventually(func(g Gomega) { + tp := infra.GetTopology(types.NamespacedName{ + Name: topologyRef.Name, + Namespace: topologyRef.Namespace, + }) + finalizers := tp.GetFinalizers() + g.Expect(finalizers).To(HaveLen(1)) + placement := GetPlacementAPI(names.PlacementAPIName) + g.Expect(placement.Status.LastAppliedTopology).ToNot(BeNil()) + g.Expect(placement.Status.LastAppliedTopology).To(Equal(topologyRef)) + g.Expect(finalizers).To(ContainElement( + fmt.Sprintf("openstack.org/placementapi-%s", names.PlacementAPIName.Name))) + }, timeout, interval).Should(Succeed()) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.TopologyReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("sets topology in resource specs", func() { + Eventually(func(g Gomega) { + _, topologySpecObj := GetSampleTopologySpec(topologyRef.Name) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.Affinity).To(BeNil()) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.TopologySpreadConstraints).ToNot(BeNil()) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.TopologySpreadConstraints).To(Equal(topologySpecObj)) + }, timeout, interval).Should(Succeed()) + }) + It("updates topology when the reference changes", func() { + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + placement.Spec.TopologyRef.Name = topologyRefAlt.Name + g.Expect(k8sClient.Update(ctx, placement)).To(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + tp := infra.GetTopology(types.NamespacedName{ + Name: topologyRefAlt.Name, + Namespace: topologyRefAlt.Namespace, + }) + finalizers := tp.GetFinalizers() + g.Expect(finalizers).To(HaveLen(1)) + placement := GetPlacementAPI(names.PlacementAPIName) + g.Expect(placement.Status.LastAppliedTopology).ToNot(BeNil()) + g.Expect(placement.Status.LastAppliedTopology).To(Equal(topologyRefAlt)) + g.Expect(finalizers).To(ContainElement( + fmt.Sprintf("openstack.org/placementapi-%s", names.PlacementAPIName.Name))) + // Verify the previous referenced topology has no finalizers + tp = infra.GetTopology(types.NamespacedName{ + Name: topologyRef.Name, + Namespace: topologyRef.Namespace, + }) + finalizers = tp.GetFinalizers() + g.Expect(finalizers).To(BeEmpty()) + }, timeout, interval).Should(Succeed()) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.TopologyReadyCondition, + corev1.ConditionTrue, + ) + }) + It("removes topologyRef from the spec", func() { + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + // Remove the TopologyRef from the existing Placement .Spec + placement.Spec.TopologyRef = nil + g.Expect(k8sClient.Update(ctx, placement)).To(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + g.Expect(placement.Status.LastAppliedTopology).Should(BeNil()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.TopologySpreadConstraints).To(BeNil()) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.Affinity).ToNot(BeNil()) + }, timeout, interval).Should(Succeed()) + + // Verify the existing topologies have no finalizer anymore + Eventually(func(g Gomega) { + for _, topology := range names.PlacementAPITopologies { + tp := infra.GetTopology(types.NamespacedName{ + Name: topology.Name, + Namespace: topology.Namespace, + }) + finalizers := tp.GetFinalizers() + g.Expect(finalizers).To(BeEmpty()) + } + }, timeout, interval).Should(Succeed()) + }) + }) + + When("A PlacementAPI is created with nodeSelector", func() { + BeforeEach(func() { + spec := GetDefaultPlacementAPISpec() + spec["nodeSelector"] = map[string]any{ + "foo": "bar", + } + + placement := CreatePlacementAPI(names.PlacementAPIName, spec) + DeferCleanup(th.DeleteInstance, placement) + + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placement) + }) + + It("sets nodeSelector in resource specs", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + }) + + It("updates nodeSelector in resource specs when changed", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + newNodeSelector := map[string]string{ + "foo2": "bar2", + } + placement.Spec.NodeSelector = &newNodeSelector + g.Expect(k8sClient.Update(ctx, placement)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo2": "bar2"})) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo2": "bar2"})) + }, timeout, interval).Should(Succeed()) + }) + + It("removes nodeSelector from resource specs when cleared", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + emptyNodeSelector := map[string]string{} + placement.Spec.NodeSelector = &emptyNodeSelector + g.Expect(k8sClient.Update(ctx, placement)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(BeNil()) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(BeNil()) + }, timeout, interval).Should(Succeed()) + }) + + It("removes nodeSelector from resource specs when nilled", func() { + Eventually(func(g Gomega) { + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(Equal(map[string]string{"foo": "bar"})) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + placement := GetPlacementAPI(names.PlacementAPIName) + placement.Spec.NodeSelector = nil + g.Expect(k8sClient.Update(ctx, placement)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + Eventually(func(g Gomega) { + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + g.Expect(th.GetDeployment(names.DeploymentName).Spec.Template.Spec.NodeSelector).To(BeNil()) + g.Expect(th.GetJob(names.DBSyncJobName).Spec.Template.Spec.NodeSelector).To(BeNil()) + }, timeout, interval).Should(Succeed()) + }) + }) + // Run MariaDBAccount suite tests. these are pre-packaged ginkgo tests + // that exercise standard account create / update patterns that should be + // common to all controllers that ensure MariaDBAccount CRs. + + mariadbSuite := &mariadb_test.MariaDBTestHarness{ + PopulateHarness: func(harness *mariadb_test.MariaDBTestHarness) { + harness.Setup( + "Placement", + names.PlacementAPIName.Namespace, + placement.DatabaseName, + "openstack.org/placementapi", + mariadb, timeout, interval, + ) + }, + + // Generate a fully running Keystone service given an accountName + // needs to make it all the way to the end where the mariadb finalizers + // are removed from unused accounts since that's part of what we are testing + SetupCR: func(accountName types.NamespacedName) { + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + spec := GetDefaultPlacementAPISpec() + spec["databaseAccount"] = accountName.Name + DeferCleanup( + th.DeleteInstance, + CreatePlacementAPI(names.PlacementAPIName, spec), + ) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + db := mariadb.GetMariaDBDatabase(names.MariaDBDatabaseName) + Expect(db.Spec.Name).To(Equal(names.MariaDBDatabaseName.Name)) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(accountName) + + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }, + // Change the account name in the service to a new name + UpdateAccount: func(newAccountName types.NamespacedName) { + + Eventually(func(g Gomega) { + placementapi := GetPlacementAPI(names.PlacementAPIName) + placementapi.Spec.DatabaseAccount = newAccountName.Name + g.Expect(th.K8sClient.Update(ctx, placementapi)).Should(Succeed()) + }, timeout, interval).Should(Succeed()) + + }, + SwitchToNewAccount: func() { + th.SimulateJobSuccess(names.DBSyncJobName) + + Eventually(func(g Gomega) { + th.SimulateDeploymentReplicaReady(names.DeploymentName) + placementapi := GetPlacementAPI(names.PlacementAPIName) + g.Expect(placementapi.Status.Conditions.Get(condition.DeploymentReadyCondition).Status).To( + Equal(corev1.ConditionTrue)) + + }, timeout, interval).Should(Succeed()) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }, + // delete the CR instance to exercise finalizer removal + DeleteCR: func() { + th.DeleteInstance(GetPlacementAPI(names.PlacementAPIName)) + }, + } + + mariadbSuite.RunBasicSuite() + + mariadbSuite.RunURLAssertSuite(func(_ types.NamespacedName, username string, password string) { + Eventually(func(g Gomega) { + cm := th.GetSecret(names.ConfigMapName) + + conf := cm.Data["placement.conf"] + + g.Expect(string(conf)).Should( + ContainSubstring(fmt.Sprintf("connection = mysql+pymysql://%s:%s@hostname-for-openstack.%s.svc/placement?read_default_file=/etc/my.cnf", + username, password, namespace))) + }, timeout, interval).Should(Succeed()) + + }) + + mariadbSuite.RunConfigHashSuite(func() string { + deployment := th.GetDeployment(names.DeploymentName) + return GetEnvVarValue(deployment.Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + }) + +}) + +var _ = Describe("PlacementAPI reconfiguration", func() { + BeforeEach(func() { + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("TLS certs are reconfigured", func() { + BeforeEach(func() { + + DeferCleanup(k8sClient.Delete, ctx, th.CreateCABundleSecret(names.CaBundleSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(names.InternalCertSecretName)) + DeferCleanup(k8sClient.Delete, ctx, th.CreateCertSecret(names.PublicCertSecretName)) + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, GetTLSPlacementAPISpec(names))) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + + spec := GetTLSPlacementAPISpec(names) + placement := CreatePlacementAPI(names.PlacementAPIName, spec) + + serviceSpec := corev1.ServiceSpec{Ports: []corev1.ServicePort{{Port: 3306}}} + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService(namespace, "openstack", serviceSpec), + ) + mariadb.SimulateMariaDBTLSDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + + th.SimulateJobSuccess(names.DBSyncJobName) + DeferCleanup(th.DeleteInstance, placement) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + + It("reconfigures the API pod", func() { + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.TLSInputReadyCondition, + corev1.ConditionTrue, + ) + + // Grab the current config hash + originalHash := GetEnvVarValue( + th.GetDeployment(names.DeploymentName).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + Expect(originalHash).NotTo(BeEmpty()) + + // Change the content of the CA secret + th.UpdateSecret(names.CaBundleSecretName, "tls-ca-bundle.pem", []byte("DifferentCAData")) + // Assert that the deployment is updated + Eventually(func(g Gomega) { + newHash := GetEnvVarValue( + th.GetDeployment(names.DeploymentName).Spec.Template.Spec.Containers[0].Env, "CONFIG_HASH", "") + g.Expect(newHash).NotTo(BeEmpty()) + g.Expect(newHash).NotTo(Equal(originalHash)) + }, timeout, interval).Should(Succeed()) + }) + + }) + + When("an ApplicationCredential is created for Placement", func() { + BeforeEach(func() { + DeferCleanup( + k8sClient.Delete, ctx, CreatePlacementAPISecret(names.Namespace, SecretName)) + keystoneAPIName := keystone.CreateKeystoneAPI(names.Namespace) + DeferCleanup(keystone.DeleteKeystoneAPI, keystoneAPIName) + + acSecretName := fmt.Sprintf("ac-%s-secret", placement.ServiceName) + acSecret := th.CreateSecret( + types.NamespacedName{Namespace: names.Namespace, Name: acSecretName}, + map[string][]byte{ + keystonev1.ACIDSecretKey: []byte("test-ac-id"), + keystonev1.ACSecretSecretKey: []byte("test-ac-secret"), + }, + ) + DeferCleanup(th.DeleteInstance, acSecret) + + spec := GetDefaultPlacementAPISpec() + spec["auth"] = map[string]any{ + "applicationCredentialSecret": acSecretName, + } + + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(names.PlacementAPIName, spec)) + + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + names.Namespace, + GetDefaultPlacementAPISpec()["databaseInstance"].(string), + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + }) + + It("should render ApplicationCredential auth in placement.conf", func() { + configSecret := th.GetSecret(names.ConfigMapName) + conf := configSecret.Data["placement.conf"] + + // AC auth is configured + Expect(conf).To(ContainSubstring("auth_type = v3applicationcredential")) + Expect(conf).To(ContainSubstring("application_credential_id = test-ac-id")) + Expect(conf).To(ContainSubstring("application_credential_secret = test-ac-secret")) + + // Password auth fields should not be present + Expect(conf).NotTo(ContainSubstring("auth_type = password")) + Expect(conf).NotTo(ContainSubstring("username =")) + Expect(conf).NotTo(ContainSubstring("password =")) + Expect(conf).NotTo(ContainSubstring("project_name =")) + Expect(conf).NotTo(ContainSubstring("user_domain_name =")) + Expect(conf).NotTo(ContainSubstring("project_domain_name =")) + }) + }) + +}) diff --git a/placement-import/test/functional/placementapi_webhook_test.go b/placement-import/test/functional/placementapi_webhook_test.go new file mode 100644 index 000000000..7e2aaa841 --- /dev/null +++ b/placement-import/test/functional/placementapi_webhook_test.go @@ -0,0 +1,219 @@ +/* +Copyright 2023. + +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 functional_test + +import ( + "errors" + "os" + + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + + //revive:disable-next-line:dot-imports + . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + + corev1 "k8s.io/api/core/v1" + k8s_errors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" +) + +var _ = Describe("PlacementAPI Webhook", func() { + + var placementAPIName types.NamespacedName + + BeforeEach(func() { + + placementAPIName = types.NamespacedName{ + Name: "placement", + Namespace: namespace, + } + + err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + Expect(err).NotTo(HaveOccurred()) + }) + + When("A PlacementAPI instance is created without container images", func() { + BeforeEach(func() { + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(placementAPIName, GetDefaultPlacementAPISpec())) + }) + + It("should have the defaults initialized by webhook", func() { + PlacementAPI := GetPlacementAPI(placementAPIName) + Expect(PlacementAPI.Spec.ContainerImage).Should(Equal( + placementv1.PlacementAPIContainerImage, + )) + }) + }) + + When("A PlacementAPI instance is created with container images", func() { + BeforeEach(func() { + placementAPISpec := GetDefaultPlacementAPISpec() + placementAPISpec["containerImage"] = "api-container-image" + DeferCleanup(th.DeleteInstance, CreatePlacementAPI(placementAPIName, placementAPISpec)) + }) + + It("should use the given values", func() { + PlacementAPI := GetPlacementAPI(placementAPIName) + Expect(PlacementAPI.Spec.ContainerImage).Should(Equal( + "api-container-image", + )) + }) + }) + + It("rejects PlacementAPI with wrong defaultConfigOverwrite", func() { + spec := GetDefaultPlacementAPISpec() + spec["defaultConfigOverwrite"] = map[string]any{ + "policy.yaml": "support", + "api-paste.ini": "not supported", + } + raw := map[string]any{ + "apiVersion": "placement.openstack.org/v1beta1", + "kind": "PlacementAPI", + "metadata": map[string]any{ + "name": placementAPIName.Name, + "namespace": placementAPIName.Namespace, + }, + "spec": spec, + } + unstructuredObj := &unstructured.Unstructured{Object: raw} + _, err := controllerutil.CreateOrPatch( + ctx, k8sClient, unstructuredObj, func() error { return nil }) + + Expect(err).Should(HaveOccurred()) + var statusError *k8s_errors.StatusError + Expect(errors.As(err, &statusError)).To(BeTrue()) + Expect(statusError.ErrStatus.Details.Kind).To(Equal("PlacementAPI")) + Expect(statusError.ErrStatus.Message).To( + ContainSubstring( + "invalid: spec.defaultConfigOverwrite: " + + "Invalid value: \"api-paste.ini\": " + + "Only the following keys are valid: policy.yaml", + ), + ) + }) + + It("rejects with wrong service override endpoint type", func() { + spec := GetDefaultPlacementAPISpec() + spec["override"] = map[string]any{ + "service": map[string]any{ + "internal": map[string]any{}, + "wrooong": map[string]any{}, + }, + } + + raw := map[string]any{ + "apiVersion": "placement.openstack.org/v1beta1", + "kind": "PlacementAPI", + "metadata": map[string]any{ + "name": placementAPIName.Name, + "namespace": placementAPIName.Namespace, + }, + "spec": spec, + } + + unstructuredObj := &unstructured.Unstructured{Object: raw} + _, err := controllerutil.CreateOrPatch( + th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring( + "invalid: spec.override.service[wrooong]: " + + "Invalid value: \"wrooong\": invalid endpoint type: wrooong"), + ) + }) + + When("A PlacementAPI instance is updated with wrong service override endpoint", func() { + BeforeEach(func() { + DeferCleanup(k8sClient.Delete, ctx, CreatePlacementAPISecret(namespace, SecretName)) + DeferCleanup(keystone.DeleteKeystoneAPI, keystone.CreateKeystoneAPI(namespace)) + + placementAPI := CreatePlacementAPI(names.PlacementAPIName, GetDefaultPlacementAPISpec()) + DeferCleanup( + mariadb.DeleteDBService, + mariadb.CreateDBService( + namespace, + GetPlacementAPI(names.PlacementAPIName).Spec.DatabaseInstance, + corev1.ServiceSpec{ + Ports: []corev1.ServicePort{{Port: 3306}}, + }, + ), + ) + + mariadb.SimulateMariaDBDatabaseCompleted(names.MariaDBDatabaseName) + mariadb.SimulateMariaDBAccountCompleted(names.MariaDBAccount) + th.SimulateJobSuccess(names.DBSyncJobName) + th.SimulateDeploymentReplicaReady(names.DeploymentName) + keystone.SimulateKeystoneServiceReady(names.KeystoneServiceName) + keystone.SimulateKeystoneEndpointReady(names.KeystoneEndpointName) + DeferCleanup(th.DeleteInstance, placementAPI) + + th.ExpectCondition( + names.PlacementAPIName, + ConditionGetterFunc(PlacementConditionGetter), + condition.ReadyCondition, + corev1.ConditionTrue, + ) + }) + It("rejects update with wrong service override endpoint type", func() { + PlacementAPI := GetPlacementAPI(names.PlacementAPIName) + Expect(PlacementAPI).NotTo(BeNil()) + if PlacementAPI.Spec.Override.Service == nil { + PlacementAPI.Spec.Override.Service = map[service.Endpoint]service.RoutedOverrideSpec{} + } + PlacementAPI.Spec.Override.Service["wrooong"] = service.RoutedOverrideSpec{} + err := k8sClient.Update(ctx, PlacementAPI) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring( + "invalid: spec.override.service[wrooong]: " + + "Invalid value: \"wrooong\": invalid endpoint type: wrooong"), + ) + }) + }) + + It("rejects a wrong TopologyRef on a different namespace", func() { + spec := GetDefaultPlacementAPISpec() + // Inject a topologyRef that points to a different namespace + spec["topologyRef"] = map[string]any{ + "name": "foo", + "namespace": "bar", + } + raw := map[string]any{ + "apiVersion": "placement.openstack.org/v1beta1", + "kind": "PlacementAPI", + "metadata": map[string]any{ + "name": placementAPIName.Name, + "namespace": placementAPIName.Namespace, + }, + "spec": spec, + } + unstructuredObj := &unstructured.Unstructured{Object: raw} + _, err := controllerutil.CreateOrPatch( + th.Ctx, th.K8sClient, unstructuredObj, func() error { return nil }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To( + ContainSubstring( + "spec.topologyRef.namespace: Invalid value: \"namespace\": Customizing namespace field is not supported"), + ) + }) +}) diff --git a/placement-import/test/functional/suite_test.go b/placement-import/test/functional/suite_test.go new file mode 100644 index 000000000..e243a6655 --- /dev/null +++ b/placement-import/test/functional/suite_test.go @@ -0,0 +1,253 @@ +/* +Copyright 2022. + +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 functional_test + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + "github.com/go-logr/logr" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" //revive:disable:dot-imports + . "github.com/onsi/gomega" //revive:disable:dot-imports + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "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" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + infra_test "github.com/openstack-k8s-operators/infra-operator/apis/test/helpers" + topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" + test "github.com/openstack-k8s-operators/lib-common/modules/test" + mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + controllers "github.com/openstack-k8s-operators/placement-operator/internal/controller" + webhookv1 "github.com/openstack-k8s-operators/placement-operator/internal/webhook/v1beta1" + + keystone_test "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" + common_test "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" + mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" + //+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 ( + cfg *rest.Config + k8sClient client.Client // You'll be using this client in your tests. + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + logger logr.Logger + th *common_test.TestHelper + keystone *keystone_test.TestHelper + mariadb *mariadb_test.TestHelper + infra *infra_test.TestHelper + namespace string + names Names +) + +const ( + timeout = time.Second * 10 + + SecretName = "test-osp-secret" + + AccountName = "test-placement-account" + + PublicCertSecretName = "public-tls-certs" // #nosec G101 + + InternalCertSecretName = "internal-tls-certs" // #nosec G101 + + CABundleSecretName = "combined-ca-bundle" // #nosec G101 + + interval = time.Millisecond * 200 +) + +func TestAPIs(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()) + + keystoneCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/keystone-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + mariaDBCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/mariadb-operator/api", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + topologyCRDs, err := test.GetCRDDirFromModule( + "github.com/openstack-k8s-operators/infra-operator/apis", "../../go.mod", "bases") + Expect(err).ShouldNot(HaveOccurred()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + // Increase this to 60 or 120 seconds for the single-core run + ControlPlaneStartTimeout: 120 * time.Second, + // Give it plenty of time to wind down (e.g., 60-120 seconds) + ControlPlaneStopTimeout: 120 * time.Second, + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + // NOTE(gibi): we need to list all the external CRDs our operator depends on + keystoneCRDs, + mariaDBCRDs, + topologyCRDs, + }, + ErrorIfCRDPathMissing: true, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + // NOTE(gibi): if localhost is resolved to ::1 (ipv6) then starting + // the webhook fails as it try to parse the address as ipv4 and + // failing on the colons in ::1 + LocalServingHost: "127.0.0.1", + }, + ControlPlane: envtest.ControlPlane{ + APIServer: &envtest.APIServer{ + Args: []string{ + "--service-cluster-ip-range=10.0.0.0/12", // 65k+ IPs + "--disable-admission-plugins=ResourceQuota,ServiceAccount,NamespaceLifecycle", + }, + }, + }, + } + + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + // NOTE(gibi): Need to add all API schemas our operator can own. + // Keep this in synch with PlacementAPIReconciler.SetupWithManager, + // otherwise the reconciler loop will silently not start + // in the test env. + err = placementv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = mariadbv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = keystonev1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + err = topologyv1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:scheme + + logger = ctrl.Log.WithName("---Test---") + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + th = common_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(th).NotTo(BeNil()) + keystone = keystone_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(keystone).NotTo(BeNil()) + mariadb = mariadb_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(mariadb).NotTo(BeNil()) + infra = infra_test.NewTestHelper(ctx, k8sClient, timeout, interval, logger) + Expect(infra).NotTo(BeNil()) + + // Start the controller-manager if goroutine + webhookInstallOptions := &testEnv.WebhookInstallOptions + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + Metrics: metricsserver.Options{ + BindAddress: "0", + }, + WebhookServer: webhook.NewServer( + webhook.Options{ + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + }), + LeaderElection: false, + }) + Expect(err).ToNot(HaveOccurred()) + + kclient, err := kubernetes.NewForConfig(cfg) + Expect(err).ToNot(HaveOccurred(), "failed to create kclient") + + err = webhookv1.SetupPlacementAPIWebhookWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + placementv1.SetupDefaults() + + err = (&controllers.PlacementAPIReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Kclient: kclient, + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + + go func() { + defer GinkgoRecover() + err = k8sManager.Start(ctx) + Expect(err).ToNot(HaveOccurred(), "failed to run manager") + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Duration(10) * time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) // #nosec G402 + if err != nil { + return err + } + _ = conn.Close() // Ignore close error in tests + return nil + }).Should(Succeed()) +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) + +var _ = BeforeEach(func() { + // NOTE(gibi): We need to create a unique namespace for each test run + // as namespaces cannot be deleted in a locally running envtest. See + // https://book.kubebuilder.io/reference/envtest.html#namespace-usage-limitation + namespace = uuid.New().String() + th.CreateNamespace(namespace) + // We still request the delete of the Namespace to properly cleanup if + // we run the test in an existing cluster. + DeferCleanup(th.DeleteNamespace, namespace) + + placementAPIName := types.NamespacedName{ + Namespace: namespace, + Name: uuid.New().String()[:25], + } + + names = CreateNames(placementAPIName) +}) diff --git a/placement-import/test/kuttl/common/assert_sample_deployment.yaml b/placement-import/test/kuttl/common/assert_sample_deployment.yaml new file mode 100644 index 000000000..197258bc9 --- /dev/null +++ b/placement-import/test/kuttl/common/assert_sample_deployment.yaml @@ -0,0 +1,287 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +spec: + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + passwordSelectors: + service: PlacementPassword + preserveJobs: false + replicas: 1 + secret: osp-secret + serviceUser: placement +status: + databaseHostname: openstack.placement-kuttl-tests.svc + readyCount: 1 + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Create service completed + reason: Ready + status: "True" + type: CreateServiceReady + - message: DB create completed + reason: Ready + status: "True" + type: DBReady + - message: DBsync completed + reason: Ready + status: "True" + type: DBSyncReady + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Setup complete + reason: Ready + status: "True" + type: KeystoneEndpointReady + - message: Setup complete + reason: Ready + status: "True" + type: KeystoneServiceReady + - message: MariaDBAccount creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady + - message: NetworkAttachments completed + reason: Ready + status: "True" + type: NetworkAttachmentsReady + - message: RoleBinding created + reason: Ready + status: "True" + type: RoleBindingReady + - message: Role created + reason: Ready + status: "True" + type: RoleReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Service config create completed + reason: Ready + status: "True" + type: ServiceConfigReady + - message: Input data complete + reason: Ready + status: "True" + type: TLSInputReady +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +spec: + replicas: 1 + template: + metadata: + labels: + service: placement + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: service + operator: In + values: + - placement + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - args: + - --single-child + - -- + - /usr/bin/tail + - -n+1 + - -F + - /var/log/placement/placement-api.log + command: + - /usr/bin/dumb-init + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + name: placement-log + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + resources: {} + securityContext: + runAsUser: 42482 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /usr/local/bin/container-scripts + name: scripts + readOnly: true + - mountPath: /var/log/placement + name: logs + - mountPath: /var/lib/openstack/config + name: config-data + - mountPath: /var/lib/kolla/config_files/config.json + name: config-data + readOnly: true + subPath: placement-api-config.json + - args: + - -c + - /usr/local/bin/kolla_start + command: + - /bin/bash + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + name: placement-api + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + resources: {} + restartPolicy: Always + securityContext: {} + serviceAccount: placement-placement + serviceAccountName: placement-placement +status: + availableReplicas: 1 + replicas: 1 +--- +# the openshift annotations can't be checked through the deployment above +apiVersion: v1 +kind: Pod +metadata: + annotations: + openshift.io/scc: anyuid + labels: + service: placement +status: + phase: Running +--- +apiVersion: v1 +kind: Service +metadata: + labels: + endpoint: internal + service: placement + name: placement-internal +spec: + ports: + - name: placement-internal + selector: + service: placement + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + endpoint: public + service: placement + name: placement-public +spec: + ports: + - name: placement-public + selector: + service: placement + type: ClusterIP +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + placement.openstack.org/name: placement + name: placement-config-data + ownerReferences: + - blockOwnerDeletion: true + controller: true + kind: PlacementAPI + name: placement +--- +apiVersion: keystone.openstack.org/v1beta1 +kind: KeystoneEndpoint +metadata: + name: placement + ownerReferences: + - apiVersion: placement.openstack.org/v1beta1 + blockOwnerDeletion: true + controller: true + kind: PlacementAPI + name: placement +--- +# the actual addresses of the api endpoints are platform specific, so we can't rely on +# kuttl asserts to check them. This short script gathers the addresses and checks that +# the two endpoints are defined and their addresses follow the default pattern +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +namespaced: true +commands: + - script: | + . $PLACEMENT_KUTTL_DIR/../lib/helper_functions.sh + apiEndpoints=$(oc get -n $NAMESPACE KeystoneEndpoint placement -o go-template-file=$PLACEMENT_KUTTL_DIR/../go_templates/apiEndpoints.gotemplate) + assert_regex $apiEndpoints 'http:\/\/placement-internal\..+\.svc.*' + assert_regex $apiEndpoints 'http:\/\/placement-public\..+\.svc.*' + + # when using image digests the containerImage URLs are SHA's so we verify them with a script + tupleTemplate='{{ range (index .spec.template.spec.containers 0).env }}{{ .name }}{{ "#" }}{{ .value}}{{"\n"}}{{ end }}' + imageTuples=$(oc get -n openstack-operators deployment placement-operator-controller-manager -o go-template="$tupleTemplate") + # format of imageTuple is: RELATED_IMAGE_PLACEMENT_# separated by newlines + for ITEM in $(echo $imageTuples); do + # it is an image + if echo $ITEM | grep 'RELATED_IMAGE' &> /dev/null; then + NAME=$(echo $ITEM | sed -e 's|^RELATED_IMAGE_PLACEMENT_\([^_]*\)_.*|\1|') + IMG_FROM_ENV=$(echo $ITEM | sed -e 's|^.*#\(.*\)|\1|') + template='{{.spec.containerImage}}' + case $NAME in + API) + SERVICE_IMAGE=$(oc get -n $NAMESPACE placementapi placement -o go-template="$template") + ;; + esac + if [ "$SERVICE_IMAGE" != "$IMG_FROM_ENV" ]; then + echo "$NAME image does not equal $VALUE" + exit 1 + fi + fi + done diff --git a/placement-import/test/kuttl/common/cleanup-placement.yaml b/placement-import/test/kuttl/common/cleanup-placement.yaml new file mode 100644 index 000000000..06857f7da --- /dev/null +++ b/placement-import/test/kuttl/common/cleanup-placement.yaml @@ -0,0 +1,6 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +delete: +- apiVersion: placement.openstack.org/v1beta1 + kind: PlacementAPI + name: placement diff --git a/placement-import/test/kuttl/common/errors_cleanup_placement.yaml b/placement-import/test/kuttl/common/errors_cleanup_placement.yaml new file mode 100644 index 000000000..4e0a769b6 --- /dev/null +++ b/placement-import/test/kuttl/common/errors_cleanup_placement.yaml @@ -0,0 +1,94 @@ +# +# Check for: +# +# No PlacementAPI CR +# No Deployment for PlacementAPI CR +# No Pods in placement Deployment +# No Placement Services +# +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +--- +# the openshift annotations can't be checked through the deployment above +apiVersion: v1 +kind: Pod +metadata: + annotations: + openshift.io/scc: anyuid + labels: + service: placement +--- +apiVersion: v1 +kind: Service +metadata: + labels: + admin: "true" + service: placement + name: placement-admin +spec: + ports: + - name: placement-admin + selector: + service: placement + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + endpoint: internal + service: placement + name: placement-internal +spec: + ports: + - name: placement-internal + selector: + service: placement + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + labels: + endpoint: public + service: placement + name: placement-public +spec: + ports: + - name: placement-public + selector: + service: placement + type: ClusterIP +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + placement.openstack.org/name: placement + name: placement-scripts + ownerReferences: + - blockOwnerDeletion: true + controller: true + kind: PlacementAPI + name: placement +--- +apiVersion: v1 +kind: Secret +metadata: + labels: + placement.openstack.org/name: placement + name: placement-config-data + ownerReferences: + - blockOwnerDeletion: true + controller: true + kind: PlacementAPI + name: placement diff --git a/placement-import/test/kuttl/common/patch_placement_deploy.yaml b/placement-import/test/kuttl/common/patch_placement_deploy.yaml new file mode 100644 index 000000000..5647c771b --- /dev/null +++ b/placement-import/test/kuttl/common/patch_placement_deploy.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + oc patch placementapi -n $NAMESPACE placement --type='json' -p='[{"op": "replace", "path": "/spec/secret", "value":"osp-secret"}]' diff --git a/placement-import/test/kuttl/common/placementapi_deploy.yaml b/placement-import/test/kuttl/common/placementapi_deploy.yaml new file mode 120000 index 000000000..1302e1646 --- /dev/null +++ b/placement-import/test/kuttl/common/placementapi_deploy.yaml @@ -0,0 +1 @@ +../../../config/samples/placement_v1beta1_placementapi.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/common/tls_certificates.yaml b/placement-import/test/kuttl/common/tls_certificates.yaml new file mode 100644 index 000000000..76e826947 --- /dev/null +++ b/placement-import/test/kuttl/common/tls_certificates.yaml @@ -0,0 +1,31 @@ +# Hardcoded certs secret, so kuttl doesn't require cert-manager at test runtime +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle + labels: + service: placement +data: + tls-ca-bundle.pem: IyByb290Y2EtaW50ZXJuYWwKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJmekNDQVNhZ0F3SUJBZ0lRUWxlcTNZcDBtU2kwVDNiTm03Q29UVEFLQmdncWhrak9QUVFEQWpBZ01SNHcKSEFZRFZRUURFeFZ5YjI5MFkyRXRhM1YwZEd3dGFXNTBaWEp1WVd3d0hoY05NalF3TVRFMU1URTBOelUwV2hjTgpNelF3TVRFeU1URTBOelUwV2pBZ01SNHdIQVlEVlFRREV4VnliMjkwWTJFdGEzVjBkR3d0YVc1MFpYSnVZV3d3CldUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFTRk9rNHJPUldVUGhoTjUrK09EN1I2MW5Gb1lBY0QKenpvUS91SW93NktjeGhwRWNQTDFxb3ZZUGxUYUJabEh3c2FpNE50VHA4aDA1RHVRSGZKOE9JNXFvMEl3UURBTwpCZ05WSFE4QkFmOEVCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXE3TGtFSk1TCm1MOVpKWjBSOUluKzZkclhycEl3Q2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnVlN1K00ydnZ3QlF3eTJHMVlhdkkKQld2RGtSNlRla0I5U0VqdzJIblRSMWtDSUZSNFNkWGFPQkFGWjVHa2RLWCtSY2IzaDFIZm52eFJEVW96bTl2agphenp3Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KIyByb290Y2EtcHVibGljCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlCZXpDQ0FTS2dBd0lCQWdJUU5IREdZc0JzNzk4aWJERDdxL28ybGpBS0JnZ3Foa2pPUFFRREFqQWVNUnd3CkdnWURWUVFERXhOeWIyOTBZMkV0YTNWMGRHd3RjSFZpYkdsak1CNFhEVEkwTURFeE5URXdNVFV6TmxvWERUTTAKTURFeE1qRXdNVFV6Tmxvd0hqRWNNQm9HQTFVRUF4TVRjbTl2ZEdOaExXdDFkSFJzTFhCMVlteHBZekJaTUJNRwpCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkQ3OGF2WHFocmhDNXc4czlXa2Q0SXBiZUV1MDNDUitYWFVkCmtEek9SeXhhOXdjY0lkRGl2YkdKakpGWlRUY1ZtYmpxMUJNWXNqcjEyVUlFNUVUM1ZscWpRakJBTUE0R0ExVWQKRHdFQi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJUS0ppeldVSjllVUtpMQpkczBscjZjNnNEN0VCREFLQmdncWhrak9QUVFEQWdOSEFEQkVBaUJJWndZcTYxQnFNSmFCNlVjRm9Sc3hlY3dICjV6L3pNT2RyT3llMG1OaThKZ0lnUUxCNHdES3JwZjl0WDJsb00rMHVUb3BBRFNZSW5yY2ZWdTRGQnVZVTNJZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-internal-svc + labels: + service: placement +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkVENDQVJxZ0F3SUJBZ0lRTkZUVDE2eTc0RGJaOGJTL25ESDBkakFLQmdncWhrak9QUVFEQWpBYU1SZ3cKRmdZRFZRUURFdzl5YjI5MFkyRXRhVzUwWlhKdVlXd3dIaGNOTWpRd01URXdNVFV5T0RBMFdoY05NalF3TkRBNQpNVFV5T0RBMFdqQWFNUmd3RmdZRFZRUURFdzl5YjI5MFkyRXRhVzUwWlhKdVlXd3dXVEFUQmdjcWhrak9QUUlCCkJnZ3Foa2pPUFFNQkJ3TkNBQVFjK2d5OVFCNmw1NFNBQlkxUTJKZWx5MEhSTGEvMzlkRUxzU2RhNnJDRENKQWwKWjJ2bGlGbUo5WVlJNCtSbGRIejJWNXYvYjBpK2x0RjcxMGZ1OHJTbW8wSXdRREFPQmdOVkhROEJBZjhFQkFNQwpBcVF3RHdZRFZSMFRBUUgvQkFVd0F3RUIvekFkQmdOVkhRNEVGZ1FVeUsyc0hXaUxHNnR6bWlVbENkUmhsRTJLCnNHSXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBSzVtTi9zQlBVcXAwckd1QjhnMVRxY21KR3ZMVUpyNjlnaEEKaEozMldCT1BBaUVBbEtwU0dVTzhac25UcVQrQ1hWbXNuWkxBcVJMV1NhbUI5U2NyczNDZ05zWT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNhekNDQWhHZ0F3SUJBZ0lSQU1GRmpzWkpHY3BuaVBFNXNmQytrOEV3Q2dZSUtvWkl6ajBFQXdJd0dqRVkKTUJZR0ExVUVBeE1QY205dmRHTmhMV2x1ZEdWeWJtRnNNQjRYRFRJME1ERXhOVEV4TkRnMU1sb1hEVE0wTURFeApNakV4TkRnMU1sb3dBRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNRzhQSWwzCnc4RXdXMHdUUG5qRURpU2dTdVI4WHJaajcrSjYyUkJMTHJ3ZUxKdWd1Wm1MaUh3M09uSldWa0hEOVpaZzlYSGUKbGZ6UDY3Wi8rYXBNMzJ5VWJTVUcrRjlBdXlGMHRTK2lPODFkUFRSY1luNzVBK0xWdnk1UkVpOGIvTFkzNTNPbgpxUEhuK2kyeTNLUC9HZkhjSi9lVlVXNFJkV2wyTHEyejRtRDRUK2twS0VwSnRGSTJQa2lrSVNOV2RRdmtEeW1WClF3a1B3U01FVy9yaEdGL2s3b0gvVWtwdy9wU1N1R0M2a1lpSnlwOTFHT0xCMlVoc254Z3dLelh5VS9MdGFrZXoKS2RHSFUvNUNLTTRKczg0ZnlNTDBBNXMxalpZQXZEWkVLNEgvYVpCb3EzV0NoQ1R4WWhIOVVuczhIQy9KbHJCMApHaitwVHNuaEc2cUlFQ2tDQXdFQUFhT0JoakNCZ3pBT0JnTlZIUThCQWY4RUJBTUNCYUF3RXdZRFZSMGxCQXd3CkNnWUlLd1lCQlFVSEF3RXdEQVlEVlIwVEFRSC9CQUl3QURBZkJnTlZIU01FR0RBV2dCVElyYXdkYUlzYnEzT2EKSlNVSjFHR1VUWXF3WWpBdEJnTlZIUkVCQWY4RUl6QWhnaDlyWlhsemRHOXVaUzFwYm5SbGNtNWhiQzV2Y0dWdQpjM1JoWTJzdWMzWmpNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJRTFJYXcxcnRnU0ROZmxBSjJRek9VQjJxU1llCk03ZWdsaXZLVW01cmVOZThBaUVBMU93SGcwQ1YxOUNhYUpSSi9SS25UcXNJTGhNdjBEUVNPdnFwbWc0MWZDTT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb3dJQkFBS0NBUUVBd2J3OGlYZkR3VEJiVEJNK2VNUU9KS0JLNUh4ZXRtUHY0bnJaRUVzdXZCNHNtNkM1Cm1ZdUlmRGM2Y2xaV1FjUDFsbUQxY2Q2Vi9NL3J0bi81cWt6ZmJKUnRKUWI0WDBDN0lYUzFMNkk3elYwOU5GeGkKZnZrRDR0Vy9MbEVTTHh2OHRqZm5jNmVvOGVmNkxiTGNvLzhaOGR3bjk1VlJiaEYxYVhZdXJiUGlZUGhQNlNrbwpTa20wVWpZK1NLUWhJMVoxQytRUEtaVkRDUS9CSXdSYit1RVlYK1R1Z2Y5U1NuRCtsSks0WUxxUmlJbktuM1VZCjRzSFpTR3lmR0RBck5mSlQ4dTFxUjdNcDBZZFQva0lvemdtenpoL0l3dlFEbXpXTmxnQzhOa1FyZ2Y5cGtHaXIKZFlLRUpQRmlFZjFTZXp3Y0w4bVdzSFFhUDZsT3llRWJxb2dRS1FJREFRQUJBb0lCQVFDQ0hweUdNK05OY040UQo1V2Z6RXJMeEZKdlloRlBVcXFDbWU1NG9uR1ppUU4zekZPc3pYbzBuNkt3ZnVTOHI4cUtUQXNJM1hhbGRhSVRIClNZTDFSN1pVSmdoOGN3Y0VhdVNFbnU5R2MrODRpbVFlTStLUHAwNWQzdlFOOXJPQTRvcEVGSjRtaHJnbzZZYVYKaE9rK1dJc2piNXVFWlV5UTRiYjdRejRzdW9IVVlDYXFkVGlqU1lYQzNOd092YUlwa3pTNEo0cU5CUlhyYnNWSwowaGt4ZFNIY1hKNEREN0hybktpcEsxT2xUbUVObVZYbmlaNnRPcWc2eUNFeXFteWN0UnlUVTZRRzVPbVM2clJVCm82Z25EclA1TlgwRUhuakY3b1lka0JVbGJxWk96UHVGbG5CdUVKOFpTUEtOZHA5ejhuS3lqbGJiL0YxWGRDdEkKZERhVUhmREZBb0dCQVBFZkZZbDhPb2VhU21oSElKcGpxd3RCanYrRjFuOXNJbHNuZWNyQ1JmN243RW53d1hXaQpReStXQ3l6aDJGRVVad1dod2RQeXFJT3NVaG1vaXBIQmN3NlVUaW9xalM4SlpvSDlURFBQUEd5OXIwMHZwRkNuCnFkdjNXMkhWVytRckMrWk1nc2ZKdUlTTnFtbFdFeHpCNFBJQWRHQTdKVzFMY0ZCcG1Zd25DdXZyQW9HQkFNMncKbS94cVRhMmgySjFnNUI0elE5UnBhM280SEoyL2pTaHEzNW9heVNGNWJDYWtnWGRxek0yU0FwQ0x4dzlvY3doRAp3WWRaMWliaHl6b1dDQVZZZ0RlaXViUi96ZTN3Nzk4NktScUNmNnptMk5HOEoxODVDZDdKSjBiaTZBTTgvalpTCnFqWkJIK0FqanF2aFFJM0FMMEdzNlFvc2Z3L3hOL2k1cG00UWM5TTdBb0dBZjZCbFpQVmxnWnN3WVV1c3ZTdWUKUUlIOTc5Qm12ZUY5dWVRR09rVmtpVTAzSzlnTWZuaFp1WmxnNXV2UDlQS29xVGw2Zi9aRUxoWUxDdHZFSk94UgpPMWxTbWswVmw5MFE3aU1scjVLMHVCWWE4TzhUdVVGVnprRjZsQ2s3ejJUZGtwUFM4VzhiaE1YN2VtLytBODIzCmhFQ3JXTGhWMGlrSkZQY2dPQ2YrUnVzQ2dZQTYvcld1cnhxNmUxb3l3WENNVE8zZWhhSUMrd2NTSTdlcjZRTmIKSXVXZlNVRkEwQndtRVNiT3ExczY5Q3hTK2dWTVVJcTRkSWJjdmhSWkE2cW5SZHY0bVI2a2E2ZTM0RXdjZllUKwppb0Z1S1FQMUcvODY2NVF1SndteDVqRGZoT1h3MU1MbkxzU2l0L0FhMGs5K21LbTFMNC9qa0NHZGcvVW16TEMwCmp0bDVzd0tCZ0VPTVI3ODVLT2hyNXFoWmE2b0MvU25JeEptS1FxTWdXU0NGV1pGMDZrVlRnSmthb1hwUEl0bUIKOUZGbE1nTTJSeC91S2V3YTNDSTdQK240ek1uYSswTmhDL0RwNkMxVFVsVWlrcnJYQ3I5a1NPR2dXaEFISDljTwozRENvdkhOcE1PaG51dnhoMlpDeTdYbjFJeGgxWXdlYnVobFZzeTFvR0tDQ0lJb00rOVg1Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-public-svc + labels: + service: placement +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlekNDQVNLZ0F3SUJBZ0lRTkhER1lzQnM3OThpYkREN3EvbzJsakFLQmdncWhrak9QUVFEQWpBZU1Sd3cKR2dZRFZRUURFeE55YjI5MFkyRXRhM1YwZEd3dGNIVmliR2xqTUI0WERUSTBNREV4TlRFd01UVXpObG9YRFRNMApNREV4TWpFd01UVXpObG93SGpFY01Cb0dBMVVFQXhNVGNtOXZkR05oTFd0MWRIUnNMWEIxWW14cFl6QlpNQk1HCkJ5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCRDc4YXZYcWhyaEM1dzhzOVdrZDRJcGJlRXUwM0NSK1hYVWQKa0R6T1J5eGE5d2NjSWREaXZiR0pqSkZaVFRjVm1ianExQk1Zc2pyMTJVSUU1RVQzVmxxalFqQkFNQTRHQTFVZApEd0VCL3dRRUF3SUNwREFQQmdOVkhSTUJBZjhFQlRBREFRSC9NQjBHQTFVZERnUVdCQlRLSml6V1VKOWVVS2kxCmRzMGxyNmM2c0Q3RUJEQUtCZ2dxaGtqT1BRUURBZ05IQURCRUFpQklad1lxNjFCcU1KYUI2VWNGb1JzeGVjd0gKNXovek1PZHJPeWUwbU5pOEpnSWdRTEI0d0RLcnBmOXRYMmxvTSswdVRvcEFEU1lJbnJjZlZ1NEZCdVlVM0lnPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUNiVENDQWhPZ0F3SUJBZ0lSQUtacXlMbUhLNC9VRTZmMi9LNWxiQnN3Q2dZSUtvWkl6ajBFQXdJd0hqRWMKTUJvR0ExVUVBeE1UY205dmRHTmhMV3QxZEhSc0xYQjFZbXhwWXpBZUZ3MHlOREF4TVRVeE1ESXdOVFJhRncwegpOREF4TVRJeE1ESXdOVFJhTUFBd2dnRWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUJEd0F3Z2dFS0FvSUJBUUMxCjhDcFJRVG1abHNzSUlmZ2hIK2ltUUtFMFdZVlJOeS8vMVM0aDVtV2tBcUZiVkhoUmptbFJ2cCtQUWpKOU16TDUKMXpXdmYxandEQ2pzYUxvL2FwSW9OSXJIcjN4TTRoYWl0emU0RjFwZzNoL3MvblExNWN5Q2U5dHdHR0RuWEllMwo2djBuNE9LNnAwSWJjcVk2Q1RBMTBwcGJZa3V6bzdVRkx6ZWxsc1ZhRlhzZ21JWDg4bTRXNmNBTi84cjJPWUI3Ck9HM0ZNOXAxSUFxT0hyT21EelFlTldqOUVjQy9TSCs5MGg4c1FyY1pvMWtWa1g1b2tpSUhDZjRlc2o3Q08rTGgKR3lsTmZyRzl6QTlPM0c3QVNDWVdPVWwyZTBhNHhZbE9QMmI4ejFEV3NIMTBVYXVsZHlRQXNtbkhtaW1VNzBmKwpEazZkQ1hXVHN4cGZ2cXphOVR4YkFnTUJBQUdqZ1lRd2dZRXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkCkpRUU1NQW9HQ0NzR0FRVUZCd01CTUF3R0ExVWRFd0VCL3dRQ01BQXdId1lEVlIwakJCZ3dGb0FVeWlZczFsQ2YKWGxDb3RYYk5KYStuT3JBK3hBUXdLd1lEVlIwUkFRSC9CQ0V3SDRJZGEyVjVjM1J2Ym1VdGNIVmliR2xqTG05dwpaVzV6ZEdGamF5NXpkbU13Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnTzAzT2JmNm9uV2RiZG4xa282OVpuTFhMCmtQSHFYU3VRNlcxTDFvY3NDR3NDSVFEakEyVm9pWVdYN0hzSjVGNkZYV3FsZnl0RmduVVgvTmhvT1lIVnB2TWQKSGc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + tls.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBdGZBcVVVRTVtWmJMQ0NINElSL29wa0NoTkZtRlVUY3YvOVV1SWVabHBBS2hXMVI0ClVZNXBVYjZmajBJeWZUTXkrZGMxcjM5WThBd283R2k2UDJxU0tEU0t4Njk4VE9JV29yYzN1QmRhWU40ZjdQNTAKTmVYTWdudmJjQmhnNTF5SHQrcjlKK0RpdXFkQ0czS21PZ2t3TmRLYVcySkxzNk8xQlM4M3BaYkZXaFY3SUppRgovUEp1RnVuQURmL0s5am1BZXpodHhUUGFkU0FLamg2enBnODBIalZvL1JIQXYwaC92ZElmTEVLM0dhTlpGWkYrCmFKSWlCd24rSHJJK3dqdmk0UnNwVFg2eHZjd1BUdHh1d0VnbUZqbEpkbnRHdU1XSlRqOW0vTTlRMXJCOWRGR3IKcFhja0FMSnB4NW9wbE85SC9nNU9uUWwxazdNYVg3NnMydlU4V3dJREFRQUJBb0lCQUd0eVdvdUNLYkk3Qzh6Ugp3dWhOSCtpUFlxUzMrYlB0RTd2UytsdXE1WHZtMGNST0xvQjd5bGNzYks3K09UTVhlWk56TlpGZmMvYlFONXJtCmZwZlZLRnYySzcraU01WjBMMG9KU2k2K0cvSDVQSUdLQkxlUDd5ZGdYa2ZsSGRXRkgrSE9OWlBIakI4UGlFc04KZW4zcnp6ejZFNDdFamxDWTdkOFI4NXNuWDRYREN2bG1CQnhvcnpqVERuK1dTWWpKS09SSk5zY3oxQXFYR1VjVwpQaHRNYkwybC8zN2hPbTA4SjRRWXowTWduOWE5VUFXLzFNS2lXbHVpc1NHNG9YaFNPS1hkdk1IS3VxS09sUDJzCk9xWjBlR3JBNmpKdWlmZVY2Q2NIU2p3VUgwdHpiMmdZQVM2cm5RQlREbFkxR1I4Skx0YWhWREtqdUwyV0hjclkKbHhCOGZBRUNnWUVBMWpxc01weFo5cG9LNkpmRDZzVTU0ZUU1UWlNYlB0MERRZjlYU1J6NW5zQXlraHdKWEZDVwpKWTNiU3BhcGREeEgzakpDQ0VzN3NSWUhUeDRzNVJQSldtWC9oZTFwVEs4TDVlaFV2TmVudG5nVTB3aE4rZVEzCjl4Sk1VbHVYdGkvU0FpNi9jQk5HY1ZjQjFGRStmVzY0VDhqYVVQakRrL0Z2dFRXOXkzZnNVZnNDZ1lFQTJXbXMKYStuZ2RaS24rVTlCMlFCTXB0K0RLL0txNVF6RW1qZDVMSjJMb3FLUjhGbjlpVVZoUVljUEpobDJVV3VjTTl0RQp0QUlYdEY0anVUejlqUUNMMGQ5a09DeCswdTBKUFZJejdlVmFFVGs1enF0azRsTnhVUkJhQ3pCUEJkdjZJd3BDCkR4UXJWRXBXYlMra1JvYTNKSElOdHdjUWt2Nk50N1JIajd1WEVTRUNnWUJsREozbTdZc2Q0QUZmUHg4Qm9YQXgKRkt5T2ZzSytQei9uSkl0R2lHMVNMWFJ0S041ZGRnR3N5eUh5SitqY1ZBYk9UMFNJWnZ4TUJva0NEOGk3Y1Q3Ygo3aHErVUlNSDBkVzU1NEg0NVh4TmZJek9FaSs5dktHTllFc3gyZFJROG5PTDVnTVUyWEt6eVllcVgzd3JiRXR5CkR0cXpzUE9IMkMySiswU0FNaHY5ZXdLQmdRQ2szeWs5TUwvaUNWUk9rTmNybTdtRk5xeS9rQ2dleU43eTRDeUoKTS9RbllrZHYwSjZmRWJrZU96QzJ3TXBrRmtuL1hVR3RqSVN6YUV5STlnS0ZnaXVGL1hWL3orWmhTQllncFl6eAoxR0xIK3ZDbWxIMU4wTjkzRFFKcng3ZTFoc3NhOVhXQS85ZVg5VU96UzFTMWt3V2hvc2haeXdhN29rU1FVaXVPCmlVQ1hZUUtCZ1FEUVZUVHc3WUY3QzNTVmg5OWRObUdTaHV2LzZ2aTJmNDlOMklGMURNQ1haaEpoOUVZck9TV2kKY05oakxGRFhmdzVlZlFURWU3Ykx5bTJGVDd0YnZFSm5USHFyakVuUDRUWExqZnczL3RiQ3RxWVNZRlRqdThFUApadHVwd21ZWjhFVU1pSnVHS2l2SExmSjk2dy8xR21BOHVCZUVtV05YRW9FUU1ySmxuM3g5d3c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= diff --git a/placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate b/placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate new file mode 100644 index 000000000..dcddb1070 --- /dev/null +++ b/placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate @@ -0,0 +1 @@ +{{.spec.endpoints.internal}}{{":"}}{{.spec.endpoints.public}}{{"\n"}} diff --git a/placement-import/test/kuttl/lib/helper_functions.sh b/placement-import/test/kuttl/lib/helper_functions.sh new file mode 100755 index 000000000..535b9e07a --- /dev/null +++ b/placement-import/test/kuttl/lib/helper_functions.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +assert_regex() { + + if [[ $1 =~ $2 ]]; then + exit 0 + else + printf '%s\n' "Regex check '$2' failed against oc output: $1"; + exit 1 + fi +} + +"$@" diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml new file mode 120000 index 000000000..e067bd151 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml @@ -0,0 +1 @@ +../../common/cleanup-placement.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml new file mode 100644 index 000000000..119adc661 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: cert-internal-svc +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-public-svc +--- +apiVersion: v1 +kind: Secret +metadata: + name: combined-ca-bundle diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml new file mode 120000 index 000000000..b63d9bfb8 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml @@ -0,0 +1 @@ +../../common/tls_certificates.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml new file mode 120000 index 000000000..11447fab8 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml @@ -0,0 +1 @@ +../../../../config/samples/placement_v1beta1_placementtls.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml new file mode 100644 index 000000000..207b4bd46 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml @@ -0,0 +1,217 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +spec: + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + passwordSelectors: + service: PlacementPassword + preserveJobs: false + replicas: 1 + secret: osp-secret + serviceUser: placement + tls: + api: + internal: + secretName: cert-internal-svc + public: + secretName: cert-public-svc + caBundleSecretName: combined-ca-bundle +status: + databaseHostname: openstack.placement-kuttl-tests.svc + readyCount: 1 + conditions: + - message: Setup complete + reason: Ready + status: "True" + type: Ready + - message: Create service completed + reason: Ready + status: "True" + type: CreateServiceReady + - message: DB create completed + reason: Ready + status: "True" + type: DBReady + - message: DBsync completed + reason: Ready + status: "True" + type: DBSyncReady + - message: Deployment completed + reason: Ready + status: "True" + type: DeploymentReady + - message: Input data complete + reason: Ready + status: "True" + type: InputReady + - message: Setup complete + reason: Ready + status: "True" + type: KeystoneEndpointReady + - message: Setup complete + reason: Ready + status: "True" + type: KeystoneServiceReady + - message: MariaDBAccount creation complete + reason: Ready + status: "True" + type: MariaDBAccountReady + - message: NetworkAttachments completed + reason: Ready + status: "True" + type: NetworkAttachmentsReady + - message: RoleBinding created + reason: Ready + status: "True" + type: RoleBindingReady + - message: Role created + reason: Ready + status: "True" + type: RoleReady + - message: ServiceAccount created + reason: Ready + status: "True" + type: ServiceAccountReady + - message: Service config create completed + reason: Ready + status: "True" + type: ServiceConfigReady + - message: Input data complete + reason: Ready + status: "True" + type: TLSInputReady +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +spec: + replicas: 1 + template: + metadata: + labels: + service: placement + spec: + affinity: + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - podAffinityTerm: + labelSelector: + matchExpressions: + - key: service + operator: In + values: + - placement + topologyKey: kubernetes.io/hostname + weight: 100 + containers: + - args: + - --single-child + - -- + - /usr/bin/tail + - -n+1 + - -F + - /var/log/placement/placement-api.log + command: + - /usr/bin/dumb-init + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + name: placement-log + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + resources: {} + securityContext: + runAsUser: 42482 + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + volumeMounts: + - mountPath: /usr/local/bin/container-scripts + name: scripts + readOnly: true + - mountPath: /var/log/placement + name: logs + - mountPath: /var/lib/openstack/config + name: config-data + - mountPath: /var/lib/kolla/config_files/config.json + name: config-data + readOnly: true + subPath: placement-api-config.json + - mountPath: /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem + name: combined-ca-bundle + readOnly: true + subPath: tls-ca-bundle.pem + - mountPath: /var/lib/config-data/tls/certs/internal.crt + name: internal-tls-certs + readOnly: true + subPath: tls.crt + - mountPath: /var/lib/config-data/tls/private/internal.key + name: internal-tls-certs + readOnly: true + subPath: tls.key + - mountPath: /var/lib/config-data/tls/certs/public.crt + name: public-tls-certs + readOnly: true + subPath: tls.crt + - mountPath: /var/lib/config-data/tls/private/public.key + name: public-tls-certs + readOnly: true + subPath: tls.key + - args: + - -c + - /usr/local/bin/kolla_start + command: + - /bin/bash + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + name: placement-api + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: 8778 + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 30 + successThreshold: 1 + timeoutSeconds: 30 + resources: {} + restartPolicy: Always + securityContext: {} + serviceAccount: placement-placement + serviceAccountName: placement-placement +status: + availableReplicas: 1 + replicas: 1 diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml new file mode 120000 index 000000000..82362fc71 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml @@ -0,0 +1 @@ +../../common/patch_placement_deploy.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml new file mode 120000 index 000000000..e067bd151 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml @@ -0,0 +1 @@ +../../common/cleanup-placement.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml b/placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml new file mode 120000 index 000000000..b05cc60f8 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml @@ -0,0 +1 @@ +../../common/errors_cleanup_placement.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml b/placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml new file mode 120000 index 000000000..e067bd151 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml @@ -0,0 +1 @@ +../../common/cleanup-placement.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml b/placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml new file mode 120000 index 000000000..748d21948 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml @@ -0,0 +1 @@ +../../common/placementapi_deploy.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/02-assert.yaml b/placement-import/test/kuttl/tests/placement_scale/02-assert.yaml new file mode 120000 index 000000000..461654ea9 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/02-assert.yaml @@ -0,0 +1 @@ +../../common/assert_sample_deployment.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml b/placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml new file mode 120000 index 000000000..82362fc71 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml @@ -0,0 +1 @@ +../../common/patch_placement_deploy.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/03-assert.yaml b/placement-import/test/kuttl/tests/placement_scale/03-assert.yaml new file mode 100644 index 000000000..c3534b707 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/03-assert.yaml @@ -0,0 +1,26 @@ +# +# Check for: +# +# - 1 placementAPI CR +# - 3 Pods for placementAPI CR +# + +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +spec: + replicas: 3 +status: + readyCount: 3 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +spec: + replicas: 3 +status: + availableReplicas: 3 diff --git a/placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml b/placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml new file mode 100644 index 000000000..69f72843c --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + oc patch placementapi -n $NAMESPACE placement --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":3}]' diff --git a/placement-import/test/kuttl/tests/placement_scale/04-assert.yaml b/placement-import/test/kuttl/tests/placement_scale/04-assert.yaml new file mode 100644 index 000000000..cfed8052b --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/04-assert.yaml @@ -0,0 +1,26 @@ +# +# Check for: +# +# - 1 PlacementAPI CR +# - 1 Pods for PlacementAPI CR +# + +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +spec: + replicas: 1 +status: + readyCount: 1 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +spec: + replicas: 1 +status: + availableReplicas: 1 diff --git a/placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml b/placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml new file mode 100644 index 000000000..4ddad1ca6 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + oc patch placementapi -n $NAMESPACE placement --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":1}]' diff --git a/placement-import/test/kuttl/tests/placement_scale/05-assert.yaml b/placement-import/test/kuttl/tests/placement_scale/05-assert.yaml new file mode 100644 index 000000000..209ec4645 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/05-assert.yaml @@ -0,0 +1,23 @@ +# +# Check for: +# +# - 1 PlacementAPI CR with 0 replicas +# - Placement Deployment with 0 Pods +# NOTE: This test is asserting for spec.replicas to be 0 +# NOT status.availableReplicas + +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + finalizers: + - openstack.org/placementapi + name: placement +spec: + replicas: 0 +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: placement +spec: + replicas: 0 diff --git a/placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml b/placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml new file mode 100644 index 000000000..34306eece --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml @@ -0,0 +1,5 @@ +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + oc patch placementapi -n $NAMESPACE placement --type='json' -p='[{"op": "replace", "path": "/spec/replicas", "value":0}]' diff --git a/placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml b/placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml new file mode 120000 index 000000000..e067bd151 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml @@ -0,0 +1 @@ +../../common/cleanup-placement.yaml \ No newline at end of file diff --git a/placement-import/test/kuttl/tests/placement_scale/06-errors.yaml b/placement-import/test/kuttl/tests/placement_scale/06-errors.yaml new file mode 120000 index 000000000..b05cc60f8 --- /dev/null +++ b/placement-import/test/kuttl/tests/placement_scale/06-errors.yaml @@ -0,0 +1 @@ +../../common/errors_cleanup_placement.yaml \ No newline at end of file From 148e7934af1bb010cbd59a8b60223ed93e773929 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 15:55:55 +0530 Subject: [PATCH 02/16] Move placement API to multi-group structure Move types from placement-import/api/v1beta1/ to api/placement/v1beta1 following nova-operator multigroup structure --- .../placement/v1beta1/api_types.go | 0 .../placement/v1beta1/api_webhook.go | 0 .../placement}/v1beta1/groupversion_info.go | 0 .../api/v1beta1/zz_generated.deepcopy.go | 273 ------------------ 4 files changed, 273 deletions(-) rename placement-import/api/v1beta1/placementapi_types.go => api/placement/v1beta1/api_types.go (100%) rename placement-import/api/v1beta1/placementapi_webhook.go => api/placement/v1beta1/api_webhook.go (100%) rename {placement-import/api => api/placement}/v1beta1/groupversion_info.go (100%) delete mode 100644 placement-import/api/v1beta1/zz_generated.deepcopy.go diff --git a/placement-import/api/v1beta1/placementapi_types.go b/api/placement/v1beta1/api_types.go similarity index 100% rename from placement-import/api/v1beta1/placementapi_types.go rename to api/placement/v1beta1/api_types.go diff --git a/placement-import/api/v1beta1/placementapi_webhook.go b/api/placement/v1beta1/api_webhook.go similarity index 100% rename from placement-import/api/v1beta1/placementapi_webhook.go rename to api/placement/v1beta1/api_webhook.go diff --git a/placement-import/api/v1beta1/groupversion_info.go b/api/placement/v1beta1/groupversion_info.go similarity index 100% rename from placement-import/api/v1beta1/groupversion_info.go rename to api/placement/v1beta1/groupversion_info.go diff --git a/placement-import/api/v1beta1/zz_generated.deepcopy.go b/placement-import/api/v1beta1/zz_generated.deepcopy.go deleted file mode 100644 index 8c4b13be3..000000000 --- a/placement-import/api/v1beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,273 +0,0 @@ -//go:build !ignore_autogenerated - -/* -Copyright 2022. - -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. -*/ - -// Code generated by controller-gen. DO NOT EDIT. - -package v1beta1 - -import ( - topologyv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" - "github.com/openstack-k8s-operators/lib-common/modules/common/condition" - "github.com/openstack-k8s-operators/lib-common/modules/common/service" - "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *APIOverrideSpec) DeepCopyInto(out *APIOverrideSpec) { - *out = *in - if in.Service != nil { - in, out := &in.Service, &out.Service - *out = make(map[service.Endpoint]service.RoutedOverrideSpec, len(*in)) - for key, val := range *in { - (*out)[key] = *val.DeepCopy() - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIOverrideSpec. -func (in *APIOverrideSpec) DeepCopy() *APIOverrideSpec { - if in == nil { - return nil - } - out := new(APIOverrideSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. -func (in *AuthSpec) DeepCopy() *AuthSpec { - if in == nil { - return nil - } - out := new(AuthSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PasswordSelector) DeepCopyInto(out *PasswordSelector) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSelector. -func (in *PasswordSelector) DeepCopy() *PasswordSelector { - if in == nil { - return nil - } - out := new(PasswordSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PlacementAPI) DeepCopyInto(out *PlacementAPI) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPI. -func (in *PlacementAPI) DeepCopy() *PlacementAPI { - if in == nil { - return nil - } - out := new(PlacementAPI) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PlacementAPI) 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 *PlacementAPIDefaults) DeepCopyInto(out *PlacementAPIDefaults) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIDefaults. -func (in *PlacementAPIDefaults) DeepCopy() *PlacementAPIDefaults { - if in == nil { - return nil - } - out := new(PlacementAPIDefaults) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PlacementAPIList) DeepCopyInto(out *PlacementAPIList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]PlacementAPI, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIList. -func (in *PlacementAPIList) DeepCopy() *PlacementAPIList { - if in == nil { - return nil - } - out := new(PlacementAPIList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *PlacementAPIList) 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 *PlacementAPISpec) DeepCopyInto(out *PlacementAPISpec) { - *out = *in - in.PlacementAPISpecCore.DeepCopyInto(&out.PlacementAPISpecCore) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpec. -func (in *PlacementAPISpec) DeepCopy() *PlacementAPISpec { - if in == nil { - return nil - } - out := new(PlacementAPISpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PlacementAPISpecCore) DeepCopyInto(out *PlacementAPISpecCore) { - *out = *in - if in.Replicas != nil { - in, out := &in.Replicas, &out.Replicas - *out = new(int32) - **out = **in - } - out.PasswordSelectors = in.PasswordSelectors - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = new(map[string]string) - if **in != nil { - in, out := *in, *out - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - } - if in.DefaultConfigOverwrite != nil { - in, out := &in.DefaultConfigOverwrite, &out.DefaultConfigOverwrite - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - in.Resources.DeepCopyInto(&out.Resources) - if in.NetworkAttachments != nil { - in, out := &in.NetworkAttachments, &out.NetworkAttachments - *out = make([]string, len(*in)) - copy(*out, *in) - } - in.Override.DeepCopyInto(&out.Override) - in.TLS.DeepCopyInto(&out.TLS) - out.Auth = in.Auth - if in.TopologyRef != nil { - in, out := &in.TopologyRef, &out.TopologyRef - *out = new(topologyv1beta1.TopoRef) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpecCore. -func (in *PlacementAPISpecCore) DeepCopy() *PlacementAPISpecCore { - if in == nil { - return nil - } - out := new(PlacementAPISpecCore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PlacementAPIStatus) DeepCopyInto(out *PlacementAPIStatus) { - *out = *in - if in.Hash != nil { - in, out := &in.Hash, &out.Hash - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(condition.Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.NetworkAttachments != nil { - in, out := &in.NetworkAttachments, &out.NetworkAttachments - *out = make(map[string][]string, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - inVal := (*in)[key] - in, out := &inVal, &outVal - *out = make([]string, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - if in.LastAppliedTopology != nil { - in, out := &in.LastAppliedTopology, &out.LastAppliedTopology - *out = new(topologyv1beta1.TopoRef) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIStatus. -func (in *PlacementAPIStatus) DeepCopy() *PlacementAPIStatus { - if in == nil { - return nil - } - out := new(PlacementAPIStatus) - in.DeepCopyInto(out) - return out -} From d0653282354de23adaed256be31f42ade9f83cf6 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 15:58:48 +0530 Subject: [PATCH 03/16] Move placement controller Move controller from placement-import/internal/controller/ to internal/controller/placement --- .../controller/placement/api_controller.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename placement-import/internal/controller/placementapi_controller.go => internal/controller/placement/api_controller.go (100%) diff --git a/placement-import/internal/controller/placementapi_controller.go b/internal/controller/placement/api_controller.go similarity index 100% rename from placement-import/internal/controller/placementapi_controller.go rename to internal/controller/placement/api_controller.go From 4a89f336f34c3432341c74a8063d6bb2842a5057 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:01:23 +0530 Subject: [PATCH 04/16] Move placement business logic to internal/placement Move placement business logic from placement-import/internal/placement to internal/placement/ to keep alongside nova business logic --- {placement-import/internal => internal}/placement/const.go | 0 {placement-import/internal => internal}/placement/dbsync.go | 0 {placement-import/internal => internal}/placement/deployment.go | 0 {placement-import/internal => internal}/placement/volumes.go | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {placement-import/internal => internal}/placement/const.go (100%) rename {placement-import/internal => internal}/placement/dbsync.go (100%) rename {placement-import/internal => internal}/placement/deployment.go (100%) rename {placement-import/internal => internal}/placement/volumes.go (100%) diff --git a/placement-import/internal/placement/const.go b/internal/placement/const.go similarity index 100% rename from placement-import/internal/placement/const.go rename to internal/placement/const.go diff --git a/placement-import/internal/placement/dbsync.go b/internal/placement/dbsync.go similarity index 100% rename from placement-import/internal/placement/dbsync.go rename to internal/placement/dbsync.go diff --git a/placement-import/internal/placement/deployment.go b/internal/placement/deployment.go similarity index 100% rename from placement-import/internal/placement/deployment.go rename to internal/placement/deployment.go diff --git a/placement-import/internal/placement/volumes.go b/internal/placement/volumes.go similarity index 100% rename from placement-import/internal/placement/volumes.go rename to internal/placement/volumes.go From d1d9c397f5af20b4b91f877071b2873fde624a77 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:09:37 +0530 Subject: [PATCH 05/16] Move placement webhook Move webhook from placement-import/internal/webhook/v1beta1/ to internal/webhook/placement/v1beta1/ --- .../webhook/placement/v1beta1/api_webhook.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename placement-import/internal/webhook/v1beta1/placementapi_webhook.go => internal/webhook/placement/v1beta1/api_webhook.go (100%) diff --git a/placement-import/internal/webhook/v1beta1/placementapi_webhook.go b/internal/webhook/placement/v1beta1/api_webhook.go similarity index 100% rename from placement-import/internal/webhook/v1beta1/placementapi_webhook.go rename to internal/webhook/placement/v1beta1/api_webhook.go From 18f4af52180e0c20335269ac9830f0260d916682 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:12:44 +0530 Subject: [PATCH 06/16] Move templates Move placement templates from placement-import/templates/placementapi/ to templates/placement/api/ --- .../placementapi => templates/placement/api}/config/httpd.conf | 0 .../placement/api}/config/placement-api-config.json | 0 .../placement/api}/config/placement-dbsync-config.json | 0 .../placement/api}/config/placement.conf | 0 .../placementapi => templates/placement/api}/config/ssl.conf | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {placement-import/templates/placementapi => templates/placement/api}/config/httpd.conf (100%) rename {placement-import/templates/placementapi => templates/placement/api}/config/placement-api-config.json (100%) rename {placement-import/templates/placementapi => templates/placement/api}/config/placement-dbsync-config.json (100%) rename {placement-import/templates/placementapi => templates/placement/api}/config/placement.conf (100%) rename {placement-import/templates/placementapi => templates/placement/api}/config/ssl.conf (100%) diff --git a/placement-import/templates/placementapi/config/httpd.conf b/templates/placement/api/config/httpd.conf similarity index 100% rename from placement-import/templates/placementapi/config/httpd.conf rename to templates/placement/api/config/httpd.conf diff --git a/placement-import/templates/placementapi/config/placement-api-config.json b/templates/placement/api/config/placement-api-config.json similarity index 100% rename from placement-import/templates/placementapi/config/placement-api-config.json rename to templates/placement/api/config/placement-api-config.json diff --git a/placement-import/templates/placementapi/config/placement-dbsync-config.json b/templates/placement/api/config/placement-dbsync-config.json similarity index 100% rename from placement-import/templates/placementapi/config/placement-dbsync-config.json rename to templates/placement/api/config/placement-dbsync-config.json diff --git a/placement-import/templates/placementapi/config/placement.conf b/templates/placement/api/config/placement.conf similarity index 100% rename from placement-import/templates/placementapi/config/placement.conf rename to templates/placement/api/config/placement.conf diff --git a/placement-import/templates/placementapi/config/ssl.conf b/templates/placement/api/config/ssl.conf similarity index 100% rename from placement-import/templates/placementapi/config/ssl.conf rename to templates/placement/api/config/ssl.conf From dfe24fccfc09b0b43647e88785b0bd81dcd87380 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:17:29 +0530 Subject: [PATCH 07/16] Move placement tests Move placement tests from placement-import/test/ to test/functional/placement/ --- .../functional/placement/api_controller_test.go | 0 .../functional/placement/api_webhook_test.go | 0 .../test/functional => test/functional/placement}/base_test.go | 0 .../test/functional => test/functional/placement}/suite_test.go | 0 .../kuttl/placement}/common/assert_sample_deployment.yaml | 0 .../kuttl => test/kuttl/placement}/common/cleanup-placement.yaml | 0 .../kuttl/placement}/common/errors_cleanup_placement.yaml | 0 .../kuttl/placement}/common/patch_placement_deploy.yaml | 0 .../kuttl/placement}/common/placementapi_deploy.yaml | 0 .../kuttl => test/kuttl/placement}/common/tls_certificates.yaml | 0 .../kuttl/placement}/go_templates/apiEndpoints.gotemplate | 0 .../test/kuttl => test/kuttl/placement}/lib/helper_functions.sh | 0 .../tests/placement_deploy_tls/00-cleanup-placement.yaml | 0 .../kuttl/placement}/tests/placement_deploy_tls/01-assert.yaml | 0 .../tests/placement_deploy_tls/01-tls_certificates.yaml | 0 .../tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml | 0 .../kuttl/placement}/tests/placement_deploy_tls/03-assert.yaml | 0 .../tests/placement_deploy_tls/03-patch_placement_deploy.yaml | 0 .../tests/placement_deploy_tls/04-cleanup-placement.yaml | 0 .../kuttl/placement}/tests/placement_deploy_tls/04-errors.yaml | 0 .../placement}/tests/placement_scale/00-cleanup-placement.yaml | 0 .../placement}/tests/placement_scale/01-deploy_placement.yaml | 0 .../kuttl/placement}/tests/placement_scale/02-assert.yaml | 0 .../tests/placement_scale/02-patch_placement_deploy.yaml | 0 .../kuttl/placement}/tests/placement_scale/03-assert.yaml | 0 .../placement}/tests/placement_scale/03-scale-placementapi.yaml | 0 .../kuttl/placement}/tests/placement_scale/04-assert.yaml | 0 .../tests/placement_scale/04-scale-down-placementapi.yaml | 0 .../kuttl/placement}/tests/placement_scale/05-assert.yaml | 0 .../tests/placement_scale/05-scale-down-zero-placementapi.yaml | 0 .../placement}/tests/placement_scale/06-cleanup-placement.yaml | 0 .../kuttl/placement}/tests/placement_scale/06-errors.yaml | 0 32 files changed, 0 insertions(+), 0 deletions(-) rename placement-import/test/functional/placementapi_controller_test.go => test/functional/placement/api_controller_test.go (100%) rename placement-import/test/functional/placementapi_webhook_test.go => test/functional/placement/api_webhook_test.go (100%) rename {placement-import/test/functional => test/functional/placement}/base_test.go (100%) rename {placement-import/test/functional => test/functional/placement}/suite_test.go (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/assert_sample_deployment.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/cleanup-placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/errors_cleanup_placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/patch_placement_deploy.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/placementapi_deploy.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/common/tls_certificates.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/go_templates/apiEndpoints.gotemplate (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/lib/helper_functions.sh (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/00-cleanup-placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/01-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/01-tls_certificates.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/03-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/03-patch_placement_deploy.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/04-cleanup-placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_deploy_tls/04-errors.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/00-cleanup-placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/01-deploy_placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/02-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/02-patch_placement_deploy.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/03-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/03-scale-placementapi.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/04-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/04-scale-down-placementapi.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/05-assert.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/05-scale-down-zero-placementapi.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/06-cleanup-placement.yaml (100%) rename {placement-import/test/kuttl => test/kuttl/placement}/tests/placement_scale/06-errors.yaml (100%) diff --git a/placement-import/test/functional/placementapi_controller_test.go b/test/functional/placement/api_controller_test.go similarity index 100% rename from placement-import/test/functional/placementapi_controller_test.go rename to test/functional/placement/api_controller_test.go diff --git a/placement-import/test/functional/placementapi_webhook_test.go b/test/functional/placement/api_webhook_test.go similarity index 100% rename from placement-import/test/functional/placementapi_webhook_test.go rename to test/functional/placement/api_webhook_test.go diff --git a/placement-import/test/functional/base_test.go b/test/functional/placement/base_test.go similarity index 100% rename from placement-import/test/functional/base_test.go rename to test/functional/placement/base_test.go diff --git a/placement-import/test/functional/suite_test.go b/test/functional/placement/suite_test.go similarity index 100% rename from placement-import/test/functional/suite_test.go rename to test/functional/placement/suite_test.go diff --git a/placement-import/test/kuttl/common/assert_sample_deployment.yaml b/test/kuttl/placement/common/assert_sample_deployment.yaml similarity index 100% rename from placement-import/test/kuttl/common/assert_sample_deployment.yaml rename to test/kuttl/placement/common/assert_sample_deployment.yaml diff --git a/placement-import/test/kuttl/common/cleanup-placement.yaml b/test/kuttl/placement/common/cleanup-placement.yaml similarity index 100% rename from placement-import/test/kuttl/common/cleanup-placement.yaml rename to test/kuttl/placement/common/cleanup-placement.yaml diff --git a/placement-import/test/kuttl/common/errors_cleanup_placement.yaml b/test/kuttl/placement/common/errors_cleanup_placement.yaml similarity index 100% rename from placement-import/test/kuttl/common/errors_cleanup_placement.yaml rename to test/kuttl/placement/common/errors_cleanup_placement.yaml diff --git a/placement-import/test/kuttl/common/patch_placement_deploy.yaml b/test/kuttl/placement/common/patch_placement_deploy.yaml similarity index 100% rename from placement-import/test/kuttl/common/patch_placement_deploy.yaml rename to test/kuttl/placement/common/patch_placement_deploy.yaml diff --git a/placement-import/test/kuttl/common/placementapi_deploy.yaml b/test/kuttl/placement/common/placementapi_deploy.yaml similarity index 100% rename from placement-import/test/kuttl/common/placementapi_deploy.yaml rename to test/kuttl/placement/common/placementapi_deploy.yaml diff --git a/placement-import/test/kuttl/common/tls_certificates.yaml b/test/kuttl/placement/common/tls_certificates.yaml similarity index 100% rename from placement-import/test/kuttl/common/tls_certificates.yaml rename to test/kuttl/placement/common/tls_certificates.yaml diff --git a/placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate b/test/kuttl/placement/go_templates/apiEndpoints.gotemplate similarity index 100% rename from placement-import/test/kuttl/go_templates/apiEndpoints.gotemplate rename to test/kuttl/placement/go_templates/apiEndpoints.gotemplate diff --git a/placement-import/test/kuttl/lib/helper_functions.sh b/test/kuttl/placement/lib/helper_functions.sh similarity index 100% rename from placement-import/test/kuttl/lib/helper_functions.sh rename to test/kuttl/placement/lib/helper_functions.sh diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml b/test/kuttl/placement/tests/placement_deploy_tls/00-cleanup-placement.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/00-cleanup-placement.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/00-cleanup-placement.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml b/test/kuttl/placement/tests/placement_deploy_tls/01-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/01-assert.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/01-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml b/test/kuttl/placement/tests/placement_deploy_tls/01-tls_certificates.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/01-tls_certificates.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/01-tls_certificates.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml b/test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml b/test/kuttl/placement/tests/placement_deploy_tls/03-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/03-assert.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/03-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml b/test/kuttl/placement/tests/placement_deploy_tls/03-patch_placement_deploy.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/03-patch_placement_deploy.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/03-patch_placement_deploy.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml b/test/kuttl/placement/tests/placement_deploy_tls/04-cleanup-placement.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/04-cleanup-placement.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/04-cleanup-placement.yaml diff --git a/placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml b/test/kuttl/placement/tests/placement_deploy_tls/04-errors.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_deploy_tls/04-errors.yaml rename to test/kuttl/placement/tests/placement_deploy_tls/04-errors.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml b/test/kuttl/placement/tests/placement_scale/00-cleanup-placement.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/00-cleanup-placement.yaml rename to test/kuttl/placement/tests/placement_scale/00-cleanup-placement.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml b/test/kuttl/placement/tests/placement_scale/01-deploy_placement.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/01-deploy_placement.yaml rename to test/kuttl/placement/tests/placement_scale/01-deploy_placement.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/02-assert.yaml b/test/kuttl/placement/tests/placement_scale/02-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/02-assert.yaml rename to test/kuttl/placement/tests/placement_scale/02-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml b/test/kuttl/placement/tests/placement_scale/02-patch_placement_deploy.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/02-patch_placement_deploy.yaml rename to test/kuttl/placement/tests/placement_scale/02-patch_placement_deploy.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/03-assert.yaml b/test/kuttl/placement/tests/placement_scale/03-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/03-assert.yaml rename to test/kuttl/placement/tests/placement_scale/03-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml b/test/kuttl/placement/tests/placement_scale/03-scale-placementapi.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/03-scale-placementapi.yaml rename to test/kuttl/placement/tests/placement_scale/03-scale-placementapi.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/04-assert.yaml b/test/kuttl/placement/tests/placement_scale/04-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/04-assert.yaml rename to test/kuttl/placement/tests/placement_scale/04-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml b/test/kuttl/placement/tests/placement_scale/04-scale-down-placementapi.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/04-scale-down-placementapi.yaml rename to test/kuttl/placement/tests/placement_scale/04-scale-down-placementapi.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/05-assert.yaml b/test/kuttl/placement/tests/placement_scale/05-assert.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/05-assert.yaml rename to test/kuttl/placement/tests/placement_scale/05-assert.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml b/test/kuttl/placement/tests/placement_scale/05-scale-down-zero-placementapi.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/05-scale-down-zero-placementapi.yaml rename to test/kuttl/placement/tests/placement_scale/05-scale-down-zero-placementapi.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml b/test/kuttl/placement/tests/placement_scale/06-cleanup-placement.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/06-cleanup-placement.yaml rename to test/kuttl/placement/tests/placement_scale/06-cleanup-placement.yaml diff --git a/placement-import/test/kuttl/tests/placement_scale/06-errors.yaml b/test/kuttl/placement/tests/placement_scale/06-errors.yaml similarity index 100% rename from placement-import/test/kuttl/tests/placement_scale/06-errors.yaml rename to test/kuttl/placement/tests/placement_scale/06-errors.yaml From 7d954799ba9da634afccffec5cb77c9c48ba9e64 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:26:19 +0530 Subject: [PATCH 08/16] Move placement CRD and cleanup placement-import Move placement CRD to config/crd/bases/ and remove all remaining placement-import/ files --- ...placement.openstack.org_placementapis.yaml | 0 placement-import/.ci-operator.yaml | 4 - placement-import/.dockerignore | 4 - .../workflows/build-placement-operator.yaml | 26 - .../workflows/force-bump-pr-manual.yaml | 14 - .../workflows/force-bump-pr-scheduled.yaml | 15 - placement-import/.github/workflows/lints.yaml | 14 - .../workflows/release-branch-sync.yaml | 12 - .../workflows/release-placement-operator.yaml | 48 -- placement-import/.gitignore | 37 -- placement-import/.golangci.yaml | 20 - placement-import/.pre-commit-config.yaml | 75 --- placement-import/.prow_ci.env | 2 - placement-import/.pull_request_pipeline | 39 -- placement-import/.zuul.yaml | 9 - placement-import/Dockerfile | 75 --- placement-import/LICENSE.txt | 202 ------- placement-import/Makefile | 399 -------------- placement-import/OWNERS | 8 - placement-import/OWNERS_ALIASES | 15 - placement-import/PROJECT | 27 - placement-import/README.md | 178 ------- placement-import/api/go.mod | 95 ---- placement-import/api/go.sum | 203 ------- placement-import/cmd/main.go | 303 ----------- .../certmanager/certificate-metrics.yaml | 20 - .../certmanager/certificate-webhook.yaml | 20 - .../config/certmanager/issuer.yaml | 13 - .../config/certmanager/kustomization.yaml | 7 - .../config/certmanager/kustomizeconfig.yaml | 8 - ...placement.openstack.org_placementapis.yaml | 502 ------------------ .../config/crd/kustomization.yaml | 16 - .../config/crd/kustomizeconfig.yaml | 19 - .../default/cert_metrics_manager_patch.yaml | 30 -- .../config/default/kustomization.yaml | 236 -------- .../default/manager_default_images.yaml | 15 - .../config/default/manager_metrics_patch.yaml | 4 - .../config/default/manager_webhook_patch.yaml | 31 -- .../config/default/metrics_service.yaml | 18 - .../config/manager/kustomization.yaml | 8 - placement-import/config/manager/manager.yaml | 98 ---- ...cement-operator.clusterserviceversion.yaml | 63 --- .../config/manifests/kustomization.yaml | 28 - .../network-policy/allow-metrics-traffic.yaml | 27 - .../network-policy/allow-webhook-traffic.yaml | 27 - .../config/network-policy/kustomization.yaml | 3 - .../config/prometheus/kustomization.yaml | 11 - .../config/prometheus/monitor.yaml | 27 - .../config/prometheus/monitor_tls_patch.yaml | 19 - .../config/rbac/kustomization.yaml | 27 - .../config/rbac/leader_election_role.yaml | 40 -- .../rbac/leader_election_role_binding.yaml | 15 - .../config/rbac/metrics_auth_role.yaml | 17 - .../rbac/metrics_auth_role_binding.yaml | 12 - .../config/rbac/metrics_reader_role.yaml | 9 - .../config/rbac/placementapi_admin_role.yaml | 27 - .../config/rbac/placementapi_editor_role.yaml | 33 -- .../config/rbac/placementapi_viewer_role.yaml | 29 - placement-import/config/rbac/role.yaml | 162 ------ .../config/rbac/role_binding.yaml | 15 - .../config/rbac/service_account.yaml | 8 - .../config/samples/kustomization.yaml | 4 - .../placement_v1beta1_placementapi.yaml | 18 - .../placement_v1beta1_placementtls.yaml | 21 - .../config/scorecard/bases/config.yaml | 7 - .../config/scorecard/kustomization.yaml | 18 - .../scorecard/patches/basic.config.yaml | 10 - .../config/scorecard/patches/olm.config.yaml | 50 -- .../config/webhook/kustomization.yaml | 6 - .../config/webhook/kustomizeconfig.yaml | 22 - .../config/webhook/manifests.yaml | 52 -- placement-import/config/webhook/service.yaml | 16 - placement-import/go.mod | 144 ----- placement-import/go.sum | 305 ----------- placement-import/hack/boilerplate.go.txt | 15 - .../hack/build-crd-schema-checker.sh | 15 - placement-import/hack/clean_local_webhook.sh | 5 - placement-import/hack/crd-schema-checker.sh | 22 - .../hack/run_with_local_webhook.sh | 139 ----- placement-import/kuttl-test.yaml | 40 -- placement-import/renovate.json | 16 - 81 files changed, 4393 deletions(-) rename {placement-import/api => config/crd}/bases/placement.openstack.org_placementapis.yaml (100%) delete mode 100644 placement-import/.ci-operator.yaml delete mode 100644 placement-import/.dockerignore delete mode 100644 placement-import/.github/workflows/build-placement-operator.yaml delete mode 100644 placement-import/.github/workflows/force-bump-pr-manual.yaml delete mode 100644 placement-import/.github/workflows/force-bump-pr-scheduled.yaml delete mode 100644 placement-import/.github/workflows/lints.yaml delete mode 100644 placement-import/.github/workflows/release-branch-sync.yaml delete mode 100644 placement-import/.github/workflows/release-placement-operator.yaml delete mode 100644 placement-import/.gitignore delete mode 100644 placement-import/.golangci.yaml delete mode 100644 placement-import/.pre-commit-config.yaml delete mode 100644 placement-import/.prow_ci.env delete mode 100644 placement-import/.pull_request_pipeline delete mode 100644 placement-import/.zuul.yaml delete mode 100644 placement-import/Dockerfile delete mode 100644 placement-import/LICENSE.txt delete mode 100644 placement-import/Makefile delete mode 100644 placement-import/OWNERS delete mode 100644 placement-import/OWNERS_ALIASES delete mode 100644 placement-import/PROJECT delete mode 100644 placement-import/README.md delete mode 100644 placement-import/api/go.mod delete mode 100644 placement-import/api/go.sum delete mode 100644 placement-import/cmd/main.go delete mode 100644 placement-import/config/certmanager/certificate-metrics.yaml delete mode 100644 placement-import/config/certmanager/certificate-webhook.yaml delete mode 100644 placement-import/config/certmanager/issuer.yaml delete mode 100644 placement-import/config/certmanager/kustomization.yaml delete mode 100644 placement-import/config/certmanager/kustomizeconfig.yaml delete mode 100644 placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml delete mode 100644 placement-import/config/crd/kustomization.yaml delete mode 100644 placement-import/config/crd/kustomizeconfig.yaml delete mode 100644 placement-import/config/default/cert_metrics_manager_patch.yaml delete mode 100644 placement-import/config/default/kustomization.yaml delete mode 100644 placement-import/config/default/manager_default_images.yaml delete mode 100644 placement-import/config/default/manager_metrics_patch.yaml delete mode 100644 placement-import/config/default/manager_webhook_patch.yaml delete mode 100644 placement-import/config/default/metrics_service.yaml delete mode 100644 placement-import/config/manager/kustomization.yaml delete mode 100644 placement-import/config/manager/manager.yaml delete mode 100644 placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml delete mode 100644 placement-import/config/manifests/kustomization.yaml delete mode 100644 placement-import/config/network-policy/allow-metrics-traffic.yaml delete mode 100644 placement-import/config/network-policy/allow-webhook-traffic.yaml delete mode 100644 placement-import/config/network-policy/kustomization.yaml delete mode 100644 placement-import/config/prometheus/kustomization.yaml delete mode 100644 placement-import/config/prometheus/monitor.yaml delete mode 100644 placement-import/config/prometheus/monitor_tls_patch.yaml delete mode 100644 placement-import/config/rbac/kustomization.yaml delete mode 100644 placement-import/config/rbac/leader_election_role.yaml delete mode 100644 placement-import/config/rbac/leader_election_role_binding.yaml delete mode 100644 placement-import/config/rbac/metrics_auth_role.yaml delete mode 100644 placement-import/config/rbac/metrics_auth_role_binding.yaml delete mode 100644 placement-import/config/rbac/metrics_reader_role.yaml delete mode 100644 placement-import/config/rbac/placementapi_admin_role.yaml delete mode 100644 placement-import/config/rbac/placementapi_editor_role.yaml delete mode 100644 placement-import/config/rbac/placementapi_viewer_role.yaml delete mode 100644 placement-import/config/rbac/role.yaml delete mode 100644 placement-import/config/rbac/role_binding.yaml delete mode 100644 placement-import/config/rbac/service_account.yaml delete mode 100644 placement-import/config/samples/kustomization.yaml delete mode 100644 placement-import/config/samples/placement_v1beta1_placementapi.yaml delete mode 100644 placement-import/config/samples/placement_v1beta1_placementtls.yaml delete mode 100644 placement-import/config/scorecard/bases/config.yaml delete mode 100644 placement-import/config/scorecard/kustomization.yaml delete mode 100644 placement-import/config/scorecard/patches/basic.config.yaml delete mode 100644 placement-import/config/scorecard/patches/olm.config.yaml delete mode 100644 placement-import/config/webhook/kustomization.yaml delete mode 100644 placement-import/config/webhook/kustomizeconfig.yaml delete mode 100644 placement-import/config/webhook/manifests.yaml delete mode 100644 placement-import/config/webhook/service.yaml delete mode 100644 placement-import/go.mod delete mode 100644 placement-import/go.sum delete mode 100644 placement-import/hack/boilerplate.go.txt delete mode 100755 placement-import/hack/build-crd-schema-checker.sh delete mode 100755 placement-import/hack/clean_local_webhook.sh delete mode 100755 placement-import/hack/crd-schema-checker.sh delete mode 100755 placement-import/hack/run_with_local_webhook.sh delete mode 100644 placement-import/kuttl-test.yaml delete mode 100644 placement-import/renovate.json diff --git a/placement-import/api/bases/placement.openstack.org_placementapis.yaml b/config/crd/bases/placement.openstack.org_placementapis.yaml similarity index 100% rename from placement-import/api/bases/placement.openstack.org_placementapis.yaml rename to config/crd/bases/placement.openstack.org_placementapis.yaml diff --git a/placement-import/.ci-operator.yaml b/placement-import/.ci-operator.yaml deleted file mode 100644 index df7900c64..000000000 --- a/placement-import/.ci-operator.yaml +++ /dev/null @@ -1,4 +0,0 @@ -build_root_image: - name: tools - namespace: openstack-k8s-operators - tag: ci-build-root-golang-1.24-sdk-1.41.1 diff --git a/placement-import/.dockerignore b/placement-import/.dockerignore deleted file mode 100644 index 0f046820f..000000000 --- a/placement-import/.dockerignore +++ /dev/null @@ -1,4 +0,0 @@ -# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file -# Ignore build and test binaries. -bin/ -testbin/ diff --git a/placement-import/.github/workflows/build-placement-operator.yaml b/placement-import/.github/workflows/build-placement-operator.yaml deleted file mode 100644 index 01d1f0107..000000000 --- a/placement-import/.github/workflows/build-placement-operator.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: placement operator image builder - -on: - push: - branches: - - '*' - workflow_dispatch: # This allows manual triggering from the Actions tab if needed - -env: - imageregistry: 'quay.io' - imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} - latesttag: latest - -jobs: - call-build-workflow: - uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/reusable-build-operator.yaml@main - with: - operator_name: placement - go_version: 1.24.x - operator_sdk_version: 1.41.1 - secrets: - IMAGENAMESPACE: ${{ secrets.IMAGENAMESPACE }} - QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }} - QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }} - REDHATIO_USERNAME: ${{ secrets.REDHATIO_USERNAME }} - REDHATIO_PASSWORD: ${{ secrets.REDHATIO_PASSWORD }} diff --git a/placement-import/.github/workflows/force-bump-pr-manual.yaml b/placement-import/.github/workflows/force-bump-pr-manual.yaml deleted file mode 100644 index 45e1b740d..000000000 --- a/placement-import/.github/workflows/force-bump-pr-manual.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: Manually Trigger a Force Bump PR - -on: - workflow_dispatch: - -jobs: - call-build-workflow: - uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-pull-request.yaml@main - with: - operator_name: placement - branch_name: ${{ github.ref_name }} - custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 - secrets: - FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} diff --git a/placement-import/.github/workflows/force-bump-pr-scheduled.yaml b/placement-import/.github/workflows/force-bump-pr-scheduled.yaml deleted file mode 100644 index dfc208411..000000000 --- a/placement-import/.github/workflows/force-bump-pr-scheduled.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: Scheduled Force Bump PR - -on: - schedule: - - cron: '0 6 * * 6' # 6AM UTC Saturday - -jobs: - call-build-workflow: - if: github.ref == 'refs/heads/main' && github.repository_owner == 'openstack-k8s-operators' - uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/force-bump-branches.yaml@main - with: - operator_name: placement - custom_image: quay.io/openstack-k8s-operators/openstack-k8s-operators-ci-build-tools:golang-1.24-sdk-1.41.1 - secrets: - FORCE_BUMP_PULL_REQUEST_PAT: ${{ secrets.FORCE_BUMP_PULL_REQUEST_PAT }} diff --git a/placement-import/.github/workflows/lints.yaml b/placement-import/.github/workflows/lints.yaml deleted file mode 100644 index 11281ae8c..000000000 --- a/placement-import/.github/workflows/lints.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: Lints - -on: [pull_request] - -jobs: - check-go-mod-replace-lines: - name: check for replace lines in go.mod files - runs-on: ubuntu-latest - steps: - - name: Checkout project code - uses: actions/checkout@v3 - - name: check for replace lines in go.mod files - run: | - ! egrep --invert-match -e '^replace.*/api => \./api|^replace.*//allow-merging$' `find . -name 'go.mod'` | egrep -e 'go.mod:replace' diff --git a/placement-import/.github/workflows/release-branch-sync.yaml b/placement-import/.github/workflows/release-branch-sync.yaml deleted file mode 100644 index db63bb7a8..000000000 --- a/placement-import/.github/workflows/release-branch-sync.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: Release Branch sync - -on: - push: - branches: - - '*' - schedule: - - cron: '0 * * * *' - -jobs: - call-build-workflow: - uses: openstack-k8s-operators/openstack-k8s-operators-ci/.github/workflows/release-branch-sync.yaml@main diff --git a/placement-import/.github/workflows/release-placement-operator.yaml b/placement-import/.github/workflows/release-placement-operator.yaml deleted file mode 100644 index 62d9aaa24..000000000 --- a/placement-import/.github/workflows/release-placement-operator.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Release Placement Operator - -on: - release: - types: - - released - - prereleased - -env: - imageregistry: 'quay.io' - imagenamespace: ${{ secrets.IMAGENAMESPACE || secrets.QUAY_USERNAME }} - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Tag image - uses: tinact/docker.image-retag@1.0.2 - with: - image_name: ${{ env.imagenamespace }}/ - image_old_tag: ${{ github.sha }} - image_new_tag: ${{ github.event.release.tag_name }} - registry: ${{ env.imageregistry }} - registry_username: ${{ secrets.QUAY_USERNAME }} - registry_password: ${{ secrets.QUAY_PASSWORD }} - - - name: Tag -bundle image - uses: tinact/docker.image-retag@1.0.2 - with: - image_name: ${{ env.imagenamespace }}/-bundle - image_old_tag: ${{ github.sha }} - image_new_tag: ${{ github.event.release.tag_name }} - registry: ${{ env.imageregistry }} - registry_username: ${{ secrets.QUAY_USERNAME }} - registry_password: ${{ secrets.QUAY_PASSWORD }} - - - name: Tag -index image - uses: tinact/docker.image-retag@1.0.2 - with: - image_name: ${{ env.imagenamespace }}/-index - image_old_tag: ${{ github.sha }} - image_new_tag: ${{ github.event.release.tag_name }} - registry: ${{ env.imageregistry }} - registry_username: ${{ secrets.QUAY_USERNAME }} - registry_password: ${{ secrets.QUAY_PASSWORD }} diff --git a/placement-import/.gitignore b/placement-import/.gitignore deleted file mode 100644 index 1098b4058..000000000 --- a/placement-import/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -/bin -testbin/* - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Kubernetes Generated files - skip generated files, except for vendored files - -!vendor/**/zz_generated.* - -#Operator SDK generated files -/bundle/ -bundle.Dockerfile -config/manager/kustomization.yaml - -# editor and IDE paraphernalia -.idea -*.swp -*.swo -*~ - -# Common CI tools repository -CI_TOOLS_REPO - -# generated workspace file -go.work -go.work.sum diff --git a/placement-import/.golangci.yaml b/placement-import/.golangci.yaml deleted file mode 100644 index bfdf6541c..000000000 --- a/placement-import/.golangci.yaml +++ /dev/null @@ -1,20 +0,0 @@ -version: 2 - -linters: - # Enable specific linter - # https://golangci-lint.run/usage/linters/#enabled-by-default - enable: - - errorlint - - revive - - ginkgolinter - - govet - - gosec - - errname - - err113 - -formatters: - enable: - - gofmt - -run: - timeout: 5m diff --git a/placement-import/.pre-commit-config.yaml b/placement-import/.pre-commit-config.yaml deleted file mode 100644 index 5f6e53833..000000000 --- a/placement-import/.pre-commit-config.yaml +++ /dev/null @@ -1,75 +0,0 @@ -repos: -- repo: local - hooks: - - id: make-manifests - name: make-manifests - language: system - entry: make - args: ['manifests'] - pass_filenames: false - - id: make-generate - name: make-generate - language: system - entry: make - args: ['generate'] - pass_filenames: false - - id: make-operator-lint - name: make-operator-lint - language: system - entry: make - args: ['operator-lint'] - pass_filenames: false - - id: make-bundle - name: make-bundle - language: system - entry: make - args: ['bundle', 'VERSION=0.0.1'] - pass_filenames: false - - id: make-crd-schema-check - name: make-crd-schema-check - language: system - entry: make - args: ['crd-schema-check'] - pass_filenames: false - -- repo: https://github.com/dnephin/pre-commit-golang - rev: v0.5.1 - hooks: - - id: go-mod-tidy - -- repo: https://github.com/golangci/golangci-lint - rev: v2.7.2 - hooks: - - id: golangci-lint-full - args: ["--verbose"] - -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: check-added-large-files - - id: fix-byte-order-marker - - id: check-case-conflict - - id: check-executables-have-shebangs - exclude: ^vendor - - id: check-shebang-scripts-are-executable - exclude: ^vendor - - id: check-merge-conflict - - id: check-symlinks - - id: destroyed-symlinks - - id: check-yaml - args: [-m] - - id: check-json - - id: detect-private-key - - id: end-of-file-fixer - exclude: ^vendor - - id: no-commit-to-branch - - id: trailing-whitespace - exclude: ^vendor - -- repo: https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci - # NOTE(gibi): we cannot automatically track main here - # see https://pre-commit.com/#using-the-latest-version-for-a-repository - rev: e30d72fcbced0ab8a7b6d23be1dee129e2a7b849 - hooks: - - id: kuttl-single-test-assert - args: ["test/kuttl"] diff --git a/placement-import/.prow_ci.env b/placement-import/.prow_ci.env deleted file mode 100644 index ce722ac77..000000000 --- a/placement-import/.prow_ci.env +++ /dev/null @@ -1,2 +0,0 @@ -export USE_IMAGE_DIGESTS=true -export FAIL_FIPS_CHECK=true diff --git a/placement-import/.pull_request_pipeline b/placement-import/.pull_request_pipeline deleted file mode 100644 index 7f7c0ed30..000000000 --- a/placement-import/.pull_request_pipeline +++ /dev/null @@ -1,39 +0,0 @@ -pipeline { - agent none - stages { - stage('Pull request proposed jobs') { - when { - branch 'PR-*' - } - parallel { - stage('DFG-converged-openstack-k8s-placement-operator-distgit-pr') { - steps { - build job: 'DFG-converged-openstack-k8s-placement-operator-distgit-pr', - parameters: [ - string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) - ] - } - } - stage('DFG-converged-openstack-k8s-placement-operator-buildah') { - steps { - build job: 'DFG-converged-openstack-k8s-placement-operator-buildah', - parameters: [ - string(name: 'GITHUB_PULL_REQUEST_ID', value: String.valueOf(CHANGE_ID)) - ] - } - } - } - } - stage('Pull request merged jobs') { - when { - allOf { - environment name: 'CHANGE_ID', value: '' - branch 'main' - } - } - steps { - build job: 'DFG-converged-openstack-k8s-placement-operator-merge' - } - } - } -} diff --git a/placement-import/.zuul.yaml b/placement-import/.zuul.yaml deleted file mode 100644 index f06cdb96c..000000000 --- a/placement-import/.zuul.yaml +++ /dev/null @@ -1,9 +0,0 @@ ---- - -- project: - name: openstack-k8s-operators/placement-operator - default-branch: main - merge-mode: rebase - github-check: - jobs: - - noop diff --git a/placement-import/Dockerfile b/placement-import/Dockerfile deleted file mode 100644 index cbc768ab6..000000000 --- a/placement-import/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ -ARG GOLANG_BUILDER=registry.access.redhat.com/ubi9/go-toolset:1.24 -ARG OPERATOR_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:9.6 - -# Build the manager binary -FROM $GOLANG_BUILDER AS builder - -#Arguments required by OSBS build system -ARG CACHITO_ENV_FILE=/remote-source/cachito.env - -ARG REMOTE_SOURCE=. -ARG REMOTE_SOURCE_DIR=/remote-source -ARG REMOTE_SOURCE_SUBDIR= -ARG DEST_ROOT=/dest-root -ARG GO_BUILD_EXTRA_ARGS="-tags strictfipsruntime" -ARG GO_BUILD_EXTRA_ENV_ARGS="CGO_ENABLED=1 GO111MODULE=on" - -COPY $REMOTE_SOURCE $REMOTE_SOURCE_DIR -WORKDIR $REMOTE_SOURCE_DIR/$REMOTE_SOURCE_SUBDIR -USER root -RUN mkdir -p ${DEST_ROOT}/usr/local/bin/ - -# 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 if [ ! -f $CACHITO_ENV_FILE ]; then go mod download ; fi - -# Build manager -RUN if [ -f $CACHITO_ENV_FILE ] ; then source $CACHITO_ENV_FILE ; fi ; env ${GO_BUILD_EXTRA_ENV_ARGS} go build ${GO_BUILD_EXTRA_ARGS} -a -o ${DEST_ROOT}/manager cmd/main.go - -RUN cp -r templates ${DEST_ROOT}/templates - -# Use distroless as minimal base image to package the manager binary -# Refer to https://github.com/GoogleContainerTools/distroless for more details -FROM $OPERATOR_BASE_IMAGE - -ARG DEST_ROOT=/dest-root -# NONROOT default id https://github.com/GoogleContainerTools/distroless/blob/main/base/base.bzl#L8= -ARG USER_ID=65532 - -ARG IMAGE_COMPONENT="placement-operator-container" -ARG IMAGE_NAME="placement-operator" -ARG IMAGE_VERSION="1.0.0" -ARG IMAGE_SUMMARY="Placement Operator" -ARG IMAGE_DESC="This image includes the placement-operator" -ARG IMAGE_TAGS="cn-openstack openstack" - -### DO NOT EDIT LINES BELOW -# Auto generated using CI tools from -# https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci - -# Labels required by upstream and osbs build system -LABEL com.redhat.component="${IMAGE_COMPONENT}" \ - name="${IMAGE_NAME}" \ - version="${IMAGE_VERSION}" \ - summary="${IMAGE_SUMMARY}" \ - io.k8s.name="${IMAGE_NAME}" \ - io.k8s.description="${IMAGE_DESC}" \ - io.openshift.tags="${IMAGE_TAGS}" -### DO NOT EDIT LINES ABOVE - -ENV USER_UID=$USER_ID \ - OPERATOR_TEMPLATES=/usr/share/placement-operator/templates/ - -WORKDIR / - -# Install operator binary to WORKDIR -COPY --from=builder ${DEST_ROOT}/manager . - -# Install templates -COPY --from=builder ${DEST_ROOT}/templates ${OPERATOR_TEMPLATES} - -USER $USER_ID - -ENV PATH="/:${PATH}" - -ENTRYPOINT ["/manager"] diff --git a/placement-import/LICENSE.txt b/placement-import/LICENSE.txt deleted file mode 100644 index d64569567..000000000 --- a/placement-import/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/placement-import/Makefile b/placement-import/Makefile deleted file mode 100644 index 0e4645783..000000000 --- a/placement-import/Makefile +++ /dev/null @@ -1,399 +0,0 @@ -# VERSION defines the project version for the bundle. -# Update this value when you upgrade the version of your project. -# To re-generate a bundle for another specific version without changing the standard setup, you can: -# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) -# - use environment variables to overwrite this value (e.g export VERSION=0.0.2) -VERSION ?= 0.0.1 - -# CHANNELS define the bundle channels used in the bundle. -# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") -# To re-generate a bundle for other specific channels without changing the standard setup, you can: -# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) -# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") -ifneq ($(origin CHANNELS), undefined) -BUNDLE_CHANNELS := --channels=$(CHANNELS) -endif - -# DEFAULT_CHANNEL defines the default channel used in the bundle. -# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") -# To re-generate a bundle for any other default channel without changing the default setup, you can: -# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) -# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") -ifneq ($(origin DEFAULT_CHANNEL), undefined) -BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) -endif -BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) - -# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. -# This variable is used to construct full image tags for bundle and catalog images. -# -# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both -# openstack.org/placement-operator-bundle:$VERSION and openstack.org/placement-operator-catalog:$VERSION. -IMAGE_TAG_BASE ?= quay.io/$(USER)/placement-operator - -# BUNDLE_IMG defines the image:tag used for the bundle. -# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) -BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) - -# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command -BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - -# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests -# You can enable this value if you would like to use SHA Based Digests -# To enable set flag to true -USE_IMAGE_DIGESTS ?= false -ifeq ($(USE_IMAGE_DIGESTS), true) - BUNDLE_GEN_FLAGS += --use-image-digests -endif - -# Set the Operator SDK version to use. By default, what is installed on the system is used. -# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. -OPERATOR_SDK_VERSION ?= v1.41.1 - -# Image URL to use all building/pushing image targets -DEFAULT_IMG ?= quay.io/openstack-k8s-operators/placement-operator:latest -IMG ?= $(DEFAULT_IMG) -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.31 - -SETUP_ENVTEST_VERSION ?= release-0.22 - -# 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 - -# 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 - -GOTOOLCHAIN_VERSION ?= go1.24.0 - -.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 commands 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 && \ - rm -f api/bases/* && cp -a config/crd/bases api/ - -.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: gowork ## Run go vet against code. - go vet ./... - go vet ./api/... - -.PHONY: test -test: manifests generate gowork fmt vet envtest ginkgo ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" $(GINKGO) --trace --cover --coverpkg=../../internal/...,../../api/v1beta1/... --coverprofile cover.out --covermode=atomic --randomize-all $(GINKGO_ARGS) ./test/... ./internal/... - -##@ Build - -.PHONY: build -build: generate fmt vet ## Build manager binary. - go build -o bin/manager cmd/main.go - -.PHONY: run -run: export METRICS_PORT?=8080 -run: export HEALTH_PORT?=8081 -run: export PPROF_PORT?=8082 -run: export ENABLE_WEBHOOKS?=false -run: manifests generate fmt vet ## Run a controller from your host. - /bin/bash hack/clean_local_webhook.sh - go run ./cmd/main.go -metrics-bind-address ":$(METRICS_PORT)" -health-probe-bind-address ":$(HEALTH_PORT)" -pprof-bind-address ":$(PPROF_PORT)" - - -# Extra vars which will be passed to the Docker-build -DOCKER_BUILD_ARGS ?= - -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - podman build -t ${IMG} . ${DOCKER_BUILD_ARGS} - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - podman push ${IMG} - -# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: -# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ -# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> than the export will fail) -# To properly provided solutions that supports more than one platform you should use this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: test ## 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 - - docker buildx create --name project-v3-builder - docker buildx use project-v3-builder - - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross - - docker buildx rm project-v3-builder - rm Dockerfile.cross - -##@ 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: ## 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 - - -##@ Build Dependencies - -## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin -$(LOCALBIN): - mkdir -p $(LOCALBIN) - -## Tool Binaries -KUSTOMIZE ?= $(LOCALBIN)/kustomize -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen -ENVTEST ?= $(LOCALBIN)/setup-envtest -GINKGO ?= $(LOCALBIN)/ginkgo - -## Tool Versions -KUSTOMIZE_VERSION ?= v5.6.0 -CONTROLLER_TOOLS_VERSION ?= v0.18.0 - -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. -$(KUSTOMIZE): $(LOCALBIN) - @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ - echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ - rm -rf $(LOCALBIN)/kustomize; \ - fi - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN); } - -.PHONY: controller-gen -controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. If wrong version is installed, it will be overwritten. -$(CONTROLLER_GEN): $(LOCALBIN) - test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ - GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) - -.PHONY: envtest -envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. -$(ENVTEST): $(LOCALBIN) - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@$(SETUP_ENVTEST_VERSION) - -.PHONY: ginkgo -ginkgo: $(GINKGO) ## Download ginkgo locally if necessary. -$(GINKGO): $(LOCALBIN) - test -s $(LOCALBIN)/ginkgo || GOBIN=$(LOCALBIN) go install github.com/onsi/ginkgo/v2/ginkgo - -.PHONY: operator-sdk -OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk -operator-sdk: ## Download operator-sdk locally if necessary. -ifeq (,$(wildcard $(OPERATOR_SDK))) -ifeq (, $(shell which operator-sdk 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPERATOR_SDK)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\ - chmod +x $(OPERATOR_SDK) ;\ - } -else -OPERATOR_SDK = $(shell which operator-sdk) -endif -endif - -.PHONY: bundle -bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files. - $(OPERATOR_SDK) generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS) - $(OPERATOR_SDK) bundle validate ./bundle --verbose - -.PHONY: bundle-build -bundle-build: ## Build the bundle image. - podman build -f bundle.Dockerfile -t $(BUNDLE_IMG) . - -.PHONY: bundle-push -bundle-push: ## Push the bundle image. - $(MAKE) docker-push IMG=$(BUNDLE_IMG) - -.PHONY: opm -OPM = ./bin/opm -opm: ## Download opm locally if necessary. -ifeq (,$(wildcard $(OPM))) -ifeq (,$(shell which opm 2>/dev/null)) - @{ \ - set -e ;\ - mkdir -p $(dir $(OPM)) ;\ - OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.29.0/$${OS}-$${ARCH}-opm ;\ - chmod +x $(OPM) ;\ - } -else -OPM = $(shell which opm) -endif -endif - -# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). -# These images MUST exist in a registry and be pull-able. -BUNDLE_IMGS ?= $(BUNDLE_IMG) - -# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). -CATALOG_IMG ?= $(IMAGE_TAG_BASE)-index:v$(VERSION) - -# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. -ifneq ($(origin CATALOG_BASE_IMG), undefined) -FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) -endif - -# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. -# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: -# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator -.PHONY: catalog-build -catalog-build: opm ## Build a catalog image. - $(OPM) index add --container-tool podman --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) - -# Push the catalog image. -.PHONY: catalog-push -catalog-push: ## Push a catalog image. - $(MAKE) docker-push IMG=$(CATALOG_IMG) - - -# CI tools repo for running tests -CI_TOOLS_REPO := https://github.com/openstack-k8s-operators/openstack-k8s-operators-ci -CI_TOOLS_REPO_DIR = $(shell pwd)/CI_TOOLS_REPO -.PHONY: get-ci-tools -get-ci-tools: - if [ -d "$(CI_TOOLS_REPO_DIR)" ]; then \ - echo "Ci tools exists"; \ - pushd "$(CI_TOOLS_REPO_DIR)"; \ - git pull --rebase; \ - popd; \ - else \ - git clone $(CI_TOOLS_REPO) "$(CI_TOOLS_REPO_DIR)"; \ - fi - -# Run go fmt against code -gofmt: get-ci-tools - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gofmt.sh - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/gofmt.sh ./api - -# Run go vet against code -govet: get-ci-tools - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/govet.sh ./api - -# Run go test against code -gotest: test - -# Run golangci-lint test against code -golangci: get-ci-tools - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/golangci.sh - GOWORK=off $(CI_TOOLS_REPO_DIR)/test-runner/golangci.sh ./api - -# Run go lint against code -golint: get-ci-tools - GOWORK=off PATH=$(GOBIN):$(PATH); $(CI_TOOLS_REPO_DIR)/test-runner/golint.sh - GOWORK=off PATH=$(GOBIN):$(PATH); $(CI_TOOLS_REPO_DIR)/test-runner/golint.sh ./api - -.PHONY: gowork -gowork: ## Generate go.work file to support our multi module repository - test -f go.work || GOTOOLCHAIN=$(GOTOOLCHAIN_VERSION) go work init - go work use . - go work use ./api - go work sync - -.PHONY: operator-lint -operator-lint: gowork ## Runs operator-lint - GOBIN=$(LOCALBIN) go install github.com/gibizer/operator-lint@v0.3.0 - go vet -vettool=$(LOCALBIN)/operator-lint ./... ./api/... - -# Used for webhook testing -# The configure_local_webhook.sh script below will remove any OLM webhooks -# for the operator and also scale its deployment replicas down to 0 so that -# the operator can run locally. -# We will attempt to catch SIGINT/SIGTERM and clean up the local webhooks, -# but it may be necessary to manually run ./hack/clean_local_webhook.sh -# before deploying with OLM again for other untrappable signals. -SKIP_CERT ?=false -.PHONY: run-with-webhook -run-with-webhook: export METRICS_PORT?=8080 -run-with-webhook: export HEALTH_PORT?=8081 -run-with-webhook: export PPROF_PORT?=8082 -run-with-webhook: export WEBHOOK_PORT?=9443 -run-with-webhook: manifests generate fmt vet ## Run a controller from your host. - /bin/bash hack/clean_local_webhook.sh - /bin/bash hack/run_with_local_webhook.sh - -.PHONY: tidy -tidy: ## Run go mod tidy on every mod file in the repo - go mod tidy - cd ./api && go mod tidy - -OPERATOR_NAMESPACE ?= openstack-operators - -scale-down-placement-controller-csv: - @echo "Scaling placement-controller-manager to 0 in CSV" - oc patch csv -n $(OPERATOR_NAMESPACE) placement-operator.v0.0.1 --type json -p='[{"op": "replace", "path": "/spec/install/spec/deployments/0/spec/replicas", "value": 0}]' - @echo "Removing olm installed webhooks from CSV" - $(eval has_webhooks=$(shell oc get -o json csv placement-operator.v0.0.1 | jq ".spec.webhookdefinitions")) - if [ "$(has_webhooks)" != "null" ]; then \ - oc patch csv -n $(OPERATOR_NAMESPACE) placement-operator.v0.0.1 --type json -p='[{"op": "remove", "path": "/spec/webhookdefinitions"}]'; \ - fi - -CRD_SCHEMA_CHECKER_VERSION ?= release-4.16 -BRANCH ?= main -.PHONY: force-bump -force-bump: ## Force bump operator and lib-common dependencies - for dep in $$(cat go.mod | grep openstack-k8s-operators | grep -vE -- 'indirect|placement-operator|^replace' | awk '{print $$1}'); do \ - go get $$dep@$(BRANCH) ; \ - done - for dep in $$(cat api/go.mod | grep openstack-k8s-operators | grep -vE -- 'indirect|placement-operator|^replace' | awk '{print $$1}'); do \ - cd ./api && go get $$dep@$(BRANCH) && cd .. ; \ - done - -PHONY: crd-schema-check -crd-schema-check: manifests - INSTALL_DIR=$(LOCALBIN) CRD_SCHEMA_CHECKER_VERSION=$(CRD_SCHEMA_CHECKER_VERSION) hack/build-crd-schema-checker.sh - INSTALL_DIR=$(LOCALBIN) BASE_REF="$${PULL_BASE_SHA:-$(BRANCH)}" hack/crd-schema-checker.sh diff --git a/placement-import/OWNERS b/placement-import/OWNERS deleted file mode 100644 index 09bfbb548..000000000 --- a/placement-import/OWNERS +++ /dev/null @@ -1,8 +0,0 @@ -# See the OWNERS docs at https://go.k8s.io/owners -approvers: - - openstack-approvers - - compute-approvers - -reviewers: - - openstack-approvers - - compute-approvers diff --git a/placement-import/OWNERS_ALIASES b/placement-import/OWNERS_ALIASES deleted file mode 100644 index 5fb1e16da..000000000 --- a/placement-import/OWNERS_ALIASES +++ /dev/null @@ -1,15 +0,0 @@ -# See the OWNERS_ALIASES docs: https://git.k8s.io/community/contributors/guide/owners.md#owners_aliases - -aliases: - openstack-approvers: - - abays - - dprince - - stuggi - compute-approvers: - - gibizer - - SeanMooney - - bogdando - - kk7ds - - mrKisaoLamb - - auniyal61 - - ratailor diff --git a/placement-import/PROJECT b/placement-import/PROJECT deleted file mode 100644 index 2294a3bac..000000000 --- a/placement-import/PROJECT +++ /dev/null @@ -1,27 +0,0 @@ -# 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: openstack.org -layout: -- go.kubebuilder.io/v4 -plugins: - manifests.sdk.operatorframework.io/v2: {} - scorecard.sdk.operatorframework.io/v2: {} -projectName: placement-operator -repo: github.com/openstack-k8s-operators/placement-operator -resources: -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: openstack.org - group: placement - kind: PlacementAPI - path: github.com/openstack-k8s-operators/placement-operator/api/v1beta1 - version: v1beta1 - webhooks: - defaulting: true - validation: true - webhookVersion: v1 -version: "3" diff --git a/placement-import/README.md b/placement-import/README.md deleted file mode 100644 index 9b525a483..000000000 --- a/placement-import/README.md +++ /dev/null @@ -1,178 +0,0 @@ -# placement-operator - -A Kubernetes Operator built using the [Operator Framework](https://github.com/operator-framework) for Go. -The Operator provides a way to easily install and manage an OpenStack Placement installation on Kubernetes. -This Operator was developed using [TCIB](https://github.com/openstack-k8s-operators/tcib/blob/main/container-images/containers.yaml) containers for OpenStack. - -# Deployment - -The operator is intended to be deployed via OLM [Operator Lifecycle Manager](https://github.com/operator-framework/operator-lifecycle-manager) - -# API Example - -The Operator creates a custom PlacementAPI resource that can be used to create Placement API -instances within the cluster. Example CR to create a Placement API in your cluster: - -```yaml -apiVersion: placement.openstack.org/v1beta1 -kind: PlacementAPI -metadata: - name: placement -spec: - containerImage: quay.io/podified-antelope-centos9/openstack-placement-api:current-podified - databaseInstance: openstack - secret: placement-secret -``` - -## Example: configure Placement with additional networks - -The Placement spec can be used to configure Placement to have the pods -being attached to additional networks. - -Create a network-attachement-definition which then can be referenced -from the Placement API CR. - -``` ---- -apiVersion: k8s.cni.cncf.io/v1 -kind: NetworkAttachmentDefinition -metadata: - name: storage - namespace: openstack -spec: - config: | - { - "cniVersion": "0.3.1", - "name": "storage", - "type": "macvlan", - "master": "enp7s0.21", - "ipam": { - "type": "whereabouts", - "range": "172.18.0.0/24", - "range_start": "172.18.0.50", - "range_end": "172.18.0.100" - } - } -``` - -The following represents an example of Placement resource that can be used -to trigger the service deployment, and have the service pods attached to -the storage network using the above NetworkAttachmentDefinition. - -``` -apiVersion: placement.openstack.org/v1beta1 -kind: PlacementAPI -metadata: - name: placement -spec: - ... - networkAttachents: - - storage -... -``` - -When the service is up and running, it will now have an additional nic -configured for the storage network: - -``` -# oc rsh placement-75f5cd6595-kpfr2 -sh-5.1# ip a -1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 - link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 - inet 127.0.0.1/8 scope host lo - valid_lft forever preferred_lft forever - inet6 ::1/128 scope host - valid_lft forever preferred_lft forever -3: eth0@if298: mtu 1450 qdisc noqueue state UP group default - link/ether 0a:58:0a:82:01:18 brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 10.130.1.24/23 brd 10.130.1.255 scope global eth0 - valid_lft forever preferred_lft forever - inet6 fe80::4cf2:a3ff:feb0:932/64 scope link - valid_lft forever preferred_lft forever -4: net1@if26: mtu 1500 qdisc noqueue state UP group default - link/ether a2:f1:3b:12:fd:be brd ff:ff:ff:ff:ff:ff link-netnsid 0 - inet 172.18.0.52/24 brd 172.18.0.255 scope global net1 - valid_lft forever preferred_lft forever - inet6 fe80::a0f1:3bff:fe12:fdbe/64 scope link - valid_lft forever preferred_lft forever -``` - -## Example: expose Placement to an isolated network - -The Placement spec can be used to configure Placement to register e.g. -the internal endpoint to an isolated network. MetalLB is used for this -scenario. - -As a pre requisite, MetalLB needs to be installed and worker nodes -prepared to work as MetalLB nodes to serve the LoadBalancer service. - -In this example the following MetalLB IPAddressPool is used: - -``` ---- -apiVersion: metallb.io/v1beta1 -kind: IPAddressPool -metadata: - name: osp-internalapi - namespace: metallb-system -spec: - addresses: - - 172.17.0.200-172.17.0.210 - autoAssign: false -``` - -The following represents an example of Placement resource that can be used -to trigger the service deployment, and have the internal placementAPI endpoint -registerd as a MetalLB service using the IPAddressPool `osp-internal`, -request to use the IP `172.17.0.202` as the VIP and the IP is shared with -other services. - -``` -apiVersion: placement.openstack.org/v1beta1 -kind: PlacementAPI -metadata: - name: placement -spec: - ... - override: - service: - internal: - metadata: - annotations: - metallb.universe.tf/address-pool: osp-internalapi - metallb.universe.tf/allow-shared-ip: internalapi - metallb.universe.tf/loadBalancerIPs: 172.17.0.202 - spec: - type: LoadBalancer - ... -... -``` - -The internal placement endpoint gets registered with its service name. This -service name needs to resolve to the `LoadBalancerIP` on the isolated network -either by DNS or via /etc/hosts: - -``` -# openstack endpoint list -c 'Service Name' -c Interface -c URL --service placement -+--------------+-----------+------------------------------------------------------------------+ -| Service Name | Interface | URL | -+--------------+-----------+------------------------------------------------------------------+ -| placement | internal | http://placement-internal.openstack.svc:8778 | -| placement | public | http://placement-public-openstack.apps.ostest.test.metalkube.org | -+--------------+-----------+------------------------------------------------------------------+ -``` - -# Testing -The repository uses [EnvTest](https://book.kubebuilder.io/reference/envtest.html) to validate the operator in a self -contained environment. - -The test can be run in the terminal with: -```shell -make test -``` -or in Visual Studio Code by defining the following in your settings.json: -```json -"go.testEnvVars": { - "KUBEBUILDER_ASSETS":"" -}, -``` diff --git a/placement-import/api/go.mod b/placement-import/api/go.mod deleted file mode 100644 index ebd069649..000000000 --- a/placement-import/api/go.mod +++ /dev/null @@ -1,95 +0,0 @@ -module github.com/openstack-k8s-operators/placement-operator/api - -go 1.24.4 - -require ( - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 - k8s.io/api v0.31.14 - k8s.io/apimachinery v0.31.14 - sigs.k8s.io/controller-runtime v0.19.7 -) - -require ( - github.com/beorn7/perks v1.0.1 // 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.12.2 // indirect - github.com/evanphx/json-patch v5.9.11+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // 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.22.0 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/x448/float16 v0.8.4 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/time v0.12.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/client-go v0.31.14 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // 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.6.0 // indirect -) - -// mschuppert: map to latest commit from release-4.18 tag -// must consistent within modules and service operators -replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e //allow-merging - -// pin these to avoid later versions pulled by rabbitmq -replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging - -replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging - -replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging - -replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging - -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging - -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.14 //allow-merging - -replace k8s.io/code-generator => k8s.io/code-generator v0.31.14 //allow-merging - -replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging - -// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) -replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging - -replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging diff --git a/placement-import/api/go.sum b/placement-import/api/go.sum deleted file mode 100644 index 91045d7eb..000000000 --- a/placement-import/api/go.sum +++ /dev/null @@ -1,203 +0,0 @@ -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -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.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= -github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -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/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -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/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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -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/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -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/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af h1:Ow12j/PVbEtul1bZ7s/ZenVnKPIHK2q+0VgTp+j/wro= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af/go.mod h1:nC/Jf3OYJRML8UEzJ/mn/TQcSCv/nhqO6x6LGkdDt60= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 h1:P1Nx+i6aH7rFy+T4nrB2AlQzzoLMrMwtnZPEsFvC5cM= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= -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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -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/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= -github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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/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.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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -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/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= -gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= -k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= -k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= -k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= -k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= -k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= -k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= -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-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= -k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= -sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= -sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/placement-import/cmd/main.go b/placement-import/cmd/main.go deleted file mode 100644 index c1c72f1cc..000000000 --- a/placement-import/cmd/main.go +++ /dev/null @@ -1,303 +0,0 @@ -/* -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 is the entry point for the placement-operator. -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" - - "github.com/openstack-k8s-operators/placement-operator/internal/controller" - webhookv1beta1 "github.com/openstack-k8s-operators/placement-operator/internal/webhook/v1beta1" - - // +kubebuilder:scaffold:imports - - networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" - topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" - keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" - "github.com/openstack-k8s-operators/lib-common/modules/common/operator" - mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" - "k8s.io/client-go/kubernetes" - "sigs.k8s.io/controller-runtime/pkg/client/config" -) - -var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") -) - -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(placementv1.AddToScheme(scheme)) - utilruntime.Must(mariadbv1.AddToScheme(scheme)) - utilruntime.Must(keystonev1.AddToScheme(scheme)) - utilruntime.Must(networkv1.AddToScheme(scheme)) - utilruntime.Must(topologyv1.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 pprofBindAddress string - var webhookPort int - 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.StringVar(&pprofBindAddress, "pprof-bind-address", "", "The address the pprof endpoint binds to. Set to empty to disable pprof.") - flag.IntVar(&webhookPort, "webhook-bind-address", 9443, "The port the webhook server binds to.") - 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{ - Port: webhookPort, - 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.21.0/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.21.0/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 - }) - } - - // 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, - options := ctrl.Options{ - Scheme: scheme, - Metrics: metricsServerOptions, - WebhookServer: webhookServer, - HealthProbeBindAddress: probeAddr, - PprofBindAddress: pprofBindAddress, - LeaderElection: enableLeaderElection, - LeaderElectionID: "73d6b7ce.openstack.org", - } - - // apply common openstack operator manager options - err := operator.SetManagerOptions(&options, setupLog) - if err != nil { - setupLog.Error(err, "unable to set manager options") - os.Exit(1) - } - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - cfg, err := config.GetConfig() - if err != nil { - setupLog.Error(err, "") - os.Exit(1) - } - kclient, err := kubernetes.NewForConfig(cfg) - if err != nil { - setupLog.Error(err, "") - os.Exit(1) - } - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - if err := (&controller.PlacementAPIReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Kclient: kclient, - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "PlacementAPI") - os.Exit(1) - } - - // Acquire environmental defaults and initialize operator defaults with them - placementv1.SetupDefaults() - - // nolint:goconst - checker := healthz.Ping - if os.Getenv("ENABLE_WEBHOOKS") != "false" { - if err := webhookv1beta1.SetupPlacementAPIWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "PlacementAPI") - os.Exit(1) - } - checker = mgr.GetWebhookServer().StartedChecker() - } - // +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", checker); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", checker); 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/placement-import/config/certmanager/certificate-metrics.yaml b/placement-import/config/certmanager/certificate-metrics.yaml deleted file mode 100644 index 915d753d2..000000000 --- a/placement-import/config/certmanager/certificate-metrics.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# The following manifests contain a self-signed issuer CR and a metrics certificate CR. -# More document can be found at https://docs.cert-manager.io -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: metrics-certs # this name should match the one appeared in kustomizeconfig.yaml - namespace: system -spec: - dnsNames: - # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize - # replacements in the config/default/kustomization.yaml file. - - SERVICE_NAME.SERVICE_NAMESPACE.svc - - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local - issuerRef: - kind: Issuer - name: selfsigned-issuer - secretName: metrics-server-cert diff --git a/placement-import/config/certmanager/certificate-webhook.yaml b/placement-import/config/certmanager/certificate-webhook.yaml deleted file mode 100644 index e6d8ce29e..000000000 --- a/placement-import/config/certmanager/certificate-webhook.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# The following manifests contain a self-signed issuer CR and a certificate CR. -# More document can be found at https://docs.cert-manager.io -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml - namespace: system -spec: - # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize - # replacements in the config/default/kustomization.yaml file. - dnsNames: - - SERVICE_NAME.SERVICE_NAMESPACE.svc - - SERVICE_NAME.SERVICE_NAMESPACE.svc.cluster.local - issuerRef: - kind: Issuer - name: selfsigned-issuer - secretName: webhook-server-cert diff --git a/placement-import/config/certmanager/issuer.yaml b/placement-import/config/certmanager/issuer.yaml deleted file mode 100644 index 834d52465..000000000 --- a/placement-import/config/certmanager/issuer.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# The following manifest contains a self-signed issuer CR. -# More information can be found at https://docs.cert-manager.io -# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: selfsigned-issuer - namespace: system -spec: - selfSigned: {} diff --git a/placement-import/config/certmanager/kustomization.yaml b/placement-import/config/certmanager/kustomization.yaml deleted file mode 100644 index fcb7498e4..000000000 --- a/placement-import/config/certmanager/kustomization.yaml +++ /dev/null @@ -1,7 +0,0 @@ -resources: -- issuer.yaml -- certificate-webhook.yaml -- certificate-metrics.yaml - -configurations: -- kustomizeconfig.yaml diff --git a/placement-import/config/certmanager/kustomizeconfig.yaml b/placement-import/config/certmanager/kustomizeconfig.yaml deleted file mode 100644 index cf6f89e88..000000000 --- a/placement-import/config/certmanager/kustomizeconfig.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# This configuration is for teaching kustomize how to update name ref substitution -nameReference: -- kind: Issuer - group: cert-manager.io - fieldSpecs: - - kind: Certificate - group: cert-manager.io - path: spec/issuerRef/name diff --git a/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml b/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml deleted file mode 100644 index 73ef0098b..000000000 --- a/placement-import/config/crd/bases/placement.openstack.org_placementapis.yaml +++ /dev/null @@ -1,502 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.18.0 - name: placementapis.placement.openstack.org -spec: - group: placement.openstack.org - names: - kind: PlacementAPI - listKind: PlacementAPIList - plural: placementapis - singular: placementapi - scope: Namespaced - versions: - - additionalPrinterColumns: - - description: NetworkAttachments - jsonPath: .spec.networkAttachments - name: NetworkAttachments - type: string - - description: Status - jsonPath: .status.conditions[0].status - name: Status - type: string - - description: Message - jsonPath: .status.conditions[0].message - name: Message - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: PlacementAPI is the Schema for the placementapis API - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: PlacementAPISpec defines the desired state of PlacementAPI - properties: - apiTimeout: - default: 60 - description: APITimeout for HAProxy, Apache - minimum: 10 - type: integer - auth: - description: Auth - Parameters related to authentication - properties: - applicationCredentialSecret: - description: ApplicationCredentialSecret - Secret containing Application - Credential ID and Secret - type: string - type: object - containerImage: - description: PlacementAPI Container Image URL (will be set to environmental - default if empty) - type: string - customServiceConfig: - description: |- - CustomServiceConfig - customize the service config using this parameter to change service defaults, - or overwrite rendered information using raw OpenStack config format. The content gets added to - to /etc//.conf.d directory as custom.conf file. - type: string - databaseAccount: - default: placement - description: DatabaseAccount - name of MariaDBAccount which will be - used to connect. - type: string - databaseInstance: - description: |- - MariaDB instance name - Right now required by the maridb-operator to get the credentials from the instance to create the DB - Might not be required in future - type: string - defaultConfigOverwrite: - additionalProperties: - type: string - description: DefaultConfigOverwrite - interface to overwrite default - config files like policy.yaml. - type: object - networkAttachments: - description: NetworkAttachments is a list of NetworkAttachment resource - names to expose the services to the given network - items: - type: string - type: array - nodeSelector: - additionalProperties: - type: string - description: NodeSelector to target subset of worker nodes running - this service - type: object - override: - description: Override, provides the ability to override the generated - manifest of several child resources. - properties: - service: - additionalProperties: - description: |- - RoutedOverrideSpec - a routed service override configuration for the Service created to serve traffic - to the cluster. Allows for the manifest of the created Service to be overwritten with custom configuration. - properties: - endpointURL: - type: string - metadata: - description: |- - EmbeddedLabelsAnnotations is an embedded subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. - Only labels and annotations are included. - properties: - annotations: - additionalProperties: - type: string - description: |- - Annotations is an unstructured key value map stored with a resource that may be - set by external tools to store and retrieve arbitrary metadata. They are not - queryable and should be preserved when modifying objects. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ - type: object - labels: - additionalProperties: - type: string - description: |- - Map of string keys and values that can be used to organize and categorize - (scope and select) objects. May match selectors of replication controllers - and services. - More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ - type: object - type: object - spec: - description: |- - OverrideServiceSpec is a subset of the fields included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec - Limited to Type, SessionAffinity, LoadBalancerSourceRanges, ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, - IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy - properties: - externalName: - description: |- - externalName is the external reference that discovery mechanisms will - return as an alias for this service (e.g. a DNS CNAME record). No - proxying will be involved. Must be a lowercase RFC-1123 hostname - (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". - type: string - externalTrafficPolicy: - description: |- - externalTrafficPolicy describes how nodes distribute service traffic they - receive on one of the Service's "externally-facing" addresses (NodePorts, - ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure - the service in a way that assumes that external load balancers will take care - of balancing the service traffic between nodes, and so each node will deliver - traffic only to the node-local endpoints of the service, without masquerading - the client source IP. (Traffic mistakenly sent to a node with no endpoints will - be dropped.) The default value, "Cluster", uses the standard behavior of - routing to all endpoints evenly (possibly modified by topology and other - features). Note that traffic sent to an External IP or LoadBalancer IP from - within the cluster will always get "Cluster" semantics, but clients sending to - a NodePort from within the cluster may need to take traffic policy into account - when picking a node. - type: string - internalTrafficPolicy: - description: |- - InternalTrafficPolicy describes how nodes distribute service traffic they - receive on the ClusterIP. If set to "Local", the proxy will assume that pods - only want to talk to endpoints of the service on the same node as the pod, - dropping the traffic if there are no local endpoints. The default value, - "Cluster", uses the standard behavior of routing to all endpoints evenly - (possibly modified by topology and other features). - type: string - ipFamilyPolicy: - description: |- - IPFamilyPolicy represents the dual-stack-ness requested or required by - this Service. If there is no value provided, then this field will be set - to SingleStack. Services can be "SingleStack" (a single IP family), - "PreferDualStack" (two IP families on dual-stack configured clusters or - a single IP family on single-stack clusters), or "RequireDualStack" - (two IP families on dual-stack configured clusters, otherwise fail). The - ipFamilies and clusterIPs fields depend on the value of this field. This - field will be wiped when updating a service to type ExternalName. - type: string - loadBalancerClass: - description: |- - loadBalancerClass is the class of the load balancer implementation this Service belongs to. - If specified, the value of this field must be a label-style identifier, with an optional prefix, - e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. - This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load - balancer implementation is used, today this is typically done through the cloud provider integration, - but should apply for any default implementation. If set, it is assumed that a load balancer - implementation is watching for Services with a matching class. Any default load balancer - implementation (e.g. cloud providers) should ignore Services that set this field. - This field can only be set when creating or updating a Service to type 'LoadBalancer'. - Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. - type: string - loadBalancerSourceRanges: - description: |- - If specified and supported by the platform, this will restrict traffic through the cloud-provider - load-balancer will be restricted to the specified client IPs. This field will be ignored if the - cloud-provider does not support the feature." - More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ - items: - type: string - type: array - x-kubernetes-list-type: atomic - sessionAffinity: - description: |- - Supports "ClientIP" and "None". Used to maintain session affinity. - Enable client IP based session affinity. - Must be ClientIP or None. - Defaults to None. - More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies - type: string - sessionAffinityConfig: - description: sessionAffinityConfig contains the configurations - of session affinity. - properties: - clientIP: - description: clientIP contains the configurations - of Client IP based session affinity. - properties: - timeoutSeconds: - description: |- - timeoutSeconds specifies the seconds of ClientIP type session sticky time. - The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". - Default value is 10800(for 3 hours). - format: int32 - type: integer - type: object - type: object - type: - description: |- - type determines how the Service is exposed. Defaults to ClusterIP. Valid - options are ExternalName, ClusterIP, NodePort, and LoadBalancer. - "ClusterIP" allocates a cluster-internal IP address for load-balancing - to endpoints. Endpoints are determined by the selector or if that is not - specified, by manual construction of an Endpoints object or - EndpointSlice objects. If clusterIP is "None", no virtual IP is - allocated and the endpoints are published as a set of endpoints rather - than a virtual IP. - "NodePort" builds on ClusterIP and allocates a port on every node which - routes to the same endpoints as the clusterIP. - "LoadBalancer" builds on NodePort and creates an external load-balancer - (if supported in the current cloud) which routes to the same endpoints - as the clusterIP. - "ExternalName" aliases this service to the specified externalName. - Several other fields do not apply to ExternalName services. - More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types - type: string - type: object - type: object - description: |- - Override configuration for the Service created to serve traffic to the cluster. - The key must be the endpoint type (public, internal) - type: object - type: object - passwordSelectors: - default: - service: PlacementPassword - description: PasswordSelectors - Selectors to identify the DB and - ServiceUser password from the Secret - properties: - service: - default: PlacementPassword - description: Service - Selector to get the service user password - from the Secret - type: string - type: object - preserveJobs: - default: false - description: PreserveJobs - do not delete jobs after they finished - e.g. to check logs - type: boolean - replicas: - default: 1 - description: Replicas of placement API to run - format: int32 - maximum: 32 - minimum: 0 - type: integer - resources: - description: |- - Resources - Compute Resources required by this service (Limits/Requests). - https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - properties: - claims: - description: |- - Claims lists the names of resources, defined in spec.resourceClaims, - that are used by this container. - - This is an alpha field and requires enabling the - DynamicResourceAllocation feature gate. - - This field is immutable. It can only be set for containers. - items: - description: ResourceClaim references one entry in PodSpec.ResourceClaims. - properties: - name: - description: |- - Name must match the name of one entry in pod.spec.resourceClaims of - the Pod where this field is used. It makes that resource available - inside a container. - type: string - request: - description: |- - Request is the name chosen for a request in the referenced claim. - If empty, everything from the claim is made available, otherwise - only the result of this request. - type: string - required: - - name - type: object - type: array - x-kubernetes-list-map-keys: - - name - x-kubernetes-list-type: map - limits: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Limits describes the maximum amount of compute resources allowed. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - requests: - additionalProperties: - anyOf: - - type: integer - - type: string - pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ - x-kubernetes-int-or-string: true - description: |- - Requests describes the minimum amount of compute resources required. - If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, - otherwise to an implementation-defined value. Requests cannot exceed Limits. - More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - type: object - type: object - secret: - description: Secret containing OpenStack password information for - placement PlacementPassword - type: string - serviceUser: - default: placement - description: ServiceUser - optional username used for this service - to register in keystone - type: string - tls: - description: TLS - Parameters related to the TLS - properties: - api: - description: API tls type which encapsulates for API services - properties: - internal: - description: Internal GenericService - holds the secret for - the internal endpoint - properties: - secretName: - description: SecretName - holding the cert, key for the - service - type: string - type: object - public: - description: Public GenericService - holds the secret for - the public endpoint - properties: - secretName: - description: SecretName - holding the cert, key for the - service - type: string - type: object - type: object - caBundleSecretName: - description: CaBundleSecretName - holding the CA certs in a pre-created - bundle file - type: string - type: object - topologyRef: - description: |- - TopologyRef to apply the Topology defined by the associated CR referenced - by name - properties: - name: - description: Name - The Topology CR name that the Service references - type: string - namespace: - description: |- - Namespace - The Namespace to fetch the Topology CR referenced - NOTE: Namespace currently points by default to the same namespace where - the Service is deployed. Customizing the namespace is not supported and - webhooks prevent editing this field to a value different from the - current project - type: string - type: object - required: - - containerImage - - databaseInstance - - secret - type: object - status: - description: PlacementAPIStatus defines the observed state of PlacementAPI - properties: - conditions: - description: Conditions - items: - description: Condition defines an observation of a API resource - operational state. - properties: - lastTransitionTime: - description: |- - Last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when - the API field changed is acceptable. - format: date-time - type: string - message: - description: A human readable message indicating details about - the transition. - type: string - reason: - description: The reason for the condition's last transition - in CamelCase. - type: string - severity: - description: |- - Severity provides a classification of Reason code, so the current situation is immediately - understandable and could act accordingly. - It is meant for situations where Status=False and it should be indicated if it is just - informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue - and no actions to automatically resolve the issue can/should be done). - For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. - type: string - status: - description: Status of the condition, one of True, False, Unknown. - type: string - type: - description: Type of condition in CamelCase. - type: string - required: - - lastTransitionTime - - status - - type - type: object - type: array - databaseHostname: - description: Placement Database Hostname - type: string - hash: - additionalProperties: - type: string - description: Map of hashes to track e.g. job status - type: object - lastAppliedTopology: - description: LastAppliedTopology - the last applied Topology - properties: - name: - description: Name - The Topology CR name that the Service references - type: string - namespace: - description: |- - Namespace - The Namespace to fetch the Topology CR referenced - NOTE: Namespace currently points by default to the same namespace where - the Service is deployed. Customizing the namespace is not supported and - webhooks prevent editing this field to a value different from the - current project - type: string - type: object - networkAttachments: - additionalProperties: - items: - type: string - type: array - description: NetworkAttachments status of the deployment pods - type: object - observedGeneration: - description: ObservedGeneration - the most recent generation observed - for this service. If the observed generation is less than the spec - generation, then the controller has not processed the latest changes. - format: int64 - type: integer - readyCount: - description: ReadyCount of placement API instances - format: int32 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} diff --git a/placement-import/config/crd/kustomization.yaml b/placement-import/config/crd/kustomization.yaml deleted file mode 100644 index 3ac62fa76..000000000 --- a/placement-import/config/crd/kustomization.yaml +++ /dev/null @@ -1,16 +0,0 @@ -# This kustomization.yaml is not intended to be run by itself, -# since it depends on service name and namespace that are out of this kustomize package. -# It should be run by config/default -resources: -- bases/placement.openstack.org_placementapis.yaml -# +kubebuilder:scaffold:crdkustomizeresource - -patches: -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. -# patches here are for enabling the conversion webhook for each CRD -# +kubebuilder:scaffold:crdkustomizewebhookpatch - -# [WEBHOOK] To enable webhook, uncomment the following section -# the following config is for teaching kustomize how to do kustomization for CRDs. -#configurations: -#- kustomizeconfig.yaml diff --git a/placement-import/config/crd/kustomizeconfig.yaml b/placement-import/config/crd/kustomizeconfig.yaml deleted file mode 100644 index ec5c150a9..000000000 --- a/placement-import/config/crd/kustomizeconfig.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# This file is for teaching kustomize how to substitute name and namespace reference in CRD -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/name - -namespace: -- kind: CustomResourceDefinition - version: v1 - group: apiextensions.k8s.io - path: spec/conversion/webhook/clientConfig/service/namespace - create: false - -varReference: -- path: metadata/annotations diff --git a/placement-import/config/default/cert_metrics_manager_patch.yaml b/placement-import/config/default/cert_metrics_manager_patch.yaml deleted file mode 100644 index d97501553..000000000 --- a/placement-import/config/default/cert_metrics_manager_patch.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# 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/placement-import/config/default/kustomization.yaml b/placement-import/config/default/kustomization.yaml deleted file mode 100644 index 8efb44af9..000000000 --- a/placement-import/config/default/kustomization.yaml +++ /dev/null @@ -1,236 +0,0 @@ -# Adds namespace to all resources. -namespace: placement-operator-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: placement-operator- - -# 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: -# Injects our custom images (ENV variable settings) -- path: manager_default_images.yaml -# [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/placement-import/config/default/manager_default_images.yaml b/placement-import/config/default/manager_default_images.yaml deleted file mode 100644 index 117d091b4..000000000 --- a/placement-import/config/default/manager_default_images.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# This patch inject custom ENV settings to the manager container -# Used to set our default image locations -apiVersion: apps/v1 -kind: Deployment -metadata: - name: controller-manager - namespace: system -spec: - template: - spec: - containers: - - name: manager - env: - - name: RELATED_IMAGE_PLACEMENT_API_IMAGE_URL_DEFAULT - value: quay.io/podified-antelope-centos9/openstack-placement-api:current-podified diff --git a/placement-import/config/default/manager_metrics_patch.yaml b/placement-import/config/default/manager_metrics_patch.yaml deleted file mode 100644 index 2aaef6536..000000000 --- a/placement-import/config/default/manager_metrics_patch.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# 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/placement-import/config/default/manager_webhook_patch.yaml b/placement-import/config/default/manager_webhook_patch.yaml deleted file mode 100644 index 963c8a4cc..000000000 --- a/placement-import/config/default/manager_webhook_patch.yaml +++ /dev/null @@ -1,31 +0,0 @@ -# This patch ensures the webhook certificates are properly mounted in the manager container. -# It configures the necessary arguments, volumes, volume mounts, and container ports. - -# Add the --webhook-cert-path argument for configuring the webhook certificate path -- op: add - path: /spec/template/spec/containers/0/args/- - value: --webhook-cert-path=/tmp/k8s-webhook-server/serving-certs - -# Add the volumeMount for the webhook certificates -- op: add - path: /spec/template/spec/containers/0/volumeMounts/- - value: - mountPath: /tmp/k8s-webhook-server/serving-certs - name: webhook-certs - readOnly: true - -# Add the port configuration for the webhook server -- op: add - path: /spec/template/spec/containers/0/ports/- - value: - containerPort: 9443 - name: webhook-server - protocol: TCP - -# Add the volume configuration for the webhook certificates -- op: add - path: /spec/template/spec/volumes/- - value: - name: webhook-certs - secret: - secretName: webhook-server-cert diff --git a/placement-import/config/default/metrics_service.yaml b/placement-import/config/default/metrics_service.yaml deleted file mode 100644 index a24e557ae..000000000 --- a/placement-import/config/default/metrics_service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - 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: placement-operator diff --git a/placement-import/config/manager/kustomization.yaml b/placement-import/config/manager/kustomization.yaml deleted file mode 100644 index 87eac10b7..000000000 --- a/placement-import/config/manager/kustomization.yaml +++ /dev/null @@ -1,8 +0,0 @@ -resources: -- manager.yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -images: -- name: controller - newName: quay.io/openstack-k8s-operators/placement-operator - newTag: latest diff --git a/placement-import/config/manager/manager.yaml b/placement-import/config/manager/manager.yaml deleted file mode 100644 index 078bc7d35..000000000 --- a/placement-import/config/manager/manager.yaml +++ /dev/null @@ -1,98 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - 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: placement-operator - app.kubernetes.io/managed-by: kustomize -spec: - selector: - matchLabels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - replicas: 1 - template: - metadata: - annotations: - kubectl.kubernetes.io/default-container: manager - labels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - 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/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml b/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml deleted file mode 100644 index 2ae6b216c..000000000 --- a/placement-import/config/manifests/bases/placement-operator.clusterserviceversion.yaml +++ /dev/null @@ -1,63 +0,0 @@ -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - annotations: - alm-examples: '[]' - capabilities: Basic Install - features.operators.openshift.io/disconnected: "true" - features.operators.openshift.io/fips-compliant: "true" - features.operators.openshift.io/proxy-aware: "false" - features.operators.openshift.io/tls-profiles: "false" - features.operators.openshift.io/token-auth-aws: "false" - features.operators.openshift.io/token-auth-azure: "false" - features.operators.openshift.io/token-auth-gcp: "false" - operatorframework.io/suggested-namespace: openstack - operators.operatorframework.io/operator-type: non-standalone - name: placement-operator.v0.0.0 - namespace: placeholder -spec: - apiservicedefinitions: {} - customresourcedefinitions: - owned: - - description: PlacementAPI is the Schema for the placementapis API - displayName: Placement API - kind: PlacementAPI - name: placementapis.placement.openstack.org - specDescriptors: - - description: Auth - Parameters related to authentication - displayName: Auth - path: auth - - description: ApplicationCredentialSecret - Secret containing Application Credential - ID and Secret - displayName: Application Credential Secret - path: auth.applicationCredentialSecret - - description: TLS - Parameters related to the TLS - displayName: TLS - path: tls - version: v1beta1 - description: Placement Operator - displayName: Placement Operator - install: - spec: - deployments: null - strategy: "" - installModes: - - supported: true - type: OwnNamespace - - supported: true - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: true - type: AllNamespaces - keywords: - - OpenStack - - Placement - links: - - name: Placement Operator - url: https://github.com/openstack-k8s-operators/placement-operator - maturity: beta - provider: - name: Red Hat Inc. - url: https://redhat.com/ - version: 0.0.0 diff --git a/placement-import/config/manifests/kustomization.yaml b/placement-import/config/manifests/kustomization.yaml deleted file mode 100644 index fbce11adc..000000000 --- a/placement-import/config/manifests/kustomization.yaml +++ /dev/null @@ -1,28 +0,0 @@ -# These resources constitute the fully configured set of manifests -# used to generate the 'manifests/' directory in a bundle. -resources: -- bases/placement-operator.clusterserviceversion.yaml -- ../default -- ../samples -- ../scorecard - -# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. -# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. -# These patches remove the unnecessary "cert" volume and its manager container volumeMount. -#patches: -#- target: -# group: apps -# version: v1 -# kind: Deployment -# name: controller-manager -# namespace: system -# patch: |- -# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. -# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. -# - op: remove - -# path: /spec/template/spec/containers/0/volumeMounts/0 -# # Remove the "cert" volume, since OLM will create and mount a set of certs. -# # Update the indices in this path if adding or removing volumes in the manager's Deployment. -# - op: remove -# path: /spec/template/spec/volumes/0 diff --git a/placement-import/config/network-policy/allow-metrics-traffic.yaml b/placement-import/config/network-policy/allow-metrics-traffic.yaml deleted file mode 100644 index ff444541c..000000000 --- a/placement-import/config/network-policy/allow-metrics-traffic.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# 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: placement-operator - app.kubernetes.io/managed-by: kustomize - name: allow-metrics-traffic - namespace: system -spec: - podSelector: - matchLabels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - 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/placement-import/config/network-policy/allow-webhook-traffic.yaml b/placement-import/config/network-policy/allow-webhook-traffic.yaml deleted file mode 100644 index 9907bc041..000000000 --- a/placement-import/config/network-policy/allow-webhook-traffic.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This NetworkPolicy allows ingress traffic to your webhook server running -# as part of the controller-manager from specific namespaces and pods. CR(s) which uses webhooks -# will only work when applied in namespaces labeled with 'webhook: enabled' -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: allow-webhook-traffic - namespace: system -spec: - podSelector: - matchLabels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - policyTypes: - - Ingress - ingress: - # This allows ingress traffic from any namespace with the label webhook: enabled - - from: - - namespaceSelector: - matchLabels: - webhook: enabled # Only from namespaces with this label - ports: - - port: 443 - protocol: TCP diff --git a/placement-import/config/network-policy/kustomization.yaml b/placement-import/config/network-policy/kustomization.yaml deleted file mode 100644 index 0872bee12..000000000 --- a/placement-import/config/network-policy/kustomization.yaml +++ /dev/null @@ -1,3 +0,0 @@ -resources: -- allow-webhook-traffic.yaml -- allow-metrics-traffic.yaml diff --git a/placement-import/config/prometheus/kustomization.yaml b/placement-import/config/prometheus/kustomization.yaml deleted file mode 100644 index fdc5481b1..000000000 --- a/placement-import/config/prometheus/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -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/placement-import/config/prometheus/monitor.yaml b/placement-import/config/prometheus/monitor.yaml deleted file mode 100644 index cadf258af..000000000 --- a/placement-import/config/prometheus/monitor.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# Prometheus Monitor Service (Metrics) -apiVersion: monitoring.coreos.com/v1 -kind: ServiceMonitor -metadata: - labels: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator - 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: placement-operator diff --git a/placement-import/config/prometheus/monitor_tls_patch.yaml b/placement-import/config/prometheus/monitor_tls_patch.yaml deleted file mode 100644 index 5bf84ce0d..000000000 --- a/placement-import/config/prometheus/monitor_tls_patch.yaml +++ /dev/null @@ -1,19 +0,0 @@ -# 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/placement-import/config/rbac/kustomization.yaml b/placement-import/config/rbac/kustomization.yaml deleted file mode 100644 index 8e56bfcfa..000000000 --- a/placement-import/config/rbac/kustomization.yaml +++ /dev/null @@ -1,27 +0,0 @@ -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 -# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by -# default, aiding admins in cluster management. Those roles are -# not used by the placement-operator itself. You can comment the following lines -# if you do not want those helpers be installed with your Project. -- placementapi_admin_role.yaml -- placementapi_editor_role.yaml -- placementapi_viewer_role.yaml diff --git a/placement-import/config/rbac/leader_election_role.yaml b/placement-import/config/rbac/leader_election_role.yaml deleted file mode 100644 index 0827f7bf4..000000000 --- a/placement-import/config/rbac/leader_election_role.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# permissions to do leader election. -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - labels: - app.kubernetes.io/name: placement-operator - 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/placement-import/config/rbac/leader_election_role_binding.yaml b/placement-import/config/rbac/leader_election_role_binding.yaml deleted file mode 100644 index 309ecad8a..000000000 --- a/placement-import/config/rbac/leader_election_role_binding.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - labels: - app.kubernetes.io/name: placement-operator - 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/placement-import/config/rbac/metrics_auth_role.yaml b/placement-import/config/rbac/metrics_auth_role.yaml deleted file mode 100644 index 32d2e4ec6..000000000 --- a/placement-import/config/rbac/metrics_auth_role.yaml +++ /dev/null @@ -1,17 +0,0 @@ -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/placement-import/config/rbac/metrics_auth_role_binding.yaml b/placement-import/config/rbac/metrics_auth_role_binding.yaml deleted file mode 100644 index e775d67ff..000000000 --- a/placement-import/config/rbac/metrics_auth_role_binding.yaml +++ /dev/null @@ -1,12 +0,0 @@ -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/placement-import/config/rbac/metrics_reader_role.yaml b/placement-import/config/rbac/metrics_reader_role.yaml deleted file mode 100644 index 51a75db47..000000000 --- a/placement-import/config/rbac/metrics_reader_role.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: metrics-reader -rules: -- nonResourceURLs: - - "/metrics" - verbs: - - get diff --git a/placement-import/config/rbac/placementapi_admin_role.yaml b/placement-import/config/rbac/placementapi_admin_role.yaml deleted file mode 100644 index 773fa5881..000000000 --- a/placement-import/config/rbac/placementapi_admin_role.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# This rule is not used by the project placement-operator itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants full permissions ('*') over placement.openstack.org. -# This role is intended for users authorized to modify roles and bindings within the cluster, -# enabling them to delegate specific permissions to other users or groups as needed. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: placementapi-admin-role -rules: -- apiGroups: - - placement.openstack.org - resources: - - placementapis - verbs: - - '*' -- apiGroups: - - placement.openstack.org - resources: - - placementapis/status - verbs: - - get diff --git a/placement-import/config/rbac/placementapi_editor_role.yaml b/placement-import/config/rbac/placementapi_editor_role.yaml deleted file mode 100644 index 55eb6a17d..000000000 --- a/placement-import/config/rbac/placementapi_editor_role.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# This rule is not used by the project placement-operator itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants permissions to create, update, and delete resources within the placement.openstack.org. -# This role is intended for users who need to manage these resources -# but should not control RBAC or manage permissions for others. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: placementapi-editor-role -rules: -- apiGroups: - - placement.openstack.org - resources: - - placementapis - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - placement.openstack.org - resources: - - placementapis/status - verbs: - - get diff --git a/placement-import/config/rbac/placementapi_viewer_role.yaml b/placement-import/config/rbac/placementapi_viewer_role.yaml deleted file mode 100644 index 857792af3..000000000 --- a/placement-import/config/rbac/placementapi_viewer_role.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# This rule is not used by the project placement-operator itself. -# It is provided to allow the cluster admin to help manage permissions for users. -# -# Grants read-only access to placement.openstack.org resources. -# This role is intended for users who need visibility into these resources -# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing. - -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: placementapi-viewer-role -rules: -- apiGroups: - - placement.openstack.org - resources: - - placementapis - verbs: - - get - - list - - watch -- apiGroups: - - placement.openstack.org - resources: - - placementapis/status - verbs: - - get diff --git a/placement-import/config/rbac/role.yaml b/placement-import/config/rbac/role.yaml deleted file mode 100644 index 869d5bace..000000000 --- a/placement-import/config/rbac/role.yaml +++ /dev/null @@ -1,162 +0,0 @@ ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: manager-role -rules: -- apiGroups: - - "" - resources: - - configmaps - - pods - - secrets - - services - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - get - - list - - patch - - update - - watch -- apiGroups: - - apps - resources: - - deployments - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - batch - resources: - - jobs - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - k8s.cni.cncf.io - resources: - - network-attachment-definitions - verbs: - - get - - list - - watch -- apiGroups: - - keystone.openstack.org - resources: - - keystoneapis - verbs: - - get - - list - - watch -- apiGroups: - - keystone.openstack.org - resources: - - keystoneendpoints - - keystoneservices - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mariadb.openstack.org - resources: - - mariadbaccounts - - mariadbdatabases - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - mariadb.openstack.org - resources: - - mariadbaccounts/finalizers - - mariadbdatabases/finalizers - verbs: - - patch - - update -- apiGroups: - - placement.openstack.org - resources: - - placementapis - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - placement.openstack.org - resources: - - placementapis/finalizers - verbs: - - patch - - update -- apiGroups: - - placement.openstack.org - resources: - - placementapis/status - verbs: - - get - - patch - - update -- apiGroups: - - rbac.authorization.k8s.io - resources: - - rolebindings - - roles - verbs: - - create - - get - - list - - patch - - update - - watch -- apiGroups: - - security.openshift.io - resourceNames: - - anyuid - resources: - - securitycontextconstraints - verbs: - - use -- apiGroups: - - topology.openstack.org - resources: - - topologies - verbs: - - get - - list - - update - - watch diff --git a/placement-import/config/rbac/role_binding.yaml b/placement-import/config/rbac/role_binding.yaml deleted file mode 100644 index b776ff31d..000000000 --- a/placement-import/config/rbac/role_binding.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/name: placement-operator - 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/placement-import/config/rbac/service_account.yaml b/placement-import/config/rbac/service_account.yaml deleted file mode 100644 index f1ed5daec..000000000 --- a/placement-import/config/rbac/service_account.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: controller-manager - namespace: system diff --git a/placement-import/config/samples/kustomization.yaml b/placement-import/config/samples/kustomization.yaml deleted file mode 100644 index ac2e8f1e2..000000000 --- a/placement-import/config/samples/kustomization.yaml +++ /dev/null @@ -1,4 +0,0 @@ -## Append samples you want in your CSV to this file as resources ## -resources: -- placement_v1beta1_placementapi.yaml -#+kubebuilder:scaffold:manifestskustomizesamples diff --git a/placement-import/config/samples/placement_v1beta1_placementapi.yaml b/placement-import/config/samples/placement_v1beta1_placementapi.yaml deleted file mode 100644 index 2740db960..000000000 --- a/placement-import/config/samples/placement_v1beta1_placementapi.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: placement.openstack.org/v1beta1 -kind: PlacementAPI -metadata: - name: placement -spec: - serviceUser: placement - customServiceConfig: | - [DEFAULT] - debug = true - databaseInstance: openstack - databaseAccount: placement - preserveJobs: false - replicas: 1 - secret: placement-secret - #resources: - # requests: - # memory: "500Mi" - # cpu: "1.0" diff --git a/placement-import/config/samples/placement_v1beta1_placementtls.yaml b/placement-import/config/samples/placement_v1beta1_placementtls.yaml deleted file mode 100644 index 76c8c0a90..000000000 --- a/placement-import/config/samples/placement_v1beta1_placementtls.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: placement.openstack.org/v1beta1 -kind: PlacementAPI -metadata: - name: placement -spec: - serviceUser: placement - customServiceConfig: | - [DEFAULT] - debug = true - databaseInstance: openstack - databaseAccount: placement - preserveJobs: false - replicas: 1 - secret: placement-secret - tls: - api: - internal: - secretName: cert-internal-svc - public: - secretName: cert-public-svc - caBundleSecretName: combined-ca-bundle diff --git a/placement-import/config/scorecard/bases/config.yaml b/placement-import/config/scorecard/bases/config.yaml deleted file mode 100644 index c77047841..000000000 --- a/placement-import/config/scorecard/bases/config.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: scorecard.operatorframework.io/v1alpha3 -kind: Configuration -metadata: - name: config -stages: -- parallel: true - tests: [] diff --git a/placement-import/config/scorecard/kustomization.yaml b/placement-import/config/scorecard/kustomization.yaml deleted file mode 100644 index 54e8aa507..000000000 --- a/placement-import/config/scorecard/kustomization.yaml +++ /dev/null @@ -1,18 +0,0 @@ -resources: -- bases/config.yaml -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -patches: -- path: patches/basic.config.yaml - target: - group: scorecard.operatorframework.io - kind: Configuration - name: config - version: v1alpha3 -- path: patches/olm.config.yaml - target: - group: scorecard.operatorframework.io - kind: Configuration - name: config - version: v1alpha3 -# +kubebuilder:scaffold:patches diff --git a/placement-import/config/scorecard/patches/basic.config.yaml b/placement-import/config/scorecard/patches/basic.config.yaml deleted file mode 100644 index 8237b70d8..000000000 --- a/placement-import/config/scorecard/patches/basic.config.yaml +++ /dev/null @@ -1,10 +0,0 @@ -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - basic-check-spec - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: basic - test: basic-check-spec-test diff --git a/placement-import/config/scorecard/patches/olm.config.yaml b/placement-import/config/scorecard/patches/olm.config.yaml deleted file mode 100644 index 416660a77..000000000 --- a/placement-import/config/scorecard/patches/olm.config.yaml +++ /dev/null @@ -1,50 +0,0 @@ -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - olm-bundle-validation - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: olm - test: olm-bundle-validation-test -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - olm-crds-have-validation - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: olm - test: olm-crds-have-validation-test -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - olm-crds-have-resources - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: olm - test: olm-crds-have-resources-test -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - olm-spec-descriptors - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: olm - test: olm-spec-descriptors-test -- op: add - path: /stages/0/tests/- - value: - entrypoint: - - scorecard-test - - olm-status-descriptors - image: quay.io/operator-framework/scorecard-test:v1.41.1 - labels: - suite: olm - test: olm-status-descriptors-test diff --git a/placement-import/config/webhook/kustomization.yaml b/placement-import/config/webhook/kustomization.yaml deleted file mode 100644 index 9cf26134e..000000000 --- a/placement-import/config/webhook/kustomization.yaml +++ /dev/null @@ -1,6 +0,0 @@ -resources: -- manifests.yaml -- service.yaml - -configurations: -- kustomizeconfig.yaml diff --git a/placement-import/config/webhook/kustomizeconfig.yaml b/placement-import/config/webhook/kustomizeconfig.yaml deleted file mode 100644 index 206316e54..000000000 --- a/placement-import/config/webhook/kustomizeconfig.yaml +++ /dev/null @@ -1,22 +0,0 @@ -# the following config is for teaching kustomize where to look at when substituting nameReference. -# It requires kustomize v2.1.0 or newer to work properly. -nameReference: -- kind: Service - version: v1 - fieldSpecs: - - kind: MutatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/name - - kind: ValidatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/name - -namespace: -- kind: MutatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/namespace - create: true -- kind: ValidatingWebhookConfiguration - group: admissionregistration.k8s.io - path: webhooks/clientConfig/service/namespace - create: true diff --git a/placement-import/config/webhook/manifests.yaml b/placement-import/config/webhook/manifests.yaml deleted file mode 100644 index c6983dcd5..000000000 --- a/placement-import/config/webhook/manifests.yaml +++ /dev/null @@ -1,52 +0,0 @@ ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: mutating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /mutate-placement-openstack-org-v1beta1-placementapi - failurePolicy: Fail - name: mplacementapi-v1beta1.kb.io - rules: - - apiGroups: - - placement.openstack.org - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - placementapis - sideEffects: None ---- -apiVersion: admissionregistration.k8s.io/v1 -kind: ValidatingWebhookConfiguration -metadata: - name: validating-webhook-configuration -webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: webhook-service - namespace: system - path: /validate-placement-openstack-org-v1beta1-placementapi - failurePolicy: Fail - name: vplacementapi-v1beta1.kb.io - rules: - - apiGroups: - - placement.openstack.org - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - placementapis - sideEffects: None diff --git a/placement-import/config/webhook/service.yaml b/placement-import/config/webhook/service.yaml deleted file mode 100644 index 95742c00a..000000000 --- a/placement-import/config/webhook/service.yaml +++ /dev/null @@ -1,16 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/name: placement-operator - app.kubernetes.io/managed-by: kustomize - name: webhook-service - namespace: system -spec: - ports: - - port: 443 - protocol: TCP - targetPort: 9443 - selector: - control-plane: controller-manager - app.kubernetes.io/name: placement-operator diff --git a/placement-import/go.mod b/placement-import/go.mod deleted file mode 100644 index 703132d18..000000000 --- a/placement-import/go.mod +++ /dev/null @@ -1,144 +0,0 @@ -module github.com/openstack-k8s-operators/placement-operator - -go 1.24.4 - -require ( - github.com/go-logr/logr v1.4.3 - github.com/google/uuid v1.6.0 - github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 - github.com/onsi/ginkgo/v2 v2.28.1 - github.com/onsi/gomega v1.39.1 - github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af - github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581 - github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 - github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18 - github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727 - github.com/openstack-k8s-operators/placement-operator/api v0.3.1-0.20240216174613-3d349f26e681 - k8s.io/api v0.31.14 - k8s.io/apimachinery v0.31.14 - k8s.io/client-go v0.31.14 - k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d - sigs.k8s.io/controller-runtime v0.19.7 -) - -require ( - github.com/Masterminds/semver/v3 v3.4.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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.12.2 // indirect - github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // 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.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/cel-go v0.20.1 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83 // indirect - github.com/gophercloud/gophercloud/v2 v2.8.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect - github.com/imdario/mergo v0.3.16 // 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.9.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/openshift/api v3.9.0+incompatible // indirect - github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e // indirect - github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.22.0 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.65.0 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/rabbitmq/cluster-operator/v2 v2.16.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.7 // indirect - github.com/stoewer/go-strcase v1.2.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.58.0 // indirect - go.opentelemetry.io/otel v1.34.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.34.0 // indirect - go.opentelemetry.io/otel/sdk v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.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.1 // indirect - go.yaml.in/yaml/v2 v2.4.2 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/term v0.39.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/time v0.12.0 // indirect - golang.org/x/tools v0.41.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect - google.golang.org/grpc v1.71.1 // indirect - google.golang.org/protobuf v1.36.7 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.33.2 // indirect - k8s.io/apiserver v0.33.2 // indirect - k8s.io/component-base v0.33.2 // indirect - k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250902184714-7fc278399c7f // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // 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.6.0 // indirect -) - -replace github.com/openstack-k8s-operators/placement-operator/api => ./api - -// mschuppert: map to latest commit from release-4.18 tag -// must consistent within modules and service operators -replace github.com/openshift/api => github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e //allow-merging - -// pin these to avoid later versions pulled by rabbitmq -replace k8s.io/apimachinery => k8s.io/apimachinery v0.31.14 //allow-merging - -replace k8s.io/api => k8s.io/api v0.31.14 //allow-merging - -replace k8s.io/apiserver => k8s.io/apiserver v0.31.14 //allow-merging - -replace k8s.io/client-go => k8s.io/client-go v0.31.14 //allow-merging - -replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.31.14 //allow-merging - -replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.31.14 //allow-merging - -replace k8s.io/code-generator => k8s.io/code-generator v0.31.14 //allow-merging - -replace k8s.io/component-base => k8s.io/component-base v0.31.14 //allow-merging - -// custom RabbitmqClusterSpecCore for OpenStackControlplane (v2.16.0_patches) -replace github.com/rabbitmq/cluster-operator/v2 => github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec //allow-merging - -replace k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e //allow-merging diff --git a/placement-import/go.sum b/placement-import/go.sum deleted file mode 100644 index 54026ee63..000000000 --- a/placement-import/go.sum +++ /dev/null @@ -1,305 +0,0 @@ -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -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-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -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.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -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.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= -github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -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.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= -github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= -github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= -github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= -github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= -github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -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-20260115054156-294ebfa9ad83 h1:z2ogiKUYzX5Is6zr/vP9vJGqPwcdqsWjOt+V8J7+bTc= -github.com/google/pprof v0.0.0-20260115054156-294ebfa9ad83/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gophercloud/gophercloud/v2 v2.8.0 h1:of2+8tT6+FbEYHfYC8GBu8TXJNsXYSNm9KuvpX7Neqo= -github.com/gophercloud/gophercloud/v2 v2.8.0/go.mod h1:Ki/ILhYZr/5EPebrPL9Ej+tUg4lqx71/YH2JWVeU+Qk= -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/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -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/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= -github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc= -github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw= -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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= -github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= -github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= -github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/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.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= -github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= -github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e h1:E1OdwSpqWuDPCedyUt0GEdoAE+r5TXy7YS21yNEo+2U= -github.com/openshift/api v0.0.0-20250711200046-c86d80652a9e/go.mod h1:Shkl4HanLwDiiBzakv+con/aMGnVE2MAGvoKp5oyYUo= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af h1:Ow12j/PVbEtul1bZ7s/ZenVnKPIHK2q+0VgTp+j/wro= -github.com/openstack-k8s-operators/infra-operator/apis v0.6.1-0.20260316100655-863ae03d41af/go.mod h1:nC/Jf3OYJRML8UEzJ/mn/TQcSCv/nhqO6x6LGkdDt60= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581 h1:FntUoWPVWx8xaGCwP7kdUVT/N8Z0kfIxLLK4L+oQJ7c= -github.com/openstack-k8s-operators/keystone-operator/api v0.6.1-0.20260314080138-b41734470581/go.mod h1:l15wx+Qxi/I9Nlj6u6PheZqkf9dBW7cCxAcjl8zsu+8= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18 h1:P1Nx+i6aH7rFy+T4nrB2AlQzzoLMrMwtnZPEsFvC5cM= -github.com/openstack-k8s-operators/lib-common/modules/common v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:+vcGsjqibpMUz3y/g0B5YIXNotlTvQdMB6f92siiwKM= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e h1:42OT26Ak0lwWbJDNwhv/0HsjafVkLyPhfonS5DjDb2g= -github.com/openstack-k8s-operators/lib-common/modules/openstack v0.6.1-0.20260310070607-b96da8dd520e/go.mod h1:7yqbVpg0k0vW+kZks+TMU/cd1ovoejyHfVPWcyGYLHI= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e h1:lHsE9kmPzGHfO6o9vXj6f5UTIlQRJNdrvrj8GWy/ct8= -github.com/openstack-k8s-operators/lib-common/modules/storage v0.6.1-0.20260310070607-b96da8dd520e/go.mod h1:3loLaPUDQyvbPekylZd9OCLF+EXH2klRI9IeeQhuMcs= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18 h1:beEKU5yv6ih8pbIqrU15QP0sGDXyxrKqVMh3zHDscrI= -github.com/openstack-k8s-operators/lib-common/modules/test v0.6.1-0.20260320125710-3a5f82ff0f18/go.mod h1:dEjz8zHRIlP3vnMmWdHytlLeSZ6BHcIiSTPM7xTQxFg= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727 h1:+IVZR4jm1MvVeoxS75cHNOsW7sf6jGdXx7kTnOSOxiw= -github.com/openstack-k8s-operators/mariadb-operator/api v0.6.1-0.20260314091348-5c473d964727/go.mod h1:bUpTGnR9HF19rD2Ow6IU8wJk+xrBtKdeA8PFjpguEh8= -github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec h1:saovr368HPAKHN0aRPh8h8n9s9dn3d8Frmfua0UYRlc= -github.com/openstack-k8s-operators/rabbitmq-cluster-operator/v2 v2.6.1-0.20250929174222-a0d328fa4dec/go.mod h1:Nh2NEePLjovUQof2krTAg4JaAoLacqtPTZQXK6izNfg= -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.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/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.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -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/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.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.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -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.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -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.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= -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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/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.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -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-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= -golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= -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/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -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.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= -golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= -gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -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-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -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.31.14 h1:xYn/S/WFJsksI7dk/5uBRd3Umm/D8W5g7sRnd4csotA= -k8s.io/api v0.31.14/go.mod h1:K8fvRey4z73RAuxBZCma7WtY8WFvkViYhfFLCMT4xgA= -k8s.io/apiextensions-apiserver v0.31.14 h1:1KupD0PyU7CgiT/PiZPSgZhTCL2KGwvXd1ejGcxjEfg= -k8s.io/apiextensions-apiserver v0.31.14/go.mod h1:Odk14fSl/zaciI8DRUSPMSH74UXtz4gfinw7zY7YHvE= -k8s.io/apimachinery v0.31.14 h1:/eMIwjv+GFm6A/sSGlB1NupBU6wTDPhEWsju0Fj69kY= -k8s.io/apimachinery v0.31.14/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.14 h1:DORopWIu2qg7gmVyA9UUGGGmO1Rmnq5Oe+GmsKen3yo= -k8s.io/apiserver v0.31.14/go.mod h1:q81QJuh85u/HN74pdw5Ci4EnrRmCOonZj9FvLwf8DWc= -k8s.io/client-go v0.31.14 h1:d4/G0xfksNIbMWH7ghjzOwC5bTAwQ20gABTjZw7fLlQ= -k8s.io/client-go v0.31.14/go.mod h1:0uRpRB7r5QwtsbxEngZPkbcIVoNdAQAPIcopgiXjhQc= -k8s.io/component-base v0.31.14 h1:VNjBuEMmvlwL4twRlMmlaVmsodIRaNivXcZoAx1/x7Q= -k8s.io/component-base v0.31.14/go.mod h1:9ogYcJBUdB4VQ/OMgInYVRScC9bguXxSEEZPsInY+uM= -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-20250627150254-e9823e99808e h1:UGI9rv1A2cV87NhXr4s+AUBxIuoo/SME/IyJ3b6KztE= -k8s.io/kube-openapi v0.0.0-20250627150254-e9823e99808e/go.mod h1:GLOk5B+hDbRROvt0X2+hqX64v/zO3vXN7J78OUmBSKw= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= -k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.19.7 h1:DLABZfMr20A+AwCZOHhcbcu+TqBXnJZaVBri9K3EO48= -sigs.k8s.io/controller-runtime v0.19.7/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4= -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/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= -sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= -sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/placement-import/hack/boilerplate.go.txt b/placement-import/hack/boilerplate.go.txt deleted file mode 100644 index b7950f8af..000000000 --- a/placement-import/hack/boilerplate.go.txt +++ /dev/null @@ -1,15 +0,0 @@ -/* -Copyright 2022. - -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. -*/ diff --git a/placement-import/hack/build-crd-schema-checker.sh b/placement-import/hack/build-crd-schema-checker.sh deleted file mode 100755 index 12bf3be9a..000000000 --- a/placement-import/hack/build-crd-schema-checker.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -euxo pipefail - -if [ -f "$INSTALL_DIR/crd-schema-checker" ]; then - exit 0 -fi - -mkdir -p "$INSTALL_DIR/git-tmp" -git clone https://github.com/openshift/crd-schema-checker.git \ - -b "$CRD_SCHEMA_CHECKER_VERSION" "$INSTALL_DIR/git-tmp" -pushd "$INSTALL_DIR/git-tmp" -GOWORK=off make -cp crd-schema-checker "$INSTALL_DIR/" -popd -rm -rf "$INSTALL_DIR/git-tmp" diff --git a/placement-import/hack/clean_local_webhook.sh b/placement-import/hack/clean_local_webhook.sh deleted file mode 100755 index 35c465091..000000000 --- a/placement-import/hack/clean_local_webhook.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -ex - -oc delete validatingwebhookconfiguration/vplacementapi.kb.io --ignore-not-found -oc delete mutatingwebhookconfiguration/mplacementapi.kb.io --ignore-not-found diff --git a/placement-import/hack/crd-schema-checker.sh b/placement-import/hack/crd-schema-checker.sh deleted file mode 100755 index 3a252acd6..000000000 --- a/placement-import/hack/crd-schema-checker.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -set -euxo pipefail - -CHECKER=$INSTALL_DIR/crd-schema-checker - -TMP_DIR=$(mktemp -d) - -function cleanup { - rm -rf "$TMP_DIR" -} - -trap cleanup EXIT - - -for crd in config/crd/bases/*.yaml; do - mkdir -p "$(dirname "$TMP_DIR/$crd")" - if git show "$BASE_REF:$crd" > "$TMP_DIR/$crd"; then - $CHECKER check-manifests \ - --existing-crd-filename="$TMP_DIR/$crd" \ - --new-crd-filename="$crd" - fi -done diff --git a/placement-import/hack/run_with_local_webhook.sh b/placement-import/hack/run_with_local_webhook.sh deleted file mode 100755 index 48c31dae0..000000000 --- a/placement-import/hack/run_with_local_webhook.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash -set -ex - -# Define a cleanup function -cleanup() { - echo "Caught signal, cleaning up local webhooks..." - ./hack/clean_local_webhook.sh - exit 0 -} - -# Set trap to catch SIGINT and SIGTERM -trap cleanup SIGINT SIGTERM - -TMPDIR=${TMPDIR:-"/tmp/k8s-webhook-server/serving-certs"} -SKIP_CERT=${SKIP_CERT:-false} -CRC_IP=${CRC_IP:-$(/sbin/ip -o -4 addr list crc | awk '{print $4}' | cut -d/ -f1)} -FIREWALL_ZONE=${FIREWALL_ZONE:-"libvirt"} -WEBHOOK_PORT=${WEBHOOK_PORT:-${WEBHOOK_PORT}} -SKIP_FIREWALL=${SKIP_FIREWALL:-false} - -if [ "$SKIP_FIREWALL" = false ] ; then - #Open ${WEBHOOK_PORT} - sudo firewall-cmd --zone=${FIREWALL_ZONE} --add-port=${WEBHOOK_PORT}/tcp - sudo firewall-cmd --runtime-to-permanent -fi - -# Generate the certs and the ca bundle -if [ "$SKIP_CERT" = false ] ; then - mkdir -p ${TMPDIR} - rm -rf ${TMPDIR}/* || true - - openssl req -newkey rsa:2048 -days 3650 -nodes -x509 \ - -subj "/CN=${HOSTNAME}" \ - -addext "subjectAltName = IP:${CRC_IP}" \ - -keyout ${TMPDIR}/tls.key \ - -out ${TMPDIR}/tls.crt - - cat ${TMPDIR}/tls.crt ${TMPDIR}/tls.key | base64 -w 0 > ${TMPDIR}/bundle.pem - -fi - -CA_BUNDLE=`cat ${TMPDIR}/bundle.pem` - -# Patch the webhook(s) -cat >> ${TMPDIR}/patch_webhook_configurations.yaml < "${CSV_FILE}" - - printf \ - "\n\tNow patching operator CSV to remove its OLM deployment and associated webhooks. - The original OLM version of the operator's CSV has been copied to %s. To restore it, use: - oc patch -n openstack-operators %s --type=merge --patch-file=%s\n\n" "${CSV_FILE}" "${CSV_NAME}" "${CSV_FILE}" - fi - - oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" - oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/webhookdefinitions', 'value': []}]" -else - # Handle operator deployed by Openstack Initialization resource - CSV_NAME="$(oc get csv -n openstack-operators -l operators.coreos.com/openstack-operator.openstack-operators -o name)" - - printf \ - "\n\tNow patching openstack operator CSV to scale down deployment resource. - To restore it, use: - oc patch "${CSV_NAME}" -n openstack-operators --type=json -p=\"[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 1}]\"" - - oc patch "${CSV_NAME}" -n openstack-operators --type=json -p="[{'op': 'replace', 'path': '/spec/install/spec/deployments/0/spec/replicas', 'value': 0}]" - oc scale --replicas=0 -n openstack-operators deploy/placement-operator-controller-manager -fi - -go run ./cmd/main.go -metrics-bind-address ":${METRICS_PORT}" -health-probe-bind-address ":${HEALTH_PORT}" -pprof-bind-address ":${PPROF_PORT}" -webhook-bind-address "${WEBHOOK_PORT}" diff --git a/placement-import/kuttl-test.yaml b/placement-import/kuttl-test.yaml deleted file mode 100644 index 369895d95..000000000 --- a/placement-import/kuttl-test.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# -# EXECUTION (from placement-operator repo root directory): -# -# kubectl-kuttl test --config ${PLACEMENT_KUTTL_CONF} ${PLACEMENT_KUTTL_DIR} -# -# PLACEMENT_KUTTL_CONF: path to 'kuttl-test.yaml' there is a default kuttl-test.yaml in the top directory -# of placement -# PLACEMENT_KUTTL_DIR: the test directory containing kuttl tests, relative to the placement repo -# that can be found in '////placement-operator/test/kuttl/tests' -# -# ASSUMPTIONS: -# -# 1. Latest version of kuttl is installed at /usr/local/bin/kubectl-kuttl -# - wget https://github.com/kudobuilder/kuttl/releases/download/v0.11.1/kubectl-kuttl_0.11.1_linux_x86_64 -# - mv kubectl-kuttl_0.11.1_linux_x86_64 /usr/local/bin/kubectl-kuttl -# - chmod 755 /usr/local/bin/kubectl-kuttl -# 2. An OCP 4.10+ CRC cluster with Mariadb and Keystone operators are deployed (details below using devsetup -# and install_yamls) -# 2a. CPUS=12 MEMORY=12016 make crc -# 2b. oc login -u kubeadmin -p 12345678 https://api.crc.testing:6443 -# 2c. eval $(crc oc-env) -# 2d. make crc_storage -# 2e. make input -# 2f. make keystone -# 2g. make mariadb -# 2h. make keystone_deploy -# 2i. make mariadb_deploy -# 2j. start placement-operator either through install_yamls via 'make placement' or in another -# terminal execute 'OPERATOR_TEMPLATES=./templates make run' within root of placement repo -# 3. The above environment variables PLACEMENT_KUTTL_CONF and PLACEMENT_KUTTL_DIR are set - -apiVersion: kuttl.dev/v1beta1 -kind: TestSuite -reportFormat: JSON -reportName: kuttl-test-placement -namespace: placement-kuttl-tests -timeout: 300 -parallel: 1 -suppress: - - events diff --git a/placement-import/renovate.json b/placement-import/renovate.json deleted file mode 100644 index 2a602aa99..000000000 --- a/placement-import/renovate.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": [ - "github>openstack-k8s-operators/renovate-config:default.json5" - ], - "packageRules": [ - { - "matchPackageNames": ["github.com/openstack-k8s-operators/placement-operator/api"], - "enabled": false - } - ], - "postUpgradeTasks": { - "commands": ["make gowork", "make tidy", "make manifests generate"], - "fileFilters": ["**/go.mod", "**/go.sum", "**/*.go", "**/*.yaml"], - "executionMode": "update" - } -} From 7d2a96ac3ce419810b3a6db72ef694fb06e6e55c Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:37:10 +0530 Subject: [PATCH 09/16] Update placement import paths and test package names Update all import paths in placement code from github.com/openstack-k8s-operators/placement-operator/api/v1beta1 to github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1 and github.com/openstack-k8s-operators/placement-operator/internal/placement github.com/openstack-k8s-operators/nova-operator/internal/placement package name update functional_test to placement_test --- internal/controller/placement/api_controller.go | 4 ++-- internal/placement/dbsync.go | 2 +- internal/placement/deployment.go | 2 +- internal/webhook/placement/v1beta1/api_webhook.go | 2 +- test/functional/placement/api_controller_test.go | 4 ++-- test/functional/placement/api_webhook_test.go | 4 ++-- test/functional/placement/base_test.go | 6 +++--- test/functional/placement/suite_test.go | 8 ++++---- 8 files changed, 16 insertions(+), 16 deletions(-) diff --git a/internal/controller/placement/api_controller.go b/internal/controller/placement/api_controller.go index e4fe1990b..532bfe6df 100644 --- a/internal/controller/placement/api_controller.go +++ b/internal/controller/placement/api_controller.go @@ -59,8 +59,8 @@ import ( mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" - placement "github.com/openstack-k8s-operators/placement-operator/internal/placement" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" + placement "github.com/openstack-k8s-operators/nova-operator/internal/placement" appsv1 "k8s.io/api/apps/v1" batchv1 "k8s.io/api/batch/v1" diff --git a/internal/placement/dbsync.go b/internal/placement/dbsync.go index 667b1fc80..945e255a4 100644 --- a/internal/placement/dbsync.go +++ b/internal/placement/dbsync.go @@ -16,7 +16,7 @@ limitations under the License. package placement import ( - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" env "github.com/openstack-k8s-operators/lib-common/modules/common/env" diff --git a/internal/placement/deployment.go b/internal/placement/deployment.go index b09236390..737a858d6 100644 --- a/internal/placement/deployment.go +++ b/internal/placement/deployment.go @@ -23,7 +23,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/tls" topologyv1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/internal/webhook/placement/v1beta1/api_webhook.go b/internal/webhook/placement/v1beta1/api_webhook.go index 042fb6eaf..314bc3d28 100644 --- a/internal/webhook/placement/v1beta1/api_webhook.go +++ b/internal/webhook/placement/v1beta1/api_webhook.go @@ -28,7 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - placementv1beta1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + placementv1beta1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" ) var ( diff --git a/test/functional/placement/api_controller_test.go b/test/functional/placement/api_controller_test.go index 93da1e907..905439e5d 100644 --- a/test/functional/placement/api_controller_test.go +++ b/test/functional/placement/api_controller_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package functional_test +package placement_test import ( "fmt" @@ -30,7 +30,7 @@ import ( . "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" mariadb_test "github.com/openstack-k8s-operators/mariadb-operator/api/test/helpers" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" - "github.com/openstack-k8s-operators/placement-operator/internal/placement" + "github.com/openstack-k8s-operators/nova-operator/internal/placement" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" ) diff --git a/test/functional/placement/api_webhook_test.go b/test/functional/placement/api_webhook_test.go index 7e2aaa841..d68ff37de 100644 --- a/test/functional/placement/api_webhook_test.go +++ b/test/functional/placement/api_webhook_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package functional_test +package placement_test import ( "errors" @@ -34,7 +34,7 @@ import ( condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" "github.com/openstack-k8s-operators/lib-common/modules/common/service" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" ) var _ = Describe("PlacementAPI Webhook", func() { diff --git a/test/functional/placement/base_test.go b/test/functional/placement/base_test.go index 653c5605c..e31bfbe04 100644 --- a/test/functional/placement/base_test.go +++ b/test/functional/placement/base_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package functional_test +package placement_test import ( "fmt" @@ -22,8 +22,8 @@ import ( . "github.com/onsi/gomega" //revive:disable:dot-imports condition "github.com/openstack-k8s-operators/lib-common/modules/common/condition" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" - "github.com/openstack-k8s-operators/placement-operator/internal/placement" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" + "github.com/openstack-k8s-operators/nova-operator/internal/placement" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" diff --git a/test/functional/placement/suite_test.go b/test/functional/placement/suite_test.go index e243a6655..c5c963db6 100644 --- a/test/functional/placement/suite_test.go +++ b/test/functional/placement/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package functional_test +package placement_test import ( "context" @@ -47,9 +47,9 @@ import ( keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1" test "github.com/openstack-k8s-operators/lib-common/modules/test" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" - placementv1 "github.com/openstack-k8s-operators/placement-operator/api/v1beta1" - controllers "github.com/openstack-k8s-operators/placement-operator/internal/controller" - webhookv1 "github.com/openstack-k8s-operators/placement-operator/internal/webhook/v1beta1" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" + controllers "github.com/openstack-k8s-operators/nova-operator/internal/controller/placement" + webhookv1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/placement/v1beta1" keystone_test "github.com/openstack-k8s-operators/keystone-operator/api/test/helpers" common_test "github.com/openstack-k8s-operators/lib-common/modules/common/test/helpers" From f44c9c90b92c67b88550b8ebe5436b2a60c36c71 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Mon, 6 Apr 2026 15:18:57 +0530 Subject: [PATCH 10/16] Add placement sample files from placement-operator --- config/samples/kustomization.yaml | 1 + .../placement_v1beta1_placementapi.yaml | 18 ++++++++++++++++ .../placement_v1beta1_placementtls.yaml | 21 +++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 config/samples/placement_v1beta1_placementapi.yaml create mode 100644 config/samples/placement_v1beta1_placementtls.yaml diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 84a35fd9b..af7fabef2 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -8,4 +8,5 @@ resources: - nova_v1beta1_novacell1-upcall.yaml - nova_v1beta1_nova.yaml - nova_v1beta1_novacompute-ironic.yaml +- placement_v1beta1_placementapi.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/placement_v1beta1_placementapi.yaml b/config/samples/placement_v1beta1_placementapi.yaml new file mode 100644 index 000000000..2740db960 --- /dev/null +++ b/config/samples/placement_v1beta1_placementapi.yaml @@ -0,0 +1,18 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + serviceUser: placement + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + preserveJobs: false + replicas: 1 + secret: placement-secret + #resources: + # requests: + # memory: "500Mi" + # cpu: "1.0" diff --git a/config/samples/placement_v1beta1_placementtls.yaml b/config/samples/placement_v1beta1_placementtls.yaml new file mode 100644 index 000000000..76c8c0a90 --- /dev/null +++ b/config/samples/placement_v1beta1_placementtls.yaml @@ -0,0 +1,21 @@ +apiVersion: placement.openstack.org/v1beta1 +kind: PlacementAPI +metadata: + name: placement +spec: + serviceUser: placement + customServiceConfig: | + [DEFAULT] + debug = true + databaseInstance: openstack + databaseAccount: placement + preserveJobs: false + replicas: 1 + secret: placement-secret + tls: + api: + internal: + secretName: cert-internal-svc + public: + secretName: cert-public-svc + caBundleSecretName: combined-ca-bundle From 0de63d5d1ade686069f84e98f5a3a6f31261b8dd Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Fri, 27 Mar 2026 16:50:14 +0530 Subject: [PATCH 11/16] Update Makefile to generate placement manifests --- Makefile | 7 +- ...placement.openstack.org_placementapis.yaml | 502 ++++++++++++++++++ .../v1beta1/zz_generated.deepcopy.go | 273 ++++++++++ config/rbac/role.yaml | 29 + 4 files changed, 808 insertions(+), 3 deletions(-) create mode 100644 api/bases/placement.openstack.org_placementapis.yaml create mode 100644 api/placement/v1beta1/zz_generated.deepcopy.go diff --git a/Makefile b/Makefile index f8100d0cb..2c87f97a2 100644 --- a/Makefile +++ b/Makefile @@ -114,12 +114,13 @@ docker-buildx: ## Build and push docker image for the manager for cross-platform .PHONY: manifests manifests: gowork controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases && \ + $(CONTROLLER_GEN) crd webhook paths="./api/nova/..." paths="./api/placement/..." output:crd:artifacts:config=config/crd/bases && \ + $(CONTROLLER_GEN) rbac:roleName=manager-role paths="./..." output:dir=config/rbac && \ rm -f api/bases/* && cp -a config/crd/bases api/ .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./api/nova/..." paths="./api/placement/..." .PHONY: fmt fmt: ## Run go fmt against code. @@ -150,7 +151,7 @@ PROC_CMD = --procs ${PROCS} test: manifests generate fmt vet envtest ginkgo ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" \ OPERATOR_TEMPLATES="$(PWD)/templates" \ - $(GINKGO) --trace --cover --coverpkg=../../internal/...,../../api/nova/v1beta1 --coverprofile cover.out --covermode=atomic --randomize-all ${PROC_CMD} $(GINKGO_ARGS) ./test/... + $(GINKGO) --trace --cover --coverpkg=../../internal/...,../../api/nova/v1beta1,../../api/placement/v1beta1 --coverprofile cover.out --covermode=atomic --randomize-all ${PROC_CMD} $(GINKGO_ARGS) ./test/... ##@ Build diff --git a/api/bases/placement.openstack.org_placementapis.yaml b/api/bases/placement.openstack.org_placementapis.yaml new file mode 100644 index 000000000..73ef0098b --- /dev/null +++ b/api/bases/placement.openstack.org_placementapis.yaml @@ -0,0 +1,502 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: placementapis.placement.openstack.org +spec: + group: placement.openstack.org + names: + kind: PlacementAPI + listKind: PlacementAPIList + plural: placementapis + singular: placementapi + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: NetworkAttachments + jsonPath: .spec.networkAttachments + name: NetworkAttachments + type: string + - description: Status + jsonPath: .status.conditions[0].status + name: Status + type: string + - description: Message + jsonPath: .status.conditions[0].message + name: Message + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: PlacementAPI is the Schema for the placementapis API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: PlacementAPISpec defines the desired state of PlacementAPI + properties: + apiTimeout: + default: 60 + description: APITimeout for HAProxy, Apache + minimum: 10 + type: integer + auth: + description: Auth - Parameters related to authentication + properties: + applicationCredentialSecret: + description: ApplicationCredentialSecret - Secret containing Application + Credential ID and Secret + type: string + type: object + containerImage: + description: PlacementAPI Container Image URL (will be set to environmental + default if empty) + type: string + customServiceConfig: + description: |- + CustomServiceConfig - customize the service config using this parameter to change service defaults, + or overwrite rendered information using raw OpenStack config format. The content gets added to + to /etc//.conf.d directory as custom.conf file. + type: string + databaseAccount: + default: placement + description: DatabaseAccount - name of MariaDBAccount which will be + used to connect. + type: string + databaseInstance: + description: |- + MariaDB instance name + Right now required by the maridb-operator to get the credentials from the instance to create the DB + Might not be required in future + type: string + defaultConfigOverwrite: + additionalProperties: + type: string + description: DefaultConfigOverwrite - interface to overwrite default + config files like policy.yaml. + type: object + networkAttachments: + description: NetworkAttachments is a list of NetworkAttachment resource + names to expose the services to the given network + items: + type: string + type: array + nodeSelector: + additionalProperties: + type: string + description: NodeSelector to target subset of worker nodes running + this service + type: object + override: + description: Override, provides the ability to override the generated + manifest of several child resources. + properties: + service: + additionalProperties: + description: |- + RoutedOverrideSpec - a routed service override configuration for the Service created to serve traffic + to the cluster. Allows for the manifest of the created Service to be overwritten with custom configuration. + properties: + endpointURL: + type: string + metadata: + description: |- + EmbeddedLabelsAnnotations is an embedded subset of the fields included in k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta. + Only labels and annotations are included. + properties: + annotations: + additionalProperties: + type: string + description: |- + Annotations is an unstructured key value map stored with a resource that may be + set by external tools to store and retrieve arbitrary metadata. They are not + queryable and should be preserved when modifying objects. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + type: object + labels: + additionalProperties: + type: string + description: |- + Map of string keys and values that can be used to organize and categorize + (scope and select) objects. May match selectors of replication controllers + and services. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + type: object + type: object + spec: + description: |- + OverrideServiceSpec is a subset of the fields included in https://pkg.go.dev/k8s.io/api@v0.26.6/core/v1#ServiceSpec + Limited to Type, SessionAffinity, LoadBalancerSourceRanges, ExternalName, ExternalTrafficPolicy, SessionAffinityConfig, + IPFamilyPolicy, LoadBalancerClass and InternalTrafficPolicy + properties: + externalName: + description: |- + externalName is the external reference that discovery mechanisms will + return as an alias for this service (e.g. a DNS CNAME record). No + proxying will be involved. Must be a lowercase RFC-1123 hostname + (https://tools.ietf.org/html/rfc1123) and requires `type` to be "ExternalName". + type: string + externalTrafficPolicy: + description: |- + externalTrafficPolicy describes how nodes distribute service traffic they + receive on one of the Service's "externally-facing" addresses (NodePorts, + ExternalIPs, and LoadBalancer IPs). If set to "Local", the proxy will configure + the service in a way that assumes that external load balancers will take care + of balancing the service traffic between nodes, and so each node will deliver + traffic only to the node-local endpoints of the service, without masquerading + the client source IP. (Traffic mistakenly sent to a node with no endpoints will + be dropped.) The default value, "Cluster", uses the standard behavior of + routing to all endpoints evenly (possibly modified by topology and other + features). Note that traffic sent to an External IP or LoadBalancer IP from + within the cluster will always get "Cluster" semantics, but clients sending to + a NodePort from within the cluster may need to take traffic policy into account + when picking a node. + type: string + internalTrafficPolicy: + description: |- + InternalTrafficPolicy describes how nodes distribute service traffic they + receive on the ClusterIP. If set to "Local", the proxy will assume that pods + only want to talk to endpoints of the service on the same node as the pod, + dropping the traffic if there are no local endpoints. The default value, + "Cluster", uses the standard behavior of routing to all endpoints evenly + (possibly modified by topology and other features). + type: string + ipFamilyPolicy: + description: |- + IPFamilyPolicy represents the dual-stack-ness requested or required by + this Service. If there is no value provided, then this field will be set + to SingleStack. Services can be "SingleStack" (a single IP family), + "PreferDualStack" (two IP families on dual-stack configured clusters or + a single IP family on single-stack clusters), or "RequireDualStack" + (two IP families on dual-stack configured clusters, otherwise fail). The + ipFamilies and clusterIPs fields depend on the value of this field. This + field will be wiped when updating a service to type ExternalName. + type: string + loadBalancerClass: + description: |- + loadBalancerClass is the class of the load balancer implementation this Service belongs to. + If specified, the value of this field must be a label-style identifier, with an optional prefix, + e.g. "internal-vip" or "example.com/internal-vip". Unprefixed names are reserved for end-users. + This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load + balancer implementation is used, today this is typically done through the cloud provider integration, + but should apply for any default implementation. If set, it is assumed that a load balancer + implementation is watching for Services with a matching class. Any default load balancer + implementation (e.g. cloud providers) should ignore Services that set this field. + This field can only be set when creating or updating a Service to type 'LoadBalancer'. + Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type. + type: string + loadBalancerSourceRanges: + description: |- + If specified and supported by the platform, this will restrict traffic through the cloud-provider + load-balancer will be restricted to the specified client IPs. This field will be ignored if the + cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/ + items: + type: string + type: array + x-kubernetes-list-type: atomic + sessionAffinity: + description: |- + Supports "ClientIP" and "None". Used to maintain session affinity. + Enable client IP based session affinity. + Must be ClientIP or None. + Defaults to None. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies + type: string + sessionAffinityConfig: + description: sessionAffinityConfig contains the configurations + of session affinity. + properties: + clientIP: + description: clientIP contains the configurations + of Client IP based session affinity. + properties: + timeoutSeconds: + description: |- + timeoutSeconds specifies the seconds of ClientIP type session sticky time. + The value must be >0 && <=86400(for 1 day) if ServiceAffinity == "ClientIP". + Default value is 10800(for 3 hours). + format: int32 + type: integer + type: object + type: object + type: + description: |- + type determines how the Service is exposed. Defaults to ClusterIP. Valid + options are ExternalName, ClusterIP, NodePort, and LoadBalancer. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that is not + specified, by manual construction of an Endpoints object or + EndpointSlice objects. If clusterIP is "None", no virtual IP is + allocated and the endpoints are published as a set of endpoints rather + than a virtual IP. + "NodePort" builds on ClusterIP and allocates a port on every node which + routes to the same endpoints as the clusterIP. + "LoadBalancer" builds on NodePort and creates an external load-balancer + (if supported in the current cloud) which routes to the same endpoints + as the clusterIP. + "ExternalName" aliases this service to the specified externalName. + Several other fields do not apply to ExternalName services. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types + type: string + type: object + type: object + description: |- + Override configuration for the Service created to serve traffic to the cluster. + The key must be the endpoint type (public, internal) + type: object + type: object + passwordSelectors: + default: + service: PlacementPassword + description: PasswordSelectors - Selectors to identify the DB and + ServiceUser password from the Secret + properties: + service: + default: PlacementPassword + description: Service - Selector to get the service user password + from the Secret + type: string + type: object + preserveJobs: + default: false + description: PreserveJobs - do not delete jobs after they finished + e.g. to check logs + type: boolean + replicas: + default: 1 + description: Replicas of placement API to run + format: int32 + maximum: 32 + minimum: 0 + type: integer + resources: + description: |- + Resources - Compute Resources required by this service (Limits/Requests). + https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + properties: + claims: + description: |- + Claims lists the names of resources, defined in spec.resourceClaims, + that are used by this container. + + This is an alpha field and requires enabling the + DynamicResourceAllocation feature gate. + + This field is immutable. It can only be set for containers. + items: + description: ResourceClaim references one entry in PodSpec.ResourceClaims. + properties: + name: + description: |- + Name must match the name of one entry in pod.spec.resourceClaims of + the Pod where this field is used. It makes that resource available + inside a container. + type: string + request: + description: |- + Request is the name chosen for a request in the referenced claim. + If empty, everything from the claim is made available, otherwise + only the result of this request. + type: string + required: + - name + type: object + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to an implementation-defined value. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + secret: + description: Secret containing OpenStack password information for + placement PlacementPassword + type: string + serviceUser: + default: placement + description: ServiceUser - optional username used for this service + to register in keystone + type: string + tls: + description: TLS - Parameters related to the TLS + properties: + api: + description: API tls type which encapsulates for API services + properties: + internal: + description: Internal GenericService - holds the secret for + the internal endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + public: + description: Public GenericService - holds the secret for + the public endpoint + properties: + secretName: + description: SecretName - holding the cert, key for the + service + type: string + type: object + type: object + caBundleSecretName: + description: CaBundleSecretName - holding the CA certs in a pre-created + bundle file + type: string + type: object + topologyRef: + description: |- + TopologyRef to apply the Topology defined by the associated CR referenced + by name + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + required: + - containerImage + - databaseInstance + - secret + type: object + status: + description: PlacementAPIStatus defines the observed state of PlacementAPI + properties: + conditions: + description: Conditions + items: + description: Condition defines an observation of a API resource + operational state. + properties: + lastTransitionTime: + description: |- + Last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when + the API field changed is acceptable. + format: date-time + type: string + message: + description: A human readable message indicating details about + the transition. + type: string + reason: + description: The reason for the condition's last transition + in CamelCase. + type: string + severity: + description: |- + Severity provides a classification of Reason code, so the current situation is immediately + understandable and could act accordingly. + It is meant for situations where Status=False and it should be indicated if it is just + informational, warning (next reconciliation might fix it) or an error (e.g. DB create issue + and no actions to automatically resolve the issue can/should be done). + For conditions where Status=Unknown or Status=True the Severity should be SeverityNone. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of condition in CamelCase. + type: string + required: + - lastTransitionTime + - status + - type + type: object + type: array + databaseHostname: + description: Placement Database Hostname + type: string + hash: + additionalProperties: + type: string + description: Map of hashes to track e.g. job status + type: object + lastAppliedTopology: + description: LastAppliedTopology - the last applied Topology + properties: + name: + description: Name - The Topology CR name that the Service references + type: string + namespace: + description: |- + Namespace - The Namespace to fetch the Topology CR referenced + NOTE: Namespace currently points by default to the same namespace where + the Service is deployed. Customizing the namespace is not supported and + webhooks prevent editing this field to a value different from the + current project + type: string + type: object + networkAttachments: + additionalProperties: + items: + type: string + type: array + description: NetworkAttachments status of the deployment pods + type: object + observedGeneration: + description: ObservedGeneration - the most recent generation observed + for this service. If the observed generation is less than the spec + generation, then the controller has not processed the latest changes. + format: int64 + type: integer + readyCount: + description: ReadyCount of placement API instances + format: int32 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/api/placement/v1beta1/zz_generated.deepcopy.go b/api/placement/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 000000000..8c4b13be3 --- /dev/null +++ b/api/placement/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,273 @@ +//go:build !ignore_autogenerated + +/* +Copyright 2022. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + topologyv1beta1 "github.com/openstack-k8s-operators/infra-operator/apis/topology/v1beta1" + "github.com/openstack-k8s-operators/lib-common/modules/common/condition" + "github.com/openstack-k8s-operators/lib-common/modules/common/service" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *APIOverrideSpec) DeepCopyInto(out *APIOverrideSpec) { + *out = *in + if in.Service != nil { + in, out := &in.Service, &out.Service + *out = make(map[service.Endpoint]service.RoutedOverrideSpec, len(*in)) + for key, val := range *in { + (*out)[key] = *val.DeepCopy() + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new APIOverrideSpec. +func (in *APIOverrideSpec) DeepCopy() *APIOverrideSpec { + if in == nil { + return nil + } + out := new(APIOverrideSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AuthSpec) DeepCopyInto(out *AuthSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthSpec. +func (in *AuthSpec) DeepCopy() *AuthSpec { + if in == nil { + return nil + } + out := new(AuthSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PasswordSelector) DeepCopyInto(out *PasswordSelector) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PasswordSelector. +func (in *PasswordSelector) DeepCopy() *PasswordSelector { + if in == nil { + return nil + } + out := new(PasswordSelector) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPI) DeepCopyInto(out *PlacementAPI) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPI. +func (in *PlacementAPI) DeepCopy() *PlacementAPI { + if in == nil { + return nil + } + out := new(PlacementAPI) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlacementAPI) 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 *PlacementAPIDefaults) DeepCopyInto(out *PlacementAPIDefaults) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIDefaults. +func (in *PlacementAPIDefaults) DeepCopy() *PlacementAPIDefaults { + if in == nil { + return nil + } + out := new(PlacementAPIDefaults) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPIList) DeepCopyInto(out *PlacementAPIList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]PlacementAPI, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIList. +func (in *PlacementAPIList) DeepCopy() *PlacementAPIList { + if in == nil { + return nil + } + out := new(PlacementAPIList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PlacementAPIList) 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 *PlacementAPISpec) DeepCopyInto(out *PlacementAPISpec) { + *out = *in + in.PlacementAPISpecCore.DeepCopyInto(&out.PlacementAPISpecCore) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpec. +func (in *PlacementAPISpec) DeepCopy() *PlacementAPISpec { + if in == nil { + return nil + } + out := new(PlacementAPISpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPISpecCore) DeepCopyInto(out *PlacementAPISpecCore) { + *out = *in + if in.Replicas != nil { + in, out := &in.Replicas, &out.Replicas + *out = new(int32) + **out = **in + } + out.PasswordSelectors = in.PasswordSelectors + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(map[string]string) + if **in != nil { + in, out := *in, *out + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + } + if in.DefaultConfigOverwrite != nil { + in, out := &in.DefaultConfigOverwrite, &out.DefaultConfigOverwrite + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.Resources.DeepCopyInto(&out.Resources) + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.Override.DeepCopyInto(&out.Override) + in.TLS.DeepCopyInto(&out.TLS) + out.Auth = in.Auth + if in.TopologyRef != nil { + in, out := &in.TopologyRef, &out.TopologyRef + *out = new(topologyv1beta1.TopoRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPISpecCore. +func (in *PlacementAPISpecCore) DeepCopy() *PlacementAPISpecCore { + if in == nil { + return nil + } + out := new(PlacementAPISpecCore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PlacementAPIStatus) DeepCopyInto(out *PlacementAPIStatus) { + *out = *in + if in.Hash != nil { + in, out := &in.Hash, &out.Hash + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make(condition.Conditions, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.NetworkAttachments != nil { + in, out := &in.NetworkAttachments, &out.NetworkAttachments + *out = make(map[string][]string, len(*in)) + for key, val := range *in { + var outVal []string + if val == nil { + (*out)[key] = nil + } else { + inVal := (*in)[key] + in, out := &inVal, &outVal + *out = make([]string, len(*in)) + copy(*out, *in) + } + (*out)[key] = outVal + } + } + if in.LastAppliedTopology != nil { + in, out := &in.LastAppliedTopology, &out.LastAppliedTopology + *out = new(topologyv1beta1.TopoRef) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PlacementAPIStatus. +func (in *PlacementAPIStatus) DeepCopy() *PlacementAPIStatus { + if in == nil { + return nil + } + out := new(PlacementAPIStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 422793d58..f248e8d06 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -7,6 +7,7 @@ rules: - apiGroups: - "" resources: + - configmaps - pods - secrets - services @@ -32,6 +33,7 @@ rules: - apiGroups: - apps resources: + - deployments - statefulsets verbs: - create @@ -169,6 +171,33 @@ rules: - get - patch - update +- apiGroups: + - placement.openstack.org + resources: + - placementapis + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - placement.openstack.org + resources: + - placementapis/finalizers + verbs: + - patch + - update +- apiGroups: + - placement.openstack.org + resources: + - placementapis/status + verbs: + - get + - patch + - update - apiGroups: - rabbitmq.openstack.org resources: From 985eeb34597b39b5e00edca4f84a3cc766662568 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Wed, 1 Apr 2026 15:36:02 +0530 Subject: [PATCH 12/16] Register PlacementAPI controller and webhook into nova-operator - Register PlacementAPI controller in cmd/main.go - Register PlacementAPI webhook in cmd/main.go - Add placement scheme and defaults initialization - Fix placement test paths after moving to test/functional/placement/ - Add placement API to test coverage in Makefile --- Makefile | 2 ++ cmd/main.go | 23 +++++++++++++++++-- .../placement/api_controller_test.go | 4 ++-- test/functional/placement/api_webhook_test.go | 2 +- test/functional/placement/suite_test.go | 10 ++++---- 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 2c87f97a2..91fee986f 100644 --- a/Makefile +++ b/Makefile @@ -148,6 +148,8 @@ PROCS?=$(shell expr $(shell nproc --ignore 2) / 2) PROC_CMD = --procs ${PROCS} .PHONY: test +# TODO: Currently runs all tests (Nova + Placement). In future, optimize CI to run only tests +# for the operator code that changed (e.g., skip Placement tests if only Nova code changed). test: manifests generate fmt vet envtest ginkgo ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) -v debug --bin-dir $(LOCALBIN) use $(ENVTEST_K8S_VERSION) -p path)" \ OPERATOR_TEMPLATES="$(PWD)/templates" \ diff --git a/cmd/main.go b/cmd/main.go index e6a81938d..f2a2af84e 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -38,8 +38,10 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" - "github.com/openstack-k8s-operators/nova-operator/internal/controller/nova" + novacontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/nova" + placementcontroller "github.com/openstack-k8s-operators/nova-operator/internal/controller/placement" webhookv1beta1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/nova/v1beta1" + placementwebhookv1 "github.com/openstack-k8s-operators/nova-operator/internal/webhook/placement/v1beta1" // +kubebuilder:scaffold:imports networkv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" memcachedv1 "github.com/openstack-k8s-operators/infra-operator/apis/memcached/v1beta1" @@ -49,6 +51,7 @@ import ( "github.com/openstack-k8s-operators/lib-common/modules/common/operator" mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1" novav1 "github.com/openstack-k8s-operators/nova-operator/api/nova/v1beta1" + placementv1 "github.com/openstack-k8s-operators/nova-operator/api/placement/v1beta1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" @@ -63,6 +66,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(novav1.AddToScheme(scheme)) + utilruntime.Must(placementv1.AddToScheme(scheme)) utilruntime.Must(mariadbv1.AddToScheme(scheme)) utilruntime.Must(keystonev1.AddToScheme(scheme)) utilruntime.Must(corev1.AddToScheme(scheme)) @@ -253,14 +257,25 @@ func main() { os.Exit(1) } - reconcilers := controller.NewReconcilers(mgr, kclient) + reconcilers := novacontroller.NewReconcilers(mgr, kclient) err = reconcilers.Setup(mgr, setupLog) if err != nil { os.Exit(1) } + // Setup PlacementAPI controller + if err = (&placementcontroller.PlacementAPIReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Kclient: kclient, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "PlacementAPI") + os.Exit(1) + } + // Acquire environmental defaults and initialize operator defaults with them novav1.SetupDefaults() + placementv1.SetupDefaults() // nolint:goconst checker := healthz.Ping @@ -299,6 +314,10 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "NovaCompute") os.Exit(1) } + if err := placementwebhookv1.SetupPlacementAPIWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "PlacementAPI") + os.Exit(1) + } checker = mgr.GetWebhookServer().StartedChecker() } diff --git a/test/functional/placement/api_controller_test.go b/test/functional/placement/api_controller_test.go index 905439e5d..5af303fb8 100644 --- a/test/functional/placement/api_controller_test.go +++ b/test/functional/placement/api_controller_test.go @@ -41,7 +41,7 @@ var _ = Describe("PlacementAPI controller", func() { // lib-common uses OPERATOR_TEMPLATES env var to locate the "templates" // directory of the operator. We need to set them othervise lib-common // will fail to generate the ConfigMap as it does not find common.sh - err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + err := os.Setenv("OPERATOR_TEMPLATES", "../../../templates") Expect(err).NotTo(HaveOccurred()) }) @@ -1404,7 +1404,7 @@ var _ = Describe("PlacementAPI controller", func() { var _ = Describe("PlacementAPI reconfiguration", func() { BeforeEach(func() { - err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + err := os.Setenv("OPERATOR_TEMPLATES", "../../../templates") Expect(err).NotTo(HaveOccurred()) }) diff --git a/test/functional/placement/api_webhook_test.go b/test/functional/placement/api_webhook_test.go index d68ff37de..b44fe5e76 100644 --- a/test/functional/placement/api_webhook_test.go +++ b/test/functional/placement/api_webhook_test.go @@ -48,7 +48,7 @@ var _ = Describe("PlacementAPI Webhook", func() { Namespace: namespace, } - err := os.Setenv("OPERATOR_TEMPLATES", "../../templates") + err := os.Setenv("OPERATOR_TEMPLATES", "../../../templates") Expect(err).NotTo(HaveOccurred()) }) diff --git a/test/functional/placement/suite_test.go b/test/functional/placement/suite_test.go index c5c963db6..2b933ed9f 100644 --- a/test/functional/placement/suite_test.go +++ b/test/functional/placement/suite_test.go @@ -103,13 +103,13 @@ var _ = BeforeSuite(func() { ctx, cancel = context.WithCancel(context.TODO()) keystoneCRDs, err := test.GetCRDDirFromModule( - "github.com/openstack-k8s-operators/keystone-operator/api", "../../go.mod", "bases") + "github.com/openstack-k8s-operators/keystone-operator/api", "../../../go.mod", "bases") Expect(err).ShouldNot(HaveOccurred()) mariaDBCRDs, err := test.GetCRDDirFromModule( - "github.com/openstack-k8s-operators/mariadb-operator/api", "../../go.mod", "bases") + "github.com/openstack-k8s-operators/mariadb-operator/api", "../../../go.mod", "bases") Expect(err).ShouldNot(HaveOccurred()) topologyCRDs, err := test.GetCRDDirFromModule( - "github.com/openstack-k8s-operators/infra-operator/apis", "../../go.mod", "bases") + "github.com/openstack-k8s-operators/infra-operator/apis", "../../../go.mod", "bases") Expect(err).ShouldNot(HaveOccurred()) By("bootstrapping test environment") @@ -119,7 +119,7 @@ var _ = BeforeSuite(func() { // Give it plenty of time to wind down (e.g., 60-120 seconds) ControlPlaneStopTimeout: 120 * time.Second, CRDDirectoryPaths: []string{ - filepath.Join("..", "..", "config", "crd", "bases"), + filepath.Join("..", "..", "..", "config", "crd", "bases"), // NOTE(gibi): we need to list all the external CRDs our operator depends on keystoneCRDs, mariaDBCRDs, @@ -127,7 +127,7 @@ var _ = BeforeSuite(func() { }, ErrorIfCRDPathMissing: true, WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + Paths: []string{filepath.Join("..", "..", "..", "config", "webhook")}, // NOTE(gibi): if localhost is resolved to ::1 (ipv6) then starting // the webhook fails as it try to parse the address as ipv4 and // failing on the colons in ::1 From c0710233bd0cbae0e0da03c098ace97eec3b3eb4 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Mon, 6 Apr 2026 15:03:32 +0530 Subject: [PATCH 13/16] Update kuttl test symlinks in placement kuttl tests --- test/kuttl/placement/common/placementapi_deploy.yaml | 2 +- .../tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/kuttl/placement/common/placementapi_deploy.yaml b/test/kuttl/placement/common/placementapi_deploy.yaml index 1302e1646..bcd2b84ea 120000 --- a/test/kuttl/placement/common/placementapi_deploy.yaml +++ b/test/kuttl/placement/common/placementapi_deploy.yaml @@ -1 +1 @@ -../../../config/samples/placement_v1beta1_placementapi.yaml \ No newline at end of file +../../../../config/samples/placement_v1beta1_placementapi.yaml \ No newline at end of file diff --git a/test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml b/test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml index 11447fab8..05708c6cc 120000 --- a/test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml +++ b/test/kuttl/placement/tests/placement_deploy_tls/02-placementapi_deploy_tls.yaml @@ -1 +1 @@ -../../../../config/samples/placement_v1beta1_placementtls.yaml \ No newline at end of file +../../../../../config/samples/placement_v1beta1_placementtls.yaml \ No newline at end of file From d0cc289a6155447b9ed404862107498cef3fc057 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Mon, 6 Apr 2026 15:23:01 +0530 Subject: [PATCH 14/16] Fix codespell typos in placement code --- internal/controller/placement/api_controller.go | 6 +++--- test/functional/placement/api_controller_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/placement/api_controller.go b/internal/controller/placement/api_controller.go index 532bfe6df..f571c21f2 100644 --- a/internal/controller/placement/api_controller.go +++ b/internal/controller/placement/api_controller.go @@ -225,7 +225,7 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request return ctrl.Result{}, err } - // Save a copy of the condtions so that we can restore the LastTransitionTime + // Save a copy of the conditions so that we can restore the LastTransitionTime // when a condition's state doesn't change. savedConditions := instance.Status.Conditions.DeepCopy() // initialize status fields @@ -482,7 +482,7 @@ func (r *PlacementAPIReconciler) Reconcile(ctx context.Context, req ctrl.Request // Only expose the service is the deployment succeeded if !instance.Status.Conditions.IsTrue(condition.DeploymentReadyCondition) { - Log.Info("Waiting for the Deployment to become Ready before exposing the sevice in Keystone") + Log.Info("Waiting for the Deployment to become Ready before exposing the service in Keystone") return ctrl.Result{}, nil } err = r.ensureKeystoneServiceUser(ctx, h, instance) @@ -1429,7 +1429,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps( for _, endpt := range []service.Endpoint{service.EndpointInternal, service.EndpointPublic} { endptConfig := map[string]any{} endptConfig["ServerName"] = fmt.Sprintf("placement-%s.%s.svc", endpt.String(), instance.Namespace) - endptConfig["TLS"] = false // default TLS to false, and set it bellow to true if enabled + endptConfig["TLS"] = false // default TLS to false, and set it below to true if enabled if instance.Spec.TLS.API.Enabled(endpt) { endptConfig["TLS"] = true endptConfig["SSLCertificateFile"] = fmt.Sprintf("/etc/pki/tls/certs/%s.crt", endpt.String()) diff --git a/test/functional/placement/api_controller_test.go b/test/functional/placement/api_controller_test.go index 5af303fb8..fd2da4302 100644 --- a/test/functional/placement/api_controller_test.go +++ b/test/functional/placement/api_controller_test.go @@ -39,7 +39,7 @@ var _ = Describe("PlacementAPI controller", func() { BeforeEach(func() { // lib-common uses OPERATOR_TEMPLATES env var to locate the "templates" - // directory of the operator. We need to set them othervise lib-common + // directory of the operator. We need to set them otherwise lib-common // will fail to generate the ConfigMap as it does not find common.sh err := os.Setenv("OPERATOR_TEMPLATES", "../../../templates") Expect(err).NotTo(HaveOccurred()) From 8e3e61b9c0a61320f2bd42755258820f3255b062 Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Tue, 7 Apr 2026 12:17:12 +0530 Subject: [PATCH 15/16] placement webhook manifests in config/webhook Updated Makefile to include placement webhook paths in controller-gen --- Makefile | 2 +- config/webhook/manifests.yaml | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 91fee986f..426315a9d 100644 --- a/Makefile +++ b/Makefile @@ -114,7 +114,7 @@ docker-buildx: ## Build and push docker image for the manager for cross-platform .PHONY: manifests manifests: gowork controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) crd webhook paths="./api/nova/..." paths="./api/placement/..." output:crd:artifacts:config=config/crd/bases && \ + $(CONTROLLER_GEN) crd webhook paths="./api/nova/..." paths="./api/placement/..." paths="./internal/webhook/nova/..." paths="./internal/webhook/placement/..." output:crd:artifacts:config=config/crd/bases output:webhook:artifacts:config=config/webhook && \ $(CONTROLLER_GEN) rbac:roleName=manager-role paths="./..." output:dir=config/rbac && \ rm -f api/bases/* && cp -a config/crd/bases api/ diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index ebdcbaaaf..22f6d429c 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -164,6 +164,26 @@ webhooks: resources: - novaschedulers sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-placement-openstack-org-v1beta1-placementapi + failurePolicy: Fail + name: mplacementapi-v1beta1.kb.io + rules: + - apiGroups: + - placement.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - placementapis + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration @@ -330,3 +350,23 @@ webhooks: resources: - novaschedulers sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-placement-openstack-org-v1beta1-placementapi + failurePolicy: Fail + name: vplacementapi-v1beta1.kb.io + rules: + - apiGroups: + - placement.openstack.org + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - placementapis + sideEffects: None From 1c02288ce6eef2441572cdbaf07081ed75b178fb Mon Sep 17 00:00:00 2001 From: Amit Uniyal Date: Tue, 7 Apr 2026 15:12:14 +0530 Subject: [PATCH 16/16] Updates placement template paths for multi-group structure Adds getTemplateInstanceType in placement/common.go --- .../controller/placement/api_controller.go | 6 ++-- internal/controller/placement/common.go | 35 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 internal/controller/placement/common.go diff --git a/internal/controller/placement/api_controller.go b/internal/controller/placement/api_controller.go index f571c21f2..56ca852b4 100644 --- a/internal/controller/placement/api_controller.go +++ b/internal/controller/placement/api_controller.go @@ -1441,7 +1441,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps( templateParameters["TimeOut"] = instance.Spec.APITimeout extraTemplates := map[string]string{ - "placement.conf": "placementapi/config/placement.conf", + "placement.conf": "placement/api/config/placement.conf", } cms := []util.Template{ @@ -1450,7 +1450,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps( Name: fmt.Sprintf("%s-scripts", instance.Name), Namespace: instance.Namespace, Type: util.TemplateTypeScripts, - InstanceType: instance.Kind, + InstanceType: getTemplateInstanceType(instance), Labels: cmLabels, }, // ConfigMap @@ -1458,7 +1458,7 @@ func (r *PlacementAPIReconciler) generateServiceConfigMaps( Name: fmt.Sprintf("%s-config-data", instance.Name), Namespace: instance.Namespace, Type: util.TemplateTypeConfig, - InstanceType: instance.Kind, + InstanceType: getTemplateInstanceType(instance), CustomData: customData, ConfigOptions: templateParameters, Labels: cmLabels, diff --git a/internal/controller/placement/common.go b/internal/controller/placement/common.go new file mode 100644 index 000000000..3e919439e --- /dev/null +++ b/internal/controller/placement/common.go @@ -0,0 +1,35 @@ +/* +Copyright 2022. + +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 controller contains the Kubernetes controllers for managing Placement components +package controller + +import "sigs.k8s.io/controller-runtime/pkg/client" + +// getTemplateInstanceType converts the CR Kind to the template directory path. +// Templates are in templates/placement// +// e.g. PlacementAPI -> placement/api +func getTemplateInstanceType(instance client.Object) string { + kind := instance.GetObjectKind().GroupVersionKind().Kind + + // Kind to template dir mapping + switch kind { + case "PlacementAPI": + return "placement/api" + default: + return "" + } +}