├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release.yaml └── workflows │ ├── build-dev.yaml │ ├── codeql-analysis.yml │ ├── release.yaml │ ├── synopsys-schedule.yaml │ ├── synopsys.yaml │ └── trivy-scan.yaml ├── .gitignore ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── PROJECT ├── README.md ├── api ├── v1alpha1 │ ├── database_types.go │ ├── database_webhook.go │ ├── groupversion_info.go │ ├── ndbserver_types.go │ ├── webhook_helpers.go │ ├── webhook_suite_test.go │ └── zz_generated.deepcopy.go └── webhooks_constants.go ├── automation ├── clientset │ └── v1alpha1 │ │ ├── database.go │ │ ├── ndb_server.go │ │ └── v1alpha1.go ├── constants.go ├── tests │ ├── cloning │ │ ├── mongo-si_test │ │ │ ├── config │ │ │ │ ├── database.yaml │ │ │ │ ├── db-secret.yaml │ │ │ │ ├── ndb-secret.yaml │ │ │ │ ├── ndb.yaml │ │ │ │ └── pod.yaml │ │ │ └── mongo_si_test.go │ │ ├── mssql-si_test │ │ │ ├── config │ │ │ │ ├── database.yaml │ │ │ │ ├── db-secret.yaml │ │ │ │ ├── ndb-secret.yaml │ │ │ │ ├── ndb.yaml │ │ │ │ └── pod.yaml │ │ │ └── mssql_si_test.go │ │ ├── mysql-si_test │ │ │ ├── config │ │ │ │ ├── database.yaml │ │ │ │ ├── db-secret.yaml │ │ │ │ ├── ndb-secret.yaml │ │ │ │ ├── ndb.yaml │ │ │ │ └── pod.yaml │ │ │ └── mysql_si_test.go │ │ └── pg-si_test │ │ │ ├── config │ │ │ ├── database.yaml │ │ │ ├── db-secret.yaml │ │ │ ├── ndb-secret.yaml │ │ │ ├── ndb.yaml │ │ │ └── pod.yaml │ │ │ └── pg_si_test.go │ └── provisioning │ │ ├── mongo-si_test │ │ ├── config │ │ │ ├── database.yaml │ │ │ ├── db-secret.yaml │ │ │ ├── ndb-secret.yaml │ │ │ ├── ndb.yaml │ │ │ └── pod.yaml │ │ └── mongo-si_test.go │ │ ├── mssql-si_test │ │ ├── config │ │ │ ├── database.yaml │ │ │ ├── db-secret.yaml │ │ │ ├── ndb-secret.yaml │ │ │ ├── ndb.yaml │ │ │ └── pod.yaml │ │ └── mssql-si_test.go │ │ ├── mysql-si_test │ │ ├── config │ │ │ ├── database.yaml │ │ │ ├── db-secret.yaml │ │ │ ├── ndb-secret.yaml │ │ │ ├── ndb.yaml │ │ │ └── pod.yaml │ │ └── mysql-si_test.go │ │ └── pg-si_test │ │ ├── config │ │ ├── database.yaml │ │ ├── db-secret.yaml │ │ ├── ndb-secret.yaml │ │ ├── ndb.yaml │ │ └── pod.yaml │ │ └── pg-si_test.go └── util │ ├── cloning_helpers.go │ ├── cloning_test_suite_manager.go │ ├── interfaces.go │ ├── provisioning_helpers.go │ ├── provisioning_test_suite_manager.go │ ├── setup.go │ └── test_suite_manager_common.go ├── common ├── constants.go └── util │ ├── additionalArguments.go │ ├── create_id_map.go │ ├── create_id_map_test.go │ ├── deep_equal_with_exception.go │ ├── deep_equal_with_exception_test.go │ ├── filter_slice.go │ ├── filter_slice_test.go │ ├── print.go │ ├── print_test.go │ ├── secret.go │ ├── secret_test.go │ └── webhook_utils.go ├── config ├── certmanager │ ├── certificate.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── crd │ ├── bases │ │ ├── ndb.nutanix.com_databases.yaml │ │ └── ndb.nutanix.com_ndbservers.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_databases.yaml │ │ ├── cainjection_in_ndbservers.yaml │ │ ├── webhook_in_databases.yaml │ │ └── webhook_in_ndbservers.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ ├── manager_config_patch.yaml │ ├── manager_webhook_patch.yaml │ └── webhookcainjection_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── database_editor_role.yaml │ ├── database_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── ndbserver_editor_role.yaml │ ├── ndbserver_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── scorecard │ ├── bases │ │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ │ ├── basic.config.yaml │ │ └── olm.config.yaml └── webhook │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ ├── manifests.yaml │ └── service.yaml ├── controller_adapters ├── database.go ├── database_test.go ├── profiles.go └── profiles_test.go ├── controllers ├── common.go ├── database_controller.go ├── database_reconciler_helpers.go ├── instance_manager.go ├── ndbserver_controller.go ├── ndbserver_controller_helpers.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── main.go ├── ndb_api ├── auth.go ├── auth_response_types.go ├── auth_test.go ├── clone.go ├── clone_helpers.go ├── clone_helpers_test.go ├── clone_request_types.go ├── clone_test.go ├── common_helpers.go ├── common_helpers_test.go ├── common_response_types.go ├── common_types.go ├── db.go ├── db_helpers.go ├── db_helpers_test.go ├── db_request_types.go ├── db_response_types.go ├── db_test.go ├── dbserver.go ├── dbserver_helpers.go ├── dbserver_request_types.go ├── dbserver_test.go ├── interface_mock_test.go ├── interfaces.go ├── mock_responses_test.go ├── operation.go ├── operation_helpers.go ├── operation_helpers_test.go ├── operation_response_types.go ├── operation_test.go ├── profile.go ├── profile_helpers.go ├── profile_helpers_test.go ├── profile_response_types.go ├── profile_test.go ├── sla.go ├── sla_helpers.go ├── sla_helpers_test.go ├── sla_response_types.go ├── sla_test.go ├── snapshot_request_types.go ├── snapshot_response_types.go ├── time_machine.go ├── time_machine_helpers.go ├── time_machine_helpers_test.go ├── time_machine_response_types.go ├── time_machine_test.go └── utility_test.go └── ndb_client └── ndb_client.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report for error, failure, or unexpected behavior 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Nutanix Information 11 | 18 | 19 | 20 | ### Databsae Version 21 | 22 | 23 | ### Debug Output 24 | 27 | 28 | ### Expected Behavior 29 | 30 | 31 | ### Actual Behavior 32 | 33 | 34 | 35 | ### Important Factors 36 | 43 | * #0000 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | **What this PR does / why we need it**: 10 | 11 | **Which issue(s) this PR fixes** *(optional, in `fixes #(, fixes #, ...)` format, will close the issue(s) when PR gets merged)*: 12 | Fixes # 13 | 14 | **How Has This Been Tested?**: 15 | 16 | _Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and test output_ 17 | 18 | 19 | **Special notes for your reviewer**: 20 | 21 | _Please confirm that if this PR changes any image versions, then that's the sole change this PR makes._ 22 | 23 | **Release note**: 24 | 28 | ```release-note 29 | 30 | ``` -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | # Enable version updates for Go modules 9 | - package-ecosystem: "gomod" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | 14 | # Enable version updates for GitHub Actions 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | -------------------------------------------------------------------------------- /.github/release.yaml: -------------------------------------------------------------------------------- 1 | # .github/release.yml 2 | 3 | changelog: 4 | exclude: 5 | labels: 6 | - ignore-for-release 7 | categories: 8 | - title: Breaking Changes 🛠 9 | labels: 10 | - Semver-Major 11 | - breaking-change 12 | - title: Exciting New Features 🎉 13 | labels: 14 | - Semver-Minor 15 | - enhancement 16 | - title: Bug Fixes 🐛 17 | labels: 18 | - bug 19 | - title: Documentation 📖 20 | labels: 21 | - documentation 22 | - title: Other Changes 23 | labels: 24 | - "*" 25 | -------------------------------------------------------------------------------- /.github/workflows/build-dev.yaml: -------------------------------------------------------------------------------- 1 | name: Test Build 2 | 3 | on: 4 | push: 5 | 6 | pull_request: 7 | 8 | jobs: 9 | build-binary: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 13 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 14 | 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version: "1.21.7" 22 | 23 | - name: Test build 24 | run: make test build 25 | 26 | - name: Run Trivy vulnerability scanner 27 | uses: aquasecurity/trivy-action@0.16.0 28 | with: 29 | scan-type: "fs" 30 | ignore-unfixed: true 31 | format: "table" 32 | exit-code: "1" 33 | vuln-type: "os,library" 34 | severity: "CRITICAL,HIGH" 35 | build-container: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 39 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 40 | 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | 44 | - name: Build container image 45 | env: 46 | IMAGE_TAG_BASE: ${{ github.repository }}_build_container_test 47 | run: make docker-build 48 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | name: "Code Scanning - Action" 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | schedule: 9 | - cron: "30 1 * * 0" 10 | 11 | jobs: 12 | CodeQL-Build: 13 | runs-on: ubuntu-latest 14 | 15 | permissions: 16 | # required for all workflows 17 | security-events: write 18 | 19 | # only required for workflows in private repositories 20 | actions: read 21 | contents: read 22 | 23 | steps: 24 | - name: Checkout repository 25 | uses: actions/checkout@v4 26 | 27 | # Initializes the CodeQL tools for scanning. 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v2 30 | # Override language selection by uncommenting this and choosing your languages 31 | # with: 32 | # languages: go, javascript, csharp, python, cpp, java 33 | 34 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 35 | # If this step fails, then you should remove it and run the build manually (see below). 36 | - name: Autobuild 37 | uses: github/codeql-action/autobuild@v2 38 | 39 | # ℹ️ Command-line programs to run using the OS shell. 40 | # 📚 https://git.io/JvXDl 41 | 42 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 43 | # three lines and modify them (or add more) to build your code if your 44 | # project uses a compiled language 45 | 46 | #- run: | 47 | # make bootstrap 48 | # make release 49 | 50 | - name: Perform CodeQL Analysis 51 | uses: github/codeql-action/analyze@v2 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Generate release artefact 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build_release: 10 | name: Build Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.21.7" 20 | 21 | - name: Install tools 22 | uses: redhat-actions/openshift-tools-installer@v1 23 | with: 24 | source: "github" 25 | kustomize: "latest" 26 | operator-sdk: "latest" 27 | ko: "latest" 28 | 29 | - name: Login to GHCR 30 | uses: docker/login-action@v3 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Docker meta 37 | id: meta 38 | uses: docker/metadata-action@v5 39 | with: 40 | images: ndb-operator 41 | sep-tags: "," 42 | sep-labels: "," 43 | tags: | 44 | type=semver,pattern=v{{version}} 45 | type=semver,pattern=v{{major}}.{{minor}} 46 | type=semver,pattern=v{{major}} 47 | type=sha 48 | 49 | - name: Test build 50 | run: make test 51 | 52 | - name: Build container 53 | env: 54 | KO_DOCKER_REPO: ghcr.io/${{ github.repository }}/controller 55 | TAGS: ${{ steps.meta.outputs.tags }} 56 | LABELS: ${{ steps.meta.outputs.labels }} 57 | PLATFORMS: linux/amd64,linux/arm64,linux/arm 58 | run: | 59 | PTAGS=`echo $TAGS | sed 's/ndb-operator://g'` 60 | export SOURCE_DATE_EPOCH=$(date +%s) 61 | ko build --bare --image-label "$LABELS" -t "$PTAGS" --platform=$PLATFORMS . 62 | -------------------------------------------------------------------------------- /.github/workflows/synopsys-schedule.yaml: -------------------------------------------------------------------------------- 1 | name: Black Duck Intelligent Policy Check 2 | on: 3 | schedule: 4 | - cron: "0 0 * * *" 5 | 6 | jobs: 7 | security: 8 | if: github.repository == 'nutanix-cloud-native/ndb-operator' 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version: "1.21.7" 18 | 19 | - name: Build Project 20 | run: make build 21 | 22 | - name: Run Synopsys Detect 23 | uses: synopsys-sig/detect-action@v0.3.4 24 | env: 25 | DETECT_PROJECT_USER_GROUPS: "CloudNative" 26 | with: 27 | scan-mode: INTELLIGENT 28 | github-token: ${{ secrets.GITHUB_TOKEN }} 29 | detect-version: 8.10.0 30 | blackduck-url: ${{ secrets.BLACKDUCK_URL }} 31 | blackduck-api-token: ${{ secrets.BLACKDUCK_API_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/synopsys.yaml: -------------------------------------------------------------------------------- 1 | name: Black Duck Policy Check 2 | on: 3 | pull_request: 4 | branches: 5 | - main 6 | push: 7 | 8 | jobs: 9 | security: 10 | if: github.repository == 'nutanix-cloud-native/ndb-operator' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Setup Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: "1.21.7" 20 | 21 | - name: Build Project 22 | run: make build 23 | 24 | - name: Run Synopsys Detect 25 | uses: synopsys-sig/detect-action@v0.3.4 26 | with: 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | detect-version: 8.10.0 29 | blackduck-url: ${{ secrets.BLACKDUCK_URL }} 30 | blackduck-api-token: ${{ secrets.BLACKDUCK_API_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/trivy-scan.yaml: -------------------------------------------------------------------------------- 1 | name: Trivy Scan 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "17 17 * * *" 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | permissions: 14 | contents: read 15 | security-events: write 16 | name: Scan 17 | runs-on: "ubuntu-latest" 18 | steps: 19 | - name: Checkout Code 20 | uses: actions/checkout@v4 21 | 22 | - name: Get repository name 23 | run: echo "REPOSITORY_NAME=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV 24 | 25 | - name: Setup Go 26 | uses: actions/setup-go@v5 27 | with: 28 | go-version: "1.21.7" 29 | 30 | - name: Build Project 31 | run: make build 32 | 33 | - name: Run Trivy vulnerability scanner 34 | uses: aquasecurity/trivy-action@0.16.0 35 | with: 36 | scan-type: "fs" 37 | format: "sarif" 38 | output: "trivy-results.sarif" 39 | severity: "CRITICAL,HIGH" 40 | 41 | - name: Upload Trivy scan results to GitHub Security tab 42 | uses: github/codeql-action/upload-sarif@v2 43 | with: 44 | sarif_file: "trivy-results.sarif" 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | 11 | # Test binary, build with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Kubernetes Generated files - skip generated files, except for vendored files 18 | 19 | !vendor/**/zz_generated.* 20 | 21 | # editor and IDE paraphernalia 22 | .idea 23 | *.swp 24 | *.swo 25 | *~ 26 | .vscode 27 | 28 | 29 | sandbox 30 | config/samples/ndb.yaml 31 | .DS_Store 32 | 33 | test/__debug_bin 34 | automation/sandbox 35 | automation/tests/**/*.log 36 | *.env 37 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome. Before contributing please search the [issue tracker](../../issues). Your issue may have already been discussed or fixed in `main`. To contribute, [fork](https://help.github.com/articles/fork-a-repo/) this repository, commit your changes, and [send a Pull Request](https://help.github.com/articles/using-pull-requests/). 4 | 5 | ## Feature requests 6 | 7 | Feature requests should be submitted in the [issue tracker](../../issues), with a description of the expected behavior & use case, where they'll remain closed until sufficient interest, [e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/), has been [shown by the community](../../issues?q=label%3A%22votes+needed%22+sort%3Areactions-%2B1-desc). 8 | 9 | Before submitting an issue, please search for similar ones in the [closed issues](../../issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement). 10 | 11 | ## Pull requests 12 | 13 | ### Approval and release process 14 | 15 | Pull requests approvals go through the following steps: 16 | 17 | 1. A GitHub action may be triggered to lint and test. 18 | 2. A maintainer reviews the changes. Any change requires at least one review. 19 | 3. The pull request can be merged when at least one maintainer approves it. 20 | 21 | ## Contributor License Agreement 22 | 23 | Before you submit your pull request, you'll need to sign the [Nutanix Contributor License Agreement (CLA)](https://www.nutanix.dev/cla/). The CLA must be agreed to by all contributors who are not Nutanix Full-time Employees (FTE) or interns prior to the contribution being merged into the project codebase. The CLA is substantially similar to the Apache Contributor License Agreement, which is the industry standard CLA. 24 | 25 | For more information about CLAs, please check out Alex Russell's excellent post, 26 | ["Why Do I Need to Sign This?"](https://infrequently.org/2008/06/why-do-i-need-to-sign-this/). -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.21.7 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY api/ api/ 14 | COPY common/ common/ 15 | COPY controllers/ controllers/ 16 | COPY ndb_api/ ndb_api/ 17 | COPY ndb_client/ ndb_client/ 18 | COPY controller_adapters/ controller_adapters/ 19 | COPY main.go main.go 20 | 21 | 22 | # Build 23 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go 24 | 25 | # Use distroless as minimal base image to package the manager binary 26 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 27 | FROM gcr.io/distroless/static:nonroot 28 | WORKDIR / 29 | COPY --from=builder /workspace/manager . 30 | USER 65532:65532 31 | 32 | ENTRYPOINT ["/manager"] 33 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022-2023 Nutanix, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | # Code generated by tool. DO NOT EDIT. 2 | # This file is used to track the info used to scaffold your project 3 | # and allow the plugins properly work. 4 | # More info: https://book.kubebuilder.io/reference/project-config.html 5 | domain: nutanix.com 6 | layout: 7 | - go.kubebuilder.io/v3 8 | plugins: 9 | manifests.sdk.operatorframework.io/v2: {} 10 | scorecard.sdk.operatorframework.io/v2: {} 11 | projectName: ndb-operator 12 | repo: github.com/nutanix-cloud-native/ndb-operator 13 | resources: 14 | - api: 15 | crdVersion: v1 16 | namespaced: true 17 | controller: true 18 | domain: nutanix.com 19 | group: ndb 20 | kind: Database 21 | path: github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1 22 | version: v1alpha1 23 | webhooks: 24 | defaulting: true 25 | validation: true 26 | webhookVersion: v1 27 | - api: 28 | crdVersion: v1 29 | namespaced: true 30 | controller: true 31 | domain: nutanix.com 32 | group: ndb 33 | kind: NDBServer 34 | path: github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1 35 | version: v1alpha1 36 | version: "3" 37 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | GENERATED by operator-sdk 19 | */ 20 | 21 | // Package v1alpha1 contains API Schema definitions for the ndb v1alpha1 API group 22 | // +kubebuilder:object:generate=true 23 | // +groupName=ndb.nutanix.com 24 | package v1alpha1 25 | 26 | import ( 27 | "k8s.io/apimachinery/pkg/runtime/schema" 28 | "sigs.k8s.io/controller-runtime/pkg/scheme" 29 | ) 30 | 31 | var ( 32 | // GroupVersion is group version used to register these objects 33 | GroupVersion = schema.GroupVersion{Group: "ndb.nutanix.com", Version: "v1alpha1"} 34 | 35 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 36 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 37 | 38 | // AddToScheme adds the types in this group-version to the given scheme. 39 | AddToScheme = SchemeBuilder.AddToScheme 40 | ) 41 | -------------------------------------------------------------------------------- /api/v1alpha1/ndbserver_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1alpha1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // NDBServerSpec defines the desired state of NDBServer 24 | type NDBServerSpec struct { 25 | // +kubebuilder:validation:Required 26 | Server string `json:"server"` 27 | // +kubebuilder:validation:Required 28 | CredentialSecret string `json:"credentialSecret"` 29 | // +kubebuilder:default:=false 30 | // +optional 31 | // Skip server's certificate and hostname verification 32 | SkipCertificateVerification bool `json:"skipCertificateVerification"` 33 | } 34 | 35 | // NDBServerStatus defines the observed state of NDBServer 36 | type NDBServerStatus struct { 37 | Status string `json:"status"` 38 | LastUpdated string `json:"lastUpdated"` 39 | Databases map[string]NDBServerDatabaseInfo `json:"databases"` 40 | ReconcileCounter ReconcileCounter `json:"reconcileCounter"` 41 | } 42 | 43 | type ReconcileCounter struct { 44 | Database int `json:"database"` 45 | } 46 | 47 | // +kubebuilder:object:root=true 48 | // +kubebuilder:subresource:status 49 | // +kubebuilder:resource:shortName={"ndb","ndbs"} 50 | // +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` 51 | // +kubebuilder:printcolumn:name="Updated At",type=string,JSONPath=`.status.lastUpdated` 52 | 53 | // NDBServer is the Schema for the ndbservers API 54 | type NDBServer struct { 55 | metav1.TypeMeta `json:",inline"` 56 | metav1.ObjectMeta `json:"metadata,omitempty"` 57 | 58 | Spec NDBServerSpec `json:"spec,omitempty"` 59 | Status NDBServerStatus `json:"status,omitempty"` 60 | } 61 | 62 | //+kubebuilder:object:root=true 63 | 64 | // NDBServerList contains a list of NDBServer 65 | type NDBServerList struct { 66 | metav1.TypeMeta `json:",inline"` 67 | metav1.ListMeta `json:"metadata,omitempty"` 68 | Items []NDBServer `json:"items"` 69 | } 70 | 71 | func init() { 72 | SchemeBuilder.Register(&NDBServer{}, &NDBServerList{}) 73 | } 74 | 75 | // Database related info to be stored in the status field of the NDB CR 76 | type NDBServerDatabaseInfo struct { 77 | Name string `json:"name"` 78 | Id string `json:"id"` 79 | Status string `json:"status"` 80 | DBServerId string `json:"dbServerId"` 81 | TimeMachineId string `json:"timeMachineId"` 82 | IPAddress string `json:"ipAddress"` 83 | Type string `json:"type"` 84 | } 85 | -------------------------------------------------------------------------------- /api/webhooks_constants.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | var DefaultDatabaseNames = []string{"database_one", "database_two", "database_three"} 4 | 5 | var AllowedDatabaseTypes = map[string]bool{ 6 | "mysql": true, 7 | "postgres": true, 8 | "mongodb": true, 9 | "mssql": true, 10 | } 11 | 12 | var ClosedSourceDatabaseTypes = map[string]bool{ 13 | "mssql": true, 14 | } 15 | 16 | var AllowedLogCatchupFrequencyInMinutes = map[int]bool{ 17 | 15: true, 18 | 30: true, 19 | 45: true, 20 | 60: true, 21 | 90: true, 22 | 120: true, 23 | } 24 | 25 | var AllowedWeeklySnapshotDays = map[string]bool{ 26 | "MONDAY": true, 27 | "TUESDAY": true, 28 | "WEDNESDAY": true, 29 | "THURSDAY": true, 30 | "FRIDAY": true, 31 | "SATURDAY": true, 32 | "SUNDAY": true, 33 | } 34 | 35 | var AllowedQuarterlySnapshotMonths = map[string]bool{ 36 | "Jan": true, 37 | "Feb": true, 38 | "Mar": true, 39 | } 40 | -------------------------------------------------------------------------------- /automation/clientset/v1alpha1/database.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/watch" 9 | "k8s.io/client-go/kubernetes/scheme" 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // Common Functionality used to interact with Database CR using Kubernetes Client 14 | type DatabaseInterface interface { 15 | List(opts metav1.ListOptions) (*ndbv1alpha1.DatabaseList, error) 16 | Get(name string, options metav1.GetOptions) (*ndbv1alpha1.Database, error) 17 | Create(*ndbv1alpha1.Database) (*ndbv1alpha1.Database, error) 18 | Update(*ndbv1alpha1.Database) (*ndbv1alpha1.Database, error) 19 | Delete(name string, options *metav1.DeleteOptions) error 20 | Watch(opts metav1.ListOptions) (watch.Interface, error) 21 | } 22 | 23 | type DatabaseClient struct { 24 | restClient rest.Interface 25 | namespace string 26 | } 27 | 28 | func (c *DatabaseClient) List(opts metav1.ListOptions) (*ndbv1alpha1.DatabaseList, error) { 29 | result := ndbv1alpha1.DatabaseList{} 30 | err := c.restClient. 31 | Get(). 32 | Namespace(c.namespace). 33 | Resource("Databases"). 34 | VersionedParams(&opts, scheme.ParameterCodec). 35 | Do(context.TODO()). 36 | Into(&result) 37 | 38 | return &result, err 39 | } 40 | 41 | func (c *DatabaseClient) Get(name string, opts metav1.GetOptions) (*ndbv1alpha1.Database, error) { 42 | result := ndbv1alpha1.Database{} 43 | err := c.restClient. 44 | Get(). 45 | Namespace(c.namespace). 46 | Resource("Databases"). 47 | Name(name). 48 | VersionedParams(&opts, scheme.ParameterCodec). 49 | Do(context.TODO()). 50 | Into(&result) 51 | 52 | return &result, err 53 | } 54 | 55 | func (c *DatabaseClient) Create(database *ndbv1alpha1.Database) (*ndbv1alpha1.Database, error) { 56 | result := ndbv1alpha1.Database{} 57 | err := c.restClient. 58 | Post(). 59 | Namespace(c.namespace). 60 | Resource("Databases"). 61 | Body(database). 62 | Do(context.TODO()). 63 | Into(&result) 64 | 65 | return &result, err 66 | } 67 | 68 | func (c *DatabaseClient) Update(database *ndbv1alpha1.Database) (*ndbv1alpha1.Database, error) { 69 | result := ndbv1alpha1.Database{} 70 | err := c.restClient. 71 | Put(). 72 | Namespace(c.namespace). 73 | Resource("Databases"). 74 | Name(database.Name). 75 | Body(database). 76 | Do(context.TODO()). 77 | Into(&result) 78 | 79 | return &result, err 80 | } 81 | 82 | func (c *DatabaseClient) Delete(name string, opts *metav1.DeleteOptions) error { 83 | return c.restClient. 84 | Delete(). 85 | Namespace(c.namespace). 86 | Resource("Databases"). 87 | Name(name). 88 | VersionedParams(opts, scheme.ParameterCodec). 89 | Do(context.TODO()). 90 | Error() 91 | } 92 | 93 | func (c *DatabaseClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { 94 | opts.Watch = true 95 | return c.restClient. 96 | Get(). 97 | Namespace(c.namespace). 98 | Resource("Databases"). 99 | VersionedParams(&opts, scheme.ParameterCodec). 100 | Watch(context.TODO()) 101 | } 102 | -------------------------------------------------------------------------------- /automation/clientset/v1alpha1/ndb_server.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | "context" 5 | 6 | ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/apimachinery/pkg/watch" 9 | "k8s.io/client-go/kubernetes/scheme" 10 | "k8s.io/client-go/rest" 11 | ) 12 | 13 | // Common Functionality used to interact with NDBServer CR using Kubernetes Client 14 | type NDBServerInterface interface { 15 | List(opts metav1.ListOptions) (*ndbv1alpha1.NDBServerList, error) 16 | Get(name string, options metav1.GetOptions) (*ndbv1alpha1.NDBServer, error) 17 | Create(*ndbv1alpha1.NDBServer) (*ndbv1alpha1.NDBServer, error) 18 | Update(*ndbv1alpha1.NDBServer) (*ndbv1alpha1.NDBServer, error) 19 | Delete(name string, options *metav1.DeleteOptions) error 20 | Watch(opts metav1.ListOptions) (watch.Interface, error) 21 | } 22 | 23 | type NDBServerClient struct { 24 | restClient rest.Interface 25 | namespace string 26 | } 27 | 28 | func (c *NDBServerClient) List(opts metav1.ListOptions) (*ndbv1alpha1.NDBServerList, error) { 29 | result := ndbv1alpha1.NDBServerList{} 30 | err := c.restClient. 31 | Get(). 32 | Namespace(c.namespace). 33 | Resource("NDBServers"). 34 | VersionedParams(&opts, scheme.ParameterCodec). 35 | Do(context.TODO()). 36 | Into(&result) 37 | 38 | return &result, err 39 | } 40 | 41 | func (c *NDBServerClient) Get(name string, opts metav1.GetOptions) (*ndbv1alpha1.NDBServer, error) { 42 | result := ndbv1alpha1.NDBServer{} 43 | err := c.restClient. 44 | Get(). 45 | Namespace(c.namespace). 46 | Resource("NDBServers"). 47 | Name(name). 48 | VersionedParams(&opts, scheme.ParameterCodec). 49 | Do(context.TODO()). 50 | Into(&result) 51 | 52 | return &result, err 53 | } 54 | 55 | func (c *NDBServerClient) Create(NDBServer *ndbv1alpha1.NDBServer) (*ndbv1alpha1.NDBServer, error) { 56 | result := ndbv1alpha1.NDBServer{} 57 | err := c.restClient. 58 | Post(). 59 | Namespace(c.namespace). 60 | Resource("NDBServers"). 61 | Body(NDBServer). 62 | Do(context.TODO()). 63 | Into(&result) 64 | 65 | return &result, err 66 | } 67 | 68 | func (c *NDBServerClient) Update(NDBServer *ndbv1alpha1.NDBServer) (*ndbv1alpha1.NDBServer, error) { 69 | result := ndbv1alpha1.NDBServer{} 70 | err := c.restClient. 71 | Put(). 72 | Namespace(c.namespace). 73 | Resource("NDBServers"). 74 | Name(NDBServer.Name). 75 | Body(NDBServer). 76 | Do(context.TODO()). 77 | Into(&result) 78 | 79 | return &result, err 80 | } 81 | 82 | func (c *NDBServerClient) Delete(name string, opts *metav1.DeleteOptions) error { 83 | return c.restClient. 84 | Delete(). 85 | Namespace(c.namespace). 86 | Resource("NDBServers"). 87 | Name(name). 88 | VersionedParams(opts, scheme.ParameterCodec). 89 | Do(context.TODO()). 90 | Error() 91 | } 92 | 93 | func (c *NDBServerClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { 94 | opts.Watch = true 95 | return c.restClient. 96 | Get(). 97 | Namespace(c.namespace). 98 | Resource("NDBServers"). 99 | VersionedParams(&opts, scheme.ParameterCodec). 100 | Watch(context.TODO()) 101 | } 102 | -------------------------------------------------------------------------------- /automation/clientset/v1alpha1/v1alpha1.go: -------------------------------------------------------------------------------- 1 | package v1alpha1 2 | 3 | import ( 4 | ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1" 5 | "k8s.io/apimachinery/pkg/runtime" 6 | "k8s.io/apimachinery/pkg/runtime/schema" 7 | "k8s.io/apimachinery/pkg/runtime/serializer" 8 | "k8s.io/client-go/rest" 9 | ) 10 | 11 | type V1alpha1Client struct { 12 | restClient rest.Interface 13 | } 14 | 15 | func NewForConfig(c *rest.Config) (*V1alpha1Client, error) { 16 | config := *c 17 | config.ContentConfig.GroupVersion = &schema.GroupVersion{Group: ndbv1alpha1.GroupVersion.Group, Version: ndbv1alpha1.GroupVersion.Version} 18 | config.APIPath = "/apis" 19 | scheme := runtime.NewScheme() 20 | codecs := serializer.NewCodecFactory(scheme) 21 | negotiatedSerializer := codecs.WithoutConversion() 22 | config.NegotiatedSerializer = negotiatedSerializer 23 | config.UserAgent = rest.DefaultKubernetesUserAgent() 24 | 25 | client, err := rest.RESTClientFor(&config) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | return &V1alpha1Client{restClient: client}, nil 31 | } 32 | 33 | func (c *V1alpha1Client) Databases(namespace string) DatabaseInterface { 34 | if namespace == "" { 35 | namespace = "default" 36 | } 37 | return &DatabaseClient{ 38 | restClient: c.restClient, 39 | namespace: namespace, 40 | } 41 | } 42 | 43 | func (c *V1alpha1Client) NDBServers(namespace string) NDBServerInterface { 44 | if namespace == "" { 45 | namespace = "default" 46 | } 47 | return &NDBServerClient{ 48 | restClient: c.restClient, 49 | namespace: namespace, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /automation/constants.go: -------------------------------------------------------------------------------- 1 | package automation 2 | 3 | const ( 4 | NAMESPACE_DEFAULT = "default" 5 | 6 | // Resource paths 7 | NDBSERVER_PATH = "./config/ndb.yaml" 8 | DATABASE_PATH = "./config/database.yaml" 9 | DB_SECRET_PATH = "./config/db-secret.yaml" 10 | NDB_SECRET_PATH = "./config/ndb-secret.yaml" 11 | APP_POD_PATH = "./config/pod.yaml" 12 | APP_SVC_PATH = "./config/service.yaml" 13 | 14 | // Environment Variables 15 | KUBECONFIG_ENV = "KUBECONFIG" 16 | DB_SECRET_PASSWORD_ENV = "DB_SECRET_PASSWORD" 17 | NDB_SECRET_USERNAME_ENV = "NDB_SECRET_USERNAME" 18 | NDB_SECRET_PASSWORD_ENV = "NDB_SECRET_PASSWORD" 19 | NDB_SERVER_ENV = "NDB_SERVER" 20 | NX_CLUSTER_ID_ENV = "NX_CLUSTER_ID" 21 | 22 | MONGO_SI_CLONING_NAME_ENV = "MONGO_SI_CLONING_NAME" 23 | MSSQL_SI_CLONING_NAME_ENV = "MSSQL_SI_CLONING_NAME" 24 | MYSQL_SI_CLONING_NAME_ENV = "MYSQL_SI_CLONING_NAME" 25 | POSTGRES_SI_CLONING_NAME_ENV = "POSTGRES_SI_CLONING_NAME" 26 | 27 | // Log paths 28 | PROVISIONING_LOG_PATH = "../../logs/provisioning" 29 | CLONING_LOG_PATH = "../../logs/cloning" 30 | 31 | // Provisioning ports for app connectivity tests 32 | MONGO_SI_PROVISONING_LOCAL_PORT = "3000" 33 | MSSQL_SI_PROVISONING_LOCAL_PORT = "3001" 34 | MYSQL_SI_PROVISONING_LOCAL_PORT = "3002" 35 | POSTGRES_SI_PROVISONING_LOCAL_PORT = "3003" 36 | 37 | // Cloning ports for app connectivity tests 38 | MONGO_SI_CLONING_LOCAL_PORT = "3004" 39 | MSSQL_SI_CLONING_LOCAL_PORT = "3005" 40 | MYSQL_SI_CLONING_LOCAL_PORT = "3006" 41 | POSTGRES_SI_CLONING_LOCAL_PORT = "3007" 42 | 43 | // Clone source database names 44 | MONGO_SI_CLONING_NAME_DEFAULT = "operator-mongo" 45 | MSSQL_SI_CLONING_NAME_DEFAULT = "operator-mssql" 46 | MYSQL_SI_CLONING_NAME_DEFAULT = "operator-mysql" 47 | POSTGRES_SI_CLONING_NAME_DEFAULT = "operator-postgres" 48 | ) 49 | -------------------------------------------------------------------------------- /automation/tests/cloning/mongo-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: clone-mongo-si 5 | spec: 6 | ndbRef: clone-ndb-mongo-si 7 | isClone: true 8 | clone: 9 | name: clone-mongo-si 10 | type: "mongodb" 11 | description: Cloning mongoDB single instance testing 12 | clusterId: 13 | profiles: {} 14 | credentialSecret: clone-db-secret-mongo-si 15 | timezone: America/Los_Angeles 16 | sourceDatabaseId: 17 | snapshotId: 18 | additionalArguments: {} 19 | -------------------------------------------------------------------------------- /automation/tests/cloning/mongo-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-db-secret-mongo-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mongo-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-ndb-secret-pg-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/cloning/mongo-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: clone-ndb-mongo-si 5 | spec: 6 | credentialSecret: clone-ndb-secret-pg-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mongo-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: clone-app-mongo-si 5 | labels: 6 | app: clone-app-mongo-si 7 | spec: 8 | containers: 9 | - name: app-mongo-container 10 | image: mazins/ndb-operator-mongodb:latest 11 | env: 12 | - name: DBHOST 13 | value: clone-mongo-si-svc 14 | - name: DBPORT 15 | value: "80" 16 | - name: DATABASE 17 | value: database_one 18 | - name: USERNAME 19 | value: admin 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: clone-db-secret-mongo-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 31 | env: 32 | - name: DB_HOST 33 | value: clone-mongo-si-svc -------------------------------------------------------------------------------- /automation/tests/cloning/mssql-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: clone-mssql-si 5 | spec: 6 | ndbRef: clone-ndb-mssql-si 7 | isClone: true 8 | clone: 9 | name: clone-mssql-si 10 | type: mssql 11 | description: Cloning mssql single instance testing 12 | clusterId: 13 | profiles: 14 | software: 15 | name: MSSQL-MAZIN2 16 | credentialSecret: clone-db-secret-mssql-si 17 | timezone: UTC 18 | sourceDatabaseId: 19 | snapshotId: 20 | additionalArguments: 21 | "authentication_mode": "mixed" 22 | -------------------------------------------------------------------------------- /automation/tests/cloning/mssql-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-db-secret-mssql-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mssql-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-ndb-secret-mssql-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/cloning/mssql-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: clone-ndb-mssql-si 5 | spec: 6 | credentialSecret: clone-ndb-secret-mssql-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mssql-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: clone-app-mssql-si 5 | labels: 6 | app: clone-app-mssql-si 7 | spec: 8 | containers: 9 | - name: app-mssql-container 10 | image: mazins/ndb-operator-mssql:latest 11 | env: 12 | - name: DBHOST 13 | value: clone-mssql-si-svc 14 | - name: USERNAME 15 | value: sa 16 | - name: DATABASE 17 | value: database_one 18 | - name: DBPORT 19 | value: "80" 20 | - name: MSSQL_INSTANCE_NAME 21 | value: CDMINSTANCE 22 | - name: PASSWORD 23 | valueFrom: 24 | secretKeyRef: 25 | name: clone-db-secret-mssql-si 26 | key: password 27 | ports: 28 | - containerPort: 3000 29 | initContainers: 30 | - name: init-db 31 | image: busybox:1.28 32 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 33 | env: 34 | - name: DB_HOST 35 | value: clone-mssql-si-svc -------------------------------------------------------------------------------- /automation/tests/cloning/mysql-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: clone-mysql-si 5 | spec: 6 | ndbRef: clone-ndb-mysql-si 7 | isClone: true 8 | clone: 9 | name: clone-mysql-si 10 | type: mysql 11 | description: Cloning mysql single instance testing 12 | clusterId: 13 | profiles: {} 14 | credentialSecret: clone-db-secret-mysql-si 15 | timezone: UTC 16 | sourceDatabaseId: 17 | snapshotId: 18 | additionalArguments: {} 19 | -------------------------------------------------------------------------------- /automation/tests/cloning/mysql-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-db-secret-mysql-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mysql-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-ndb-secret-mysql-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/cloning/mysql-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: clone-ndb-mysql-si 5 | spec: 6 | credentialSecret: clone-ndb-secret-mysql-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/mysql-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: clone-app-mysql-si 5 | labels: 6 | app: clone-app-mysql-si 7 | spec: 8 | containers: 9 | - name: app-mysql-container 10 | image: mazins/ndb-operator-mysql:latest 11 | env: 12 | - name: DBHOST 13 | value: clone-mysql-si-svc 14 | - name: DATABASE 15 | value: database_one 16 | - name: DBPORT 17 | value: '3306' 18 | - name: USERNAME 19 | value: root 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: clone-db-secret-mysql-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 31 | env: 32 | - name: DB_HOST 33 | value: clone-mysql-si-svc -------------------------------------------------------------------------------- /automation/tests/cloning/pg-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: clone-pg-si 5 | spec: 6 | ndbRef: clone-ndb-pg-si 7 | isClone: true 8 | clone: 9 | name: clone-pg-si 10 | type: postgres 11 | description: Cloning pg single instance testing 12 | clusterId: 13 | profiles: {} 14 | credentialSecret: clone-db-secret-pg-si 15 | timezone: UTC 16 | sourceDatabaseId: 17 | snapshotId: 18 | additionalArguments: {} 19 | -------------------------------------------------------------------------------- /automation/tests/cloning/pg-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-db-secret-pg-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/pg-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: clone-ndb-secret-pg-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/cloning/pg-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: clone-ndb-pg-si 5 | spec: 6 | credentialSecret: clone-ndb-secret-pg-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/cloning/pg-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: clone-app-pg-si 5 | labels: 6 | app: clone-app-pg-si 7 | spec: 8 | containers: 9 | - name: best-app 10 | image: manavrajvanshinx/best-app:latest 11 | resources: 12 | limits: 13 | memory: 512Mi 14 | cpu: "1" 15 | env: 16 | - name: DBHOST 17 | value: clone-pg-si-svc 18 | - name: DBPORT 19 | value: '80' 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: clone-db-secret-pg-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup clone-pg-si-svc.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for database service; sleep 2; done"] 31 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mongo-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: db-mongo-si 5 | spec: 6 | ndbRef: ndb-mongo 7 | databaseInstance: 8 | name: db-mongo-si 9 | databaseNames: 10 | - database_one 11 | clusterId: 12 | credentialSecret: db-secret-mongo-si 13 | size: 10 14 | timezone: "UTC" 15 | type: mongodb 16 | profiles: {} 17 | timeMachine: 18 | name: db-mongo-si_TM 19 | description: "TM provisioned by operator" 20 | sla : "DEFAULT_OOB_GOLD_SLA" 21 | dailySnapshotTime: "12:34:56" 22 | snapshotsPerDay: 4 23 | logCatchUpFrequency: 90 24 | weeklySnapshotDay: "WEDNESDAY" 25 | monthlySnapshotDay: 24 26 | quarterlySnapshotMonth: "Jan" 27 | additionalArguments: {} 28 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mongo-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: db-secret-mongo-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mongo-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: ndb-secret-mongo-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mongo-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: ndb-mongo 5 | spec: 6 | credentialSecret: ndb-secret-mongo-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mongo-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app-mongo-si 5 | labels: 6 | app: app-mongo-si 7 | spec: 8 | containers: 9 | - name: app-mongo-container 10 | image: mazins/ndb-operator-mongodb:latest 11 | env: 12 | - name: DBHOST 13 | value: db-mongo-si-svc 14 | - name: DBPORT 15 | value: "80" 16 | - name: DATABASE 17 | value: database_one 18 | - name: USERNAME 19 | value: admin 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: db-secret-mongo-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 31 | env: 32 | - name: DB_HOST 33 | value: db-mongo-si-svc -------------------------------------------------------------------------------- /automation/tests/provisioning/mssql-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: db-mssql-si 5 | spec: 6 | ndbRef: ndb-mssql 7 | databaseInstance: 8 | name: db-mssql-si 9 | databaseNames: 10 | - database_one 11 | clusterId: 12 | credentialSecret: db-secret-mssql-si 13 | size: 10 14 | timezone: "UTC" 15 | type: mssql 16 | profiles: 17 | software: 18 | name: "MSSQL-MAZIN2" 19 | timeMachine: 20 | name: db-mssql-si_TM 21 | description: "TM provisioned by operator" 22 | sla : "DEFAULT_OOB_GOLD_SLA" 23 | dailySnapshotTime: "12:34:56" 24 | snapshotsPerDay: 4 25 | logCatchUpFrequency: 90 26 | weeklySnapshotDay: "WEDNESDAY" 27 | monthlySnapshotDay: 24 28 | quarterlySnapshotMonth: "Jan" 29 | additionalArguments: 30 | authentication_mode: "mixed" 31 | sql_user_password: "Nutanix.1" 32 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mssql-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: db-secret-mssql-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mssql-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: ndb-secret-mssql-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mssql-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: ndb-mssql 5 | spec: 6 | credentialSecret: ndb-secret-mssql-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mssql-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app-mssql-si 5 | labels: 6 | app: app-mssql-si 7 | spec: 8 | containers: 9 | - name: app-mssql-container 10 | image: mazins/ndb-operator-mssql:latest 11 | env: 12 | - name: DBHOST 13 | value: db-mssql-si-svc 14 | - name: USERNAME 15 | value: sa 16 | - name: DATABASE 17 | value: database_one 18 | - name: DBPORT 19 | value: "80" 20 | - name: MSSQL_INSTANCE_NAME 21 | value: CDMINSTANCE 22 | - name: PASSWORD 23 | valueFrom: 24 | secretKeyRef: 25 | name: db-secret-mssql-si 26 | key: password 27 | ports: 28 | - containerPort: 3000 29 | initContainers: 30 | - name: init-db 31 | image: busybox:1.28 32 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 33 | env: 34 | - name: DB_HOST 35 | value: db-mssql-si-svc -------------------------------------------------------------------------------- /automation/tests/provisioning/mysql-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: db-mysql-si 5 | spec: 6 | ndbRef: ndb-mysql 7 | databaseInstance: 8 | name: db-mysql-si 9 | databaseNames: 10 | - database_one 11 | clusterId: 12 | credentialSecret: db-secret-mysql-si 13 | size: 10 14 | timezone: "UTC" 15 | type: mysql 16 | profiles: {} 17 | timeMachine: 18 | name: db-mysql-si_TM 19 | description: "TM provisioned by operator" 20 | sla : "DEFAULT_OOB_GOLD_SLA" 21 | dailySnapshotTime: "12:34:56" 22 | snapshotsPerDay: 4 23 | logCatchUpFrequency: 90 24 | weeklySnapshotDay: "WEDNESDAY" 25 | monthlySnapshotDay: 24 26 | quarterlySnapshotMonth: "Jan" 27 | additionalArguments: {} 28 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mysql-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: db-secret-mysql-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | 10 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mysql-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: ndb-secret-mysql-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mysql-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: ndb-mysql 5 | spec: 6 | credentialSecret: ndb-secret-mysql-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/mysql-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app-mysql-si 5 | labels: 6 | app: app-mysql-si 7 | spec: 8 | containers: 9 | - name: app-mysql-container 10 | image: mazins/ndb-operator-mysql:latest 11 | env: 12 | - name: DBHOST 13 | value: db-mysql-si-svc 14 | - name: DATABASE 15 | value: database_one 16 | - name: DBPORT 17 | value: '3306' 18 | - name: USERNAME 19 | value: root 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: db-secret-mysql-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup $(DB_HOST).$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for $(DB_HOST); sleep 2; done"] 31 | env: 32 | - name: DB_HOST 33 | value: db-mysql-si-svc -------------------------------------------------------------------------------- /automation/tests/provisioning/pg-si_test/config/database.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: Database 3 | metadata: 4 | name: db-pg-si 5 | spec: 6 | ndbRef: ndb-pg 7 | databaseInstance: 8 | Name: db-pg-si 9 | databaseNames: 10 | - database_one 11 | - database_two 12 | - database_three 13 | clusterId: 14 | credentialSecret: db-secret-pg-si 15 | size: 10 16 | timezone: "UTC" 17 | type: postgres 18 | profiles: {} 19 | timeMachine: 20 | name: db-pg-si_TM 21 | description: "TM provisioned by operator" 22 | sla : "DEFAULT_OOB_GOLD_SLA" 23 | dailySnapshotTime: "12:34:56" 24 | snapshotsPerDay: 4 25 | logCatchUpFrequency: 90 26 | weeklySnapshotDay: "WEDNESDAY" 27 | monthlySnapshotDay: 24 28 | quarterlySnapshotMonth: "Jan" 29 | additionalArguments: {} 30 | -------------------------------------------------------------------------------- /automation/tests/provisioning/pg-si_test/config/db-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: db-secret-pg-si 5 | type: Opaque 6 | stringData: 7 | password: 8 | ssh_public_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCwyAhpllp2WwrUB1aO/0/DN5nIWNXJWQ3ybhuEG4U+kHl8xFFKnPOTDQtTK8UwByoSf6wqIfTr10ESAoHySOpxHk2gyVHVmUmRZ1WFiNR5tW3Q4qbq1qKpIVy1jH9ZRoTJwzg0J33W9W8SZzhM8Nj0nwuDqp6FS8ui7q9H3tgM+9bYYxETTg52NEw7jTVQx6KaZgG+p/8armoYPKh9DGhBYGY3oCmGiOYlm/phSlj3R63qghZIsBXKxeJDEs4cLolQ+9QYoRqqusdEGVCp7Ba/GtUPdBPYdTy+xuXGiALEpsCrqyUstxypHZVJEQfmqS8uy9UB8KFg2YepwhPgX1oN noname 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/pg-si_test/config/ndb-secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: ndb-secret-pg-si 5 | type: Opaque 6 | stringData: 7 | username: 8 | password: 9 | ca_certificate: "" 10 | -------------------------------------------------------------------------------- /automation/tests/provisioning/pg-si_test/config/ndb.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ndb.nutanix.com/v1alpha1 2 | kind: NDBServer 3 | metadata: 4 | name: ndb-pg 5 | spec: 6 | credentialSecret: ndb-secret-pg-si 7 | server: :8443/era/v0.9> 8 | skipCertificateVerification: true 9 | -------------------------------------------------------------------------------- /automation/tests/provisioning/pg-si_test/config/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: app-pg-si 5 | labels: 6 | app: app-pg-si 7 | spec: 8 | containers: 9 | - name: best-app 10 | image: manavrajvanshinx/best-app:latest 11 | resources: 12 | limits: 13 | memory: 512Mi 14 | cpu: "1" 15 | env: 16 | - name: DBHOST 17 | value: db-pg-si-svc 18 | - name: DBPORT 19 | value: '80' 20 | - name: PASSWORD 21 | valueFrom: 22 | secretKeyRef: 23 | name: db-secret-pg-si 24 | key: password 25 | ports: 26 | - containerPort: 3000 27 | initContainers: 28 | - name: init-db 29 | image: busybox:1.28 30 | command: ['sh', '-c', "until nslookup db-pg-si-svc.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for database service; sleep 2; done"] 31 | -------------------------------------------------------------------------------- /automation/util/cloning_test_suite_manager.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | clientsetv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/automation/clientset/v1alpha1" 9 | "github.com/nutanix-cloud-native/ndb-operator/ndb_api" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | func (cm *CloningTestSuiteManager) Setup(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, t *testing.T) (err error) { 14 | logger := GetLogger(ctx) 15 | logger.Println("CloneTestSuiteManager.Setup() starting...") 16 | 17 | err = provisionOrClone(ctx, st, clientset, v1alpha1ClientSet, t) 18 | 19 | logger.Println("CloneTestSuiteManager.Setup() ended!") 20 | 21 | return 22 | } 23 | 24 | func (cm *CloningTestSuiteManager) TearDown(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, t *testing.T) (err error) { 25 | logger := GetLogger(ctx) 26 | logger.Println("CloneTestSuiteManager.TearDown() starting...") 27 | 28 | err = deprovisionOrDeclone(ctx, st, clientset, v1alpha1ClientSet, t) 29 | 30 | logger.Println("CloneTestSuiteManager.TearDown() ended!") 31 | 32 | return 33 | } 34 | 35 | func (cm *CloningTestSuiteManager) GetDatabaseOrCloneResponse(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client) (databaseResponse *ndb_api.DatabaseResponse, err error) { 36 | logger := GetLogger(ctx) 37 | logger.Println("CloneTestSuiteManager.GetDatabaseResponse() starting...") 38 | 39 | databaseResponse, err = getDatabaseOrCloneResponse(ctx, st, clientset, v1alpha1ClientSet) 40 | 41 | logger.Println("CloneTestSuiteManager.GetDatabaseResponse() ended!") 42 | 43 | return 44 | } 45 | 46 | func (cm *CloningTestSuiteManager) GetAppResponse(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, localPort string) (res http.Response, err error) { 47 | logger := GetLogger(ctx) 48 | logger.Println("CloneTestSuiteManager.GetAppResponse() starting...") 49 | 50 | res, err = getAppResponse(ctx, st, clientset, localPort) 51 | 52 | logger.Println("CloneTestSuiteManager.GetAppResponse() ended!") 53 | 54 | return 55 | } 56 | 57 | // EMPTY STUB 58 | func (dm *CloningTestSuiteManager) GetTimemachineResponseByDatabaseId(ctx context.Context, st *SetupTypes, clientset *kubernetes.Clientset, v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client) (timemachineResponse *ndb_api.TimeMachineResponse, err error) { 59 | return nil, nil 60 | } 61 | -------------------------------------------------------------------------------- /automation/util/interfaces.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "net/http" 6 | "testing" 7 | 8 | clientsetv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/automation/clientset/v1alpha1" 9 | "github.com/nutanix-cloud-native/ndb-operator/ndb_api" 10 | "k8s.io/client-go/kubernetes" 11 | ) 12 | 13 | func GetTestSuiteManager(ctx context.Context, st SetupTypes) (manager TestSuiteManager) { 14 | logger := GetLogger(ctx) 15 | logger.Println("GetTestSuiteManager() starting...") 16 | 17 | if st.Database.Spec.IsClone { 18 | logger.Println("CloneTestSuiteManage() retrieved!") 19 | manager = &CloningTestSuiteManager{} 20 | } else { 21 | logger.Println("DatabaseTestSuiteManager() retrieved!") 22 | manager = &ProvisioningTestSuiteManager{} 23 | } 24 | return 25 | } 26 | 27 | type TestSuiteManager interface { 28 | Setup( 29 | ctx context.Context, 30 | st *SetupTypes, 31 | clientset *kubernetes.Clientset, 32 | v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, 33 | t *testing.T) (err error) 34 | TearDown( 35 | ctx context.Context, 36 | st *SetupTypes, 37 | clientset *kubernetes.Clientset, 38 | v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client, 39 | t *testing.T) (err error) 40 | GetDatabaseOrCloneResponse( 41 | ctx context.Context, 42 | st *SetupTypes, 43 | clientset *kubernetes.Clientset, 44 | v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client) (databaseResponse *ndb_api.DatabaseResponse, err error) 45 | GetAppResponse( 46 | ctx context.Context, 47 | st *SetupTypes, 48 | clientset *kubernetes.Clientset, 49 | localPort string) (res http.Response, err error) 50 | GetTimemachineResponseByDatabaseId( 51 | ctx context.Context, 52 | st *SetupTypes, 53 | clientset *kubernetes.Clientset, 54 | v1alpha1ClientSet *clientsetv1alpha1.V1alpha1Client) (timemachineResponse *ndb_api.TimeMachineResponse, err error) 55 | } 56 | 57 | type CloningTestSuiteManager struct{} 58 | 59 | type ProvisioningTestSuiteManager struct{} 60 | -------------------------------------------------------------------------------- /common/constants.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package common 18 | 19 | // Constants are defined in lexographical order 20 | const ( 21 | AUTH_RESPONSE_STATUS_SUCCESS = "success" 22 | 23 | DATABASE_CR_STATUS_CREATING = "CREATING" 24 | DATABASE_CR_STATUS_CREATION_ERROR = "CREATION ERROR" 25 | DATABASE_CR_STATUS_DELETING = "DELETING" 26 | DATABASE_CR_STATUS_NOT_FOUND = "NOT FOUND" 27 | DATABASE_CR_STATUS_READY = "READY" 28 | 29 | DATABASE_DEFAULT_PORT_MONGODB = 27017 30 | DATABASE_DEFAULT_PORT_MSSQL = 1433 31 | DATABASE_DEFAULT_PORT_MYSQL = 3306 32 | DATABASE_DEFAULT_PORT_POSTGRES = 5432 33 | 34 | DATABASE_ENGINE_TYPE_GENERIC = "Generic" 35 | DATABASE_ENGINE_TYPE_MONGODB = "mongodb_database" 36 | DATABASE_ENGINE_TYPE_MSSQL = "sqlserver_database" 37 | DATABASE_ENGINE_TYPE_MYSQL = "mysql_database" 38 | DATABASE_ENGINE_TYPE_ORACLE = "oracle_database" 39 | DATABASE_ENGINE_TYPE_POSTGRES = "postgres_database" 40 | 41 | DATABASE_RECONCILE_INTERVAL_SECONDS = 15 42 | 43 | DATABASE_TYPE_GENERIC = "generic" 44 | DATABASE_TYPE_MONGODB = "mongodb" 45 | DATABASE_TYPE_MSSQL = "mssql" 46 | DATABASE_TYPE_MYSQL = "mysql" 47 | DATABASE_TYPE_ORACLE = "oracle" 48 | DATABASE_TYPE_POSTGRES = "postgres" 49 | DATABASE_TYPES = "mssql, mysql, postgres, mongodb" 50 | 51 | FINALIZER_DATABASE_SERVER = "ndb.nutanix.com/finalizerserver" 52 | FINALIZER_INSTANCE = "ndb.nutanix.com/finalizerinstance" 53 | 54 | NDB_CR_STATUS_AUTHENTICATION_ERROR = "Authentication Error" 55 | NDB_CR_STATUS_CREDENTIAL_ERROR = "Credential Error" 56 | NDB_CR_STATUS_ERROR = "Error" 57 | NDB_CR_STATUS_OK = "Ok" 58 | 59 | NDB_PARAM_PASSWORD = "password" 60 | NDB_PARAM_SSH_PUBLIC_KEY = "ssh_public_key" 61 | NDB_PARAM_USERNAME = "username" 62 | 63 | NDB_RECONCILE_DATABASE_COUNTER = 4 64 | NDB_RECONCILE_INTERVAL_SECONDS = 15 65 | 66 | PROFILE_DEFAULT_OOB_SMALL_COMPUTE = "DEFAULT_OOB_SMALL_COMPUTE" 67 | 68 | PROFILE_MAP_PARAM = "profileMap" 69 | 70 | PROFILE_STATUS_READY = "READY" 71 | 72 | PROFILE_TYPE_COMPUTE = "Compute" 73 | PROFILE_TYPE_DATABASE_PARAMETER = "Database_Parameter" 74 | PROFILE_TYPE_DATABASE_PARAMETER_INSTANCE = "Database_Parameter_Instance" 75 | PROFILE_TYPE_NETWORK = "Network" 76 | PROFILE_TYPE_SOFTWARE = "Software" 77 | 78 | PROPERTY_NAME_VM_IP = "vm_ip" 79 | 80 | SECRET_DATA_KEY_CA_CERTIFICATE = "ca_certificate" 81 | SECRET_DATA_KEY_PASSWORD = "password" 82 | SECRET_DATA_KEY_SSH_PUBLIC_KEY = "ssh_public_key" 83 | SECRET_DATA_KEY_USERNAME = "username" 84 | 85 | SLA_NAME_NONE = "NONE" 86 | 87 | TIMEZONE_UTC = "UTC" 88 | 89 | TOPOLOGY_ALL = "ALL" 90 | TOPOLOGY_CLUSTER = "cluster" 91 | TOPOLOGY_DATABASE = "database" 92 | TOPOLOGY_INSTANCE = "instance" 93 | TOPOLOGY_SINGLE = "single" 94 | ) 95 | -------------------------------------------------------------------------------- /common/util/create_id_map.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | ) 23 | 24 | // Returns a map from a slice of objects of type 'T' 25 | // Uses the key provided in the arguments 26 | // Returns an error if the desired key is not a field in any object of the slice 27 | func CreateMapForKey[T any](objects []T, key string) (m map[string]T, err error) { 28 | m = make(map[string]T) 29 | for i, obj := range objects { 30 | keyField := reflect.ValueOf(obj).FieldByName(key) 31 | if !keyField.IsValid() { 32 | err = fmt.Errorf("%s field not found in object at index %d", key, i) 33 | return 34 | } 35 | 36 | keyValue := fmt.Sprintf("%v", keyField.Interface()) 37 | m[keyValue] = obj 38 | } 39 | 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /common/util/create_id_map_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "reflect" 21 | "testing" 22 | ) 23 | 24 | // Tests the following scenarios: 25 | // 1. Creates a map with given field as key 26 | // 2. Returns error when key is empty 27 | // 3. Returns error when key does not exist 28 | func TestCreateMapForKey(t *testing.T) { 29 | type randomType struct { 30 | Foo int 31 | Bar string 32 | Baz float32 33 | } 34 | type args struct { 35 | objects []randomType 36 | key string 37 | } 38 | tests := []struct { 39 | name string 40 | args args 41 | wantM map[string]randomType 42 | wantErr bool 43 | }{ 44 | { 45 | name: "Creates a map with given field as key", 46 | args: args{ 47 | key: "Bar", 48 | objects: []randomType{ 49 | {1, "a", 1.1}, 50 | {2, "b", 2.2}, 51 | }, 52 | }, 53 | wantM: map[string]randomType{ 54 | "a": {1, "a", 1.1}, 55 | "b": {2, "b", 2.2}, 56 | }, 57 | wantErr: false, 58 | }, 59 | { 60 | name: "Returns error when key is empty", 61 | args: args{ 62 | key: "", 63 | objects: []randomType{ 64 | {1, "a", 1.1}, 65 | {2, "b", 2.2}, 66 | }, 67 | }, 68 | wantM: map[string]randomType{}, 69 | wantErr: true, 70 | }, 71 | { 72 | name: "Returns error when key does not exist", 73 | args: args{ 74 | key: "qwertyuiop", 75 | objects: []randomType{ 76 | {1, "a", 1.1}, 77 | {2, "b", 2.2}, 78 | }, 79 | }, 80 | wantM: map[string]randomType{}, 81 | wantErr: true, 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | gotM, err := CreateMapForKey(tt.args.objects, tt.args.key) 87 | if (err != nil) != tt.wantErr { 88 | t.Errorf("CreateMapForKey() error = %v, wantErr %v", err, tt.wantErr) 89 | return 90 | } 91 | if !reflect.DeepEqual(gotM, tt.wantM) { 92 | t.Errorf("CreateMapForKey() = %v, want %v", gotM, tt.wantM) 93 | } 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /common/util/deep_equal_with_exception.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "reflect" 21 | ) 22 | 23 | // Returns if two structs/objects are equal with the exception of 24 | // the field under the exceptionKey 25 | func DeepEqualWithException(a, b interface{}, exceptionKey string) bool { 26 | v1 := reflect.ValueOf(a) 27 | v2 := reflect.ValueOf(b) 28 | // Check if a and b are of the same type 29 | if v1.Type() != v2.Type() { 30 | return false 31 | } 32 | // Iterate over struct fields 33 | for i := 0; i < v1.NumField(); i++ { 34 | fieldName := v1.Type().Field(i).Name 35 | // Exclude the specified exception key 36 | if fieldName == exceptionKey { 37 | continue 38 | } 39 | // Use reflect.DeepEqual to compare field values 40 | if !reflect.DeepEqual(v1.Field(i).Interface(), v2.Field(i).Interface()) { 41 | return false 42 | } 43 | } 44 | return true 45 | } 46 | -------------------------------------------------------------------------------- /common/util/deep_equal_with_exception_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import "testing" 20 | 21 | // Tests the following : 22 | // 1. Returns true when both objects are same 23 | // 2. Returns true when both objects are same with different exceptionKey field 24 | // 3. Returns false when both objects are differet 25 | // 4. Returns false when both objects are of different type 26 | func TestDeepEqualWithException(t *testing.T) { 27 | type randomType struct { 28 | Foo int 29 | Bar string 30 | Baz []float32 31 | } 32 | type anotherRandomType struct { 33 | Foo int 34 | Bar string 35 | Baz []float32 36 | } 37 | type args struct { 38 | a interface{} 39 | b interface{} 40 | exceptionKey string 41 | } 42 | tests := []struct { 43 | name string 44 | args args 45 | want bool 46 | }{ 47 | { 48 | name: "Returns true when both objects are same", 49 | args: args{ 50 | a: randomType{1, "xyz", []float32{1.1, 2.2, 3.3}}, 51 | b: randomType{1, "xyz", []float32{1.1, 2.2, 3.3}}, 52 | exceptionKey: "Foo", 53 | }, 54 | want: true, 55 | }, 56 | { 57 | name: "Returns true when both objects are same with different exceptionKey field", 58 | args: args{ 59 | a: randomType{1, "xyz", []float32{1.1, 2.2, 3.3}}, 60 | b: randomType{2, "xyz", []float32{1.1, 2.2, 3.3}}, 61 | exceptionKey: "Foo", 62 | }, 63 | want: true, 64 | }, 65 | { 66 | name: "Returns false when both objects are differet", 67 | args: args{ 68 | a: randomType{1, "abc", nil}, 69 | b: randomType{1, "xyz", []float32{1.1, 2.2, 3.3}}, 70 | exceptionKey: "Foo", 71 | }, 72 | want: false, 73 | }, 74 | { 75 | name: "Returns false when both objects are of different type", 76 | args: args{ 77 | a: randomType{1, "abc", []float32{1.1, 2.2, 3.3}}, 78 | b: anotherRandomType{1, "abc", []float32{1.1, 2.2, 3.3}}, 79 | exceptionKey: "Foo", 80 | }, 81 | want: false, 82 | }, 83 | } 84 | for _, tt := range tests { 85 | t.Run(tt.name, func(t *testing.T) { 86 | if got := DeepEqualWithException(tt.args.a, tt.args.b, tt.args.exceptionKey); got != tt.want { 87 | t.Errorf("DeepEqualWithException() = %v, want %v", got, tt.want) 88 | } 89 | }) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /common/util/filter_slice.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "errors" 21 | ) 22 | 23 | // A utility function to filter the items based on the conditions 24 | // defined in the predicate 'fn' 25 | func Filter[T any](items []T, fn func(item T) bool) []T { 26 | filteredItems := []T{} 27 | for _, value := range items { 28 | if fn(value) { 29 | filteredItems = append(filteredItems, value) 30 | } 31 | } 32 | return filteredItems 33 | } 34 | 35 | // A utility function to find the first match for the given filter function 36 | // in the case of no match, returns an empty struct instance and an error 37 | func FindFirst[T any](items []T, filter func(item T) bool) (T, error) { 38 | for _, value := range items { 39 | if filter(value) { 40 | return value, nil 41 | } 42 | } 43 | 44 | // returning an empty instance of T in the case of no match 45 | var empty T 46 | return empty, errors.New("no element found matching the provided criteria") 47 | } 48 | -------------------------------------------------------------------------------- /common/util/filter_slice_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "errors" 21 | "reflect" 22 | "testing" 23 | 24 | "github.com/stretchr/testify/assert" 25 | ) 26 | 27 | func TestFilter(t *testing.T) { 28 | type args struct { 29 | items []int 30 | fn func(item int) bool 31 | } 32 | tests := []struct { 33 | name string 34 | args args 35 | want []int 36 | }{ 37 | { 38 | name: "Test 1: Filter returns a result slice based on the provided function", 39 | args: args{ 40 | items: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 41 | fn: func(item int) bool { return item%2 == 0 }, 42 | }, 43 | want: []int{2, 4, 6, 8, 10}, 44 | }, 45 | { 46 | name: "Test 2: Filter returns an empty slice if no elements filter through the provided function", 47 | args: args{ 48 | items: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 49 | fn: func(item int) bool { return item > 9999 }, 50 | }, 51 | want: []int{}, 52 | }, 53 | } 54 | for _, tt := range tests { 55 | t.Run(tt.name, func(t *testing.T) { 56 | if got := Filter(tt.args.items, tt.args.fn); !reflect.DeepEqual(got, tt.want) { 57 | t.Errorf("Filter() = %v, want %v", got, tt.want) 58 | } 59 | }) 60 | } 61 | } 62 | 63 | func TestFindFirstInt(t *testing.T) { 64 | tests := []struct { 65 | name string 66 | input []int 67 | filterFunction func(item int) bool 68 | expectedOutput int 69 | expectedError error 70 | }{ 71 | { 72 | name: "Test 1: Find the first even number", 73 | input: []int{1, 3, 5, 2, 4, 6}, 74 | filterFunction: func(item int) bool { return item%2 == 0 }, 75 | expectedOutput: 2, 76 | expectedError: nil, 77 | }, 78 | { 79 | name: "Test 2: Find the first number greater than 10", 80 | input: []int{5, 8, 12, 15}, 81 | filterFunction: func(item int) bool { return item > 10 }, 82 | expectedOutput: 12, 83 | expectedError: nil, 84 | }, 85 | { 86 | name: "Test 3: No match in the input slice", 87 | input: []int{2, 4, 6, 8, 10}, 88 | filterFunction: func(item int) bool { return item > 100 }, 89 | expectedOutput: 0, 90 | expectedError: errors.New("no element found matching the provided criteria"), 91 | }, 92 | { 93 | name: "Test 4: Empty input slice", 94 | input: []int{}, 95 | filterFunction: func(item int) bool { return item > 5 }, 96 | expectedOutput: 0, 97 | expectedError: errors.New("no element found matching the provided criteria"), 98 | }, 99 | } 100 | 101 | for _, tt := range tests { 102 | t.Run(tt.name, func(t *testing.T) { 103 | result, err := FindFirst(tt.input, tt.filterFunction) 104 | 105 | if tt.expectedError != nil { 106 | assert.EqualError(t, err, tt.expectedError.Error(), "Unexpected error") 107 | } else { 108 | assert.NoError(t, err, "Unexpected error") 109 | assert.Equal(t, tt.expectedOutput, result, "FindFirst result is not as expected") 110 | } 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /common/util/print.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import ( 20 | "encoding/json" 21 | ) 22 | 23 | // A utility function to convert an interface to a string 24 | func ToString(x interface{}) string { 25 | s, err := json.Marshal(x) 26 | if err != nil { 27 | return "" 28 | } 29 | return string(s) 30 | } 31 | -------------------------------------------------------------------------------- /common/util/print_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package util 18 | 19 | import "testing" 20 | 21 | func TestToString(t *testing.T) { 22 | 23 | tests := []struct { 24 | name string 25 | args interface{} 26 | want string 27 | }{ 28 | { 29 | name: "Test 1: Convert int to string", 30 | args: 42, 31 | want: "42", 32 | }, 33 | { 34 | name: "Test 2: Convert string to string", 35 | args: "hello", 36 | want: "\"hello\"", 37 | }, 38 | { 39 | name: "Test 3: Convert struct to string", 40 | args: struct{ Name string }{Name: "TONY STARK"}, 41 | want: `{"Name":"TONY STARK"}`, 42 | }, 43 | { 44 | name: "Test 4: Convert slice to string", 45 | args: []int{1, 2, 3}, 46 | want: "[1,2,3]", 47 | }, 48 | { 49 | name: "Test 5: Convert map to string", 50 | args: map[string]interface{}{"key": "value", "number": 8}, 51 | want: `{"key":"value","number":8}`, 52 | }, 53 | { 54 | name: "Test 6: Convert nil to string", 55 | args: nil, 56 | want: "null", 57 | }, 58 | } 59 | for _, tt := range tests { 60 | t.Run(tt.name, func(t *testing.T) { 61 | if got := ToString(tt.args); got != tt.want { 62 | t.Errorf("ToString() = %v, want %v", got, tt.want) 63 | } 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /common/util/secret.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/types" 8 | "sigs.k8s.io/controller-runtime/pkg/client" 9 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 10 | ) 11 | 12 | // Returns all the data in the given secret and namespace combination 13 | // returns an error if the secret is not found 14 | func GetAllDataFromSecret(ctx context.Context, client client.Client, name, namespace string) (data map[string][]byte, err error) { 15 | log := ctrllog.FromContext(ctx) 16 | log.Info("Entered util.GetAllDataFromSecret", "Secret Name", name, "Secret Namespace", namespace) 17 | secret := &v1.Secret{} 18 | err = client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret) 19 | if err != nil { 20 | log.Error(err, "Error occured while trying to read secret", "Secret Name", name, "Secret Namespace", namespace) 21 | } else { 22 | data = secret.Data 23 | log.Info("Returning from util.GetAllDataFromSecret") 24 | } 25 | return 26 | } 27 | -------------------------------------------------------------------------------- /common/util/secret_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/client/fake" 12 | ) 13 | 14 | func TestGetAllDataFromSecret(t *testing.T) { 15 | type args struct { 16 | ctx context.Context 17 | client client.Client 18 | name string 19 | namespace string 20 | } 21 | fakeClient := fake.NewClientBuilder().WithObjects(&v1.Secret{ 22 | ObjectMeta: metav1.ObjectMeta{ 23 | Namespace: "test-namespace", 24 | Name: "test-secret", 25 | }, 26 | Data: map[string][]byte{ 27 | "key1": []byte("value1"), 28 | "key2": []byte("value2"), 29 | }, 30 | }).Build() 31 | 32 | tests := []struct { 33 | name string 34 | args args 35 | wantData map[string][]byte 36 | wantErr bool 37 | }{ 38 | { 39 | name: "Test 1: GetAllDataFromSecret fetches all the data from the secret", 40 | args: args{ 41 | ctx: context.TODO(), 42 | client: fakeClient, 43 | name: "test-secret", 44 | namespace: "test-namespace", 45 | }, 46 | wantData: map[string][]byte{ 47 | "key1": []byte("value1"), 48 | "key2": []byte("value2"), 49 | }, 50 | wantErr: false, 51 | }, 52 | { 53 | name: "Test 2: GetAllDataFromSecret fetches returns an error if the secret does not exist", 54 | args: args{ 55 | ctx: context.TODO(), 56 | client: fakeClient, 57 | name: "test-does-not-exist", 58 | namespace: "test-namespace", 59 | }, 60 | wantData: nil, 61 | wantErr: true, 62 | }, 63 | } 64 | for _, tt := range tests { 65 | t.Run(tt.name, func(t *testing.T) { 66 | gotData, err := GetAllDataFromSecret(tt.args.ctx, tt.args.client, tt.args.name, tt.args.namespace) 67 | if (err != nil) != tt.wantErr { 68 | t.Errorf("GetAllDataFromSecret() error = %v, wantErr %v", err, tt.wantErr) 69 | return 70 | } 71 | if !reflect.DeepEqual(gotData, tt.wantData) { 72 | t.Errorf("GetAllDataFromSecret() = %v, want %v", gotData, tt.wantData) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /common/util/webhook_utils.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | "strconv" 9 | "strings" 10 | 11 | "github.com/google/uuid" 12 | "k8s.io/apimachinery/pkg/util/validation/field" 13 | ) 14 | 15 | func ValidateUUID(u string) error { 16 | _, err := uuid.Parse(u) 17 | return err 18 | } 19 | 20 | func ValidateURL(u string) error { 21 | _, err := url.ParseRequestURI(u) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return nil 27 | } 28 | 29 | func CombineFieldErrors(fieldErrors field.ErrorList) error { 30 | 31 | if len(fieldErrors) == 0 { 32 | return nil 33 | } 34 | 35 | var errorStrings []string 36 | for _, fe := range fieldErrors { 37 | errorStrings = append(errorStrings, fe.Error()) 38 | } 39 | return errors.New(strings.Join(errorStrings, "; ")) 40 | } 41 | 42 | func IsFeatureEnabled(key string) bool { 43 | val, ok := os.LookupEnv(key) 44 | 45 | if !ok { 46 | fmt.Printf("error reading %s env variable", key) 47 | // safer to return "true" as default, since we want the webhooks 48 | // to be ENABLED everywhere outside the local development process 49 | return true 50 | } else { 51 | 52 | val, err := strconv.ParseBool(val) 53 | 54 | if err != nil { 55 | return true 56 | } 57 | 58 | return val 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | # WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. 4 | apiVersion: cert-manager.io/v1 5 | kind: Issuer 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: issuer 9 | app.kubernetes.io/instance: selfsigned-issuer 10 | app.kubernetes.io/component: certificate 11 | app.kubernetes.io/created-by: ndb-operator 12 | app.kubernetes.io/part-of: ndb-operator 13 | app.kubernetes.io/managed-by: kustomize 14 | name: selfsigned-issuer 15 | namespace: system 16 | spec: 17 | selfSigned: {} 18 | --- 19 | apiVersion: cert-manager.io/v1 20 | kind: Certificate 21 | metadata: 22 | labels: 23 | app.kubernetes.io/name: certificate 24 | app.kubernetes.io/instance: serving-cert 25 | app.kubernetes.io/component: certificate 26 | app.kubernetes.io/created-by: ndb-operator 27 | app.kubernetes.io/part-of: ndb-operator 28 | app.kubernetes.io/managed-by: kustomize 29 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 30 | namespace: system 31 | spec: 32 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 33 | dnsNames: 34 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 35 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 36 | issuerRef: 37 | kind: Issuer 38 | name: selfsigned-issuer 39 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 40 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: cert-manager.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: cert-manager.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: cert-manager.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: cert-manager.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/crd/bases/ndb.nutanix.com_ndbservers.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.14.0 7 | name: ndbservers.ndb.nutanix.com 8 | spec: 9 | group: ndb.nutanix.com 10 | names: 11 | kind: NDBServer 12 | listKind: NDBServerList 13 | plural: ndbservers 14 | shortNames: 15 | - ndb 16 | - ndbs 17 | singular: ndbserver 18 | scope: Namespaced 19 | versions: 20 | - additionalPrinterColumns: 21 | - jsonPath: .status.status 22 | name: Status 23 | type: string 24 | - jsonPath: .status.lastUpdated 25 | name: Updated At 26 | type: string 27 | name: v1alpha1 28 | schema: 29 | openAPIV3Schema: 30 | description: NDBServer is the Schema for the ndbservers API 31 | properties: 32 | apiVersion: 33 | description: |- 34 | APIVersion defines the versioned schema of this representation of an object. 35 | Servers should convert recognized schemas to the latest internal value, and 36 | may reject unrecognized values. 37 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 38 | type: string 39 | kind: 40 | description: |- 41 | Kind is a string value representing the REST resource this object represents. 42 | Servers may infer this from the endpoint the client submits requests to. 43 | Cannot be updated. 44 | In CamelCase. 45 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 46 | type: string 47 | metadata: 48 | type: object 49 | spec: 50 | description: NDBServerSpec defines the desired state of NDBServer 51 | properties: 52 | credentialSecret: 53 | type: string 54 | server: 55 | type: string 56 | skipCertificateVerification: 57 | default: false 58 | description: Skip server's certificate and hostname verification 59 | type: boolean 60 | required: 61 | - credentialSecret 62 | - server 63 | type: object 64 | status: 65 | description: NDBServerStatus defines the observed state of NDBServer 66 | properties: 67 | databases: 68 | additionalProperties: 69 | description: Database related info to be stored in the status field 70 | of the NDB CR 71 | properties: 72 | dbServerId: 73 | type: string 74 | id: 75 | type: string 76 | ipAddress: 77 | type: string 78 | name: 79 | type: string 80 | status: 81 | type: string 82 | timeMachineId: 83 | type: string 84 | type: 85 | type: string 86 | required: 87 | - dbServerId 88 | - id 89 | - ipAddress 90 | - name 91 | - status 92 | - timeMachineId 93 | - type 94 | type: object 95 | type: object 96 | lastUpdated: 97 | type: string 98 | reconcileCounter: 99 | properties: 100 | database: 101 | type: integer 102 | required: 103 | - database 104 | type: object 105 | status: 106 | type: string 107 | required: 108 | - databases 109 | - lastUpdated 110 | - reconcileCounter 111 | - status 112 | type: object 113 | type: object 114 | served: true 115 | storage: true 116 | subresources: 117 | status: {} 118 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/ndb.nutanix.com_databases.yaml 6 | - bases/ndb.nutanix.com_ndbservers.yaml 7 | #+kubebuilder:scaffold:crdkustomizeresource 8 | 9 | patchesStrategicMerge: 10 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 11 | # patches here are for enabling the conversion webhook for each CRD 12 | - patches/webhook_in_databases.yaml 13 | #- patches/webhook_in_ndbservers.yaml 14 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 15 | 16 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 17 | # patches here are for enabling the CA injection for each CRD 18 | - patches/cainjection_in_databases.yaml 19 | #- patches/cainjection_in_ndbservers.yaml 20 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 21 | 22 | # the following config is for teaching kustomize how to do kustomization for CRDs. 23 | configurations: 24 | - kustomizeconfig.yaml 25 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_databases.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: cert-manager-test/selfsigned-cert 7 | name: databases.ndb.nutanix.com 8 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_ndbservers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: ndbservers.ndb.nutanix.com 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_databases.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: databases.ndb.nutanix.com 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_ndbservers.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: ndbservers.ndb.nutanix.com 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: ndb-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: ndb-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | - ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | - ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | - manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | - webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | objref: 51 | kind: Certificate 52 | group: cert-manager.io 53 | version: v1 54 | name: serving-cert # this name should match the one in certificate.yaml 55 | fieldref: 56 | fieldpath: metadata.namespace 57 | - name: CERTIFICATE_NAME 58 | objref: 59 | kind: Certificate 60 | group: cert-manager.io 61 | version: v1 62 | name: serving-cert # this name should match the one in certificate.yaml 63 | - name: SERVICE_NAMESPACE # namespace of the service 64 | objref: 65 | kind: Service 66 | version: v1 67 | name: webhook-service 68 | fieldref: 69 | fieldpath: metadata.namespace 70 | - name: SERVICE_NAME 71 | objref: 72 | kind: Service 73 | version: v1 74 | name: webhook-service 75 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | securityContext: 14 | allowPrivilegeEscalation: false 15 | # TODO(user): uncomment for common cases that do not require escalating privileges 16 | capabilities: 17 | drop: 18 | - "ALL" 19 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.16.0 20 | args: 21 | - "--secure-listen-address=0.0.0.0:8443" 22 | - "--upstream=http://127.0.0.1:8080/" 23 | - "--logtostderr=true" 24 | - "--v=0" 25 | ports: 26 | - containerPort: 8443 27 | protocol: TCP 28 | name: https 29 | resources: 30 | limits: 31 | cpu: 500m 32 | memory: 128Mi 33 | requests: 34 | cpu: 5m 35 | memory: 64Mi 36 | - name: manager 37 | args: 38 | - "--health-probe-bind-address=:8081" 39 | - "--metrics-bind-address=127.0.0.1:8080" 40 | - "--leader-elect" 41 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | labels: 7 | app.kubernetes.io/name: mutatingwebhookconfiguration 8 | app.kubernetes.io/instance: mutating-webhook-configuration 9 | app.kubernetes.io/component: webhook 10 | app.kubernetes.io/created-by: ndb-operator 11 | app.kubernetes.io/part-of: ndb-operator 12 | app.kubernetes.io/managed-by: kustomize 13 | name: mutating-webhook-configuration 14 | annotations: 15 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | --- 17 | apiVersion: admissionregistration.k8s.io/v1 18 | kind: ValidatingWebhookConfiguration 19 | metadata: 20 | labels: 21 | app.kubernetes.io/name: validatingwebhookconfiguration 22 | app.kubernetes.io/instance: validating-webhook-configuration 23 | app.kubernetes.io/component: webhook 24 | app.kubernetes.io/created-by: ndb-operator 25 | app.kubernetes.io/part-of: ndb-operator 26 | app.kubernetes.io/managed-by: kustomize 27 | name: validating-webhook-configuration 28 | annotations: 29 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 30 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 81efaee7.nutanix.com 12 | # leaderElectionReleaseOnCancel defines if the leader should step down volume 13 | # when the Manager ends. This requires the binary to immediately end when the 14 | # Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 15 | # speeds up voluntary leader transitions as the new leader don't have to wait 16 | # LeaseDuration time first. 17 | # In the default scaffold provided, the program ends immediately after 18 | # the manager stops, so would be fine to enable this option. However, 19 | # if you are doing or is intended to do any operation such as perform cleanups 20 | # after the manager stops then its usage might be unsafe. 21 | # leaderElectionReleaseOnCancel: true 22 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - files: 9 | - controller_manager_config.yaml 10 | name: manager-config 11 | apiVersion: kustomize.config.k8s.io/v1beta1 12 | kind: Kustomization 13 | images: 14 | - name: controller 15 | newName: ghcr.io/nutanix-cloud-native/ndb-operator/controller 16 | newTag: v0.5.1 17 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | annotations: 23 | kubectl.kubernetes.io/default-container: manager 24 | labels: 25 | control-plane: controller-manager 26 | spec: 27 | securityContext: 28 | runAsNonRoot: true 29 | # TODO(user): For common cases that do not require escalating privileges 30 | # it is recommended to ensure that all your Pods/Containers are restrictive. 31 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 32 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 33 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 34 | # seccompProfile: 35 | # type: RuntimeDefault 36 | containers: 37 | - args: 38 | - --leader-elect 39 | image: controller:latest 40 | name: manager 41 | securityContext: 42 | allowPrivilegeEscalation: false 43 | # TODO(user): uncomment for common cases that do not require escalating privileges 44 | # capabilities: 45 | # drop: 46 | # - "ALL" 47 | livenessProbe: 48 | httpGet: 49 | path: /healthz 50 | port: 8081 51 | initialDelaySeconds: 15 52 | periodSeconds: 20 53 | readinessProbe: 54 | httpGet: 55 | path: /readyz 56 | port: 8081 57 | initialDelaySeconds: 5 58 | periodSeconds: 10 59 | resources: 60 | limits: 61 | cpu: 500m 62 | memory: 128Mi 63 | requests: 64 | cpu: 10m 65 | memory: 64Mi 66 | serviceAccountName: controller-manager 67 | terminationGracePeriodSeconds: 10 68 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/ndb-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../scorecard 7 | 8 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 9 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 10 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 11 | #patchesJson6902: 12 | #- target: 13 | # group: apps 14 | # version: v1 15 | # kind: Deployment 16 | # name: controller-manager 17 | # namespace: system 18 | # patch: |- 19 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 20 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 21 | # - op: remove 22 | # path: /spec/template/spec/containers/1/volumeMounts/0 23 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 24 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 25 | # - op: remove 26 | # path: /spec/template/spec/volumes/0 27 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | protocol: TCP 13 | targetPort: https 14 | selector: 15 | control-plane: controller-manager 16 | -------------------------------------------------------------------------------- /config/rbac/database_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit databases. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: database-editor-role 6 | rules: 7 | - apiGroups: 8 | - ndb.nutanix.com 9 | resources: 10 | - databases 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ndb.nutanix.com 21 | resources: 22 | - databases/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/database_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view databases. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: database-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ndb.nutanix.com 9 | resources: 10 | - databases 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ndb.nutanix.com 17 | resources: 18 | - databases/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/ndbserver_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit ndbservers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ndbserver-editor-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ndb-operator 10 | app.kubernetes.io/part-of: ndb-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ndbserver-editor-role 13 | rules: 14 | - apiGroups: 15 | - ndb.nutanix.com 16 | resources: 17 | - ndbservers 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - ndb.nutanix.com 28 | resources: 29 | - ndbservers/status 30 | verbs: 31 | - get 32 | -------------------------------------------------------------------------------- /config/rbac/ndbserver_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view ndbservers. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: clusterrole 7 | app.kubernetes.io/instance: ndbserver-viewer-role 8 | app.kubernetes.io/component: rbac 9 | app.kubernetes.io/created-by: ndb-operator 10 | app.kubernetes.io/part-of: ndb-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: ndbserver-viewer-role 13 | rules: 14 | - apiGroups: 15 | - ndb.nutanix.com 16 | resources: 17 | - ndbservers 18 | verbs: 19 | - get 20 | - list 21 | - watch 22 | - apiGroups: 23 | - ndb.nutanix.com 24 | resources: 25 | - ndbservers/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - events 11 | verbs: 12 | - create 13 | - patch 14 | - apiGroups: 15 | - "" 16 | resources: 17 | - endpoints 18 | verbs: 19 | - create 20 | - delete 21 | - get 22 | - list 23 | - patch 24 | - update 25 | - watch 26 | - apiGroups: 27 | - "" 28 | resources: 29 | - secrets 30 | verbs: 31 | - get 32 | - list 33 | - watch 34 | - apiGroups: 35 | - "" 36 | resources: 37 | - services 38 | verbs: 39 | - create 40 | - delete 41 | - get 42 | - list 43 | - patch 44 | - update 45 | - watch 46 | - apiGroups: 47 | - ndb.nutanix.com 48 | resources: 49 | - databases 50 | verbs: 51 | - create 52 | - delete 53 | - get 54 | - list 55 | - patch 56 | - update 57 | - watch 58 | - apiGroups: 59 | - ndb.nutanix.com 60 | resources: 61 | - databases/finalizers 62 | verbs: 63 | - update 64 | - apiGroups: 65 | - ndb.nutanix.com 66 | resources: 67 | - databases/status 68 | verbs: 69 | - get 70 | - patch 71 | - update 72 | - apiGroups: 73 | - ndb.nutanix.com 74 | resources: 75 | - ndbservers 76 | verbs: 77 | - create 78 | - delete 79 | - get 80 | - list 81 | - patch 82 | - update 83 | - watch 84 | - apiGroups: 85 | - ndb.nutanix.com 86 | resources: 87 | - ndbservers/finalizers 88 | verbs: 89 | - update 90 | - apiGroups: 91 | - ndb.nutanix.com 92 | resources: 93 | - ndbservers/status 94 | verbs: 95 | - get 96 | - patch 97 | - update 98 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.26.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.26.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.26.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.26.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.26.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.26.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: admissionregistration.k8s.io/v1 3 | kind: MutatingWebhookConfiguration 4 | metadata: 5 | name: mutating-webhook-configuration 6 | webhooks: 7 | - admissionReviewVersions: 8 | - v1 9 | clientConfig: 10 | service: 11 | name: webhook-service 12 | namespace: system 13 | path: /mutate-ndb-nutanix-com-v1alpha1-database 14 | failurePolicy: Fail 15 | name: mdatabase.kb.io 16 | rules: 17 | - apiGroups: 18 | - ndb.nutanix.com 19 | apiVersions: 20 | - v1alpha1 21 | operations: 22 | - CREATE 23 | - UPDATE 24 | resources: 25 | - databases 26 | sideEffects: None 27 | --- 28 | apiVersion: admissionregistration.k8s.io/v1 29 | kind: ValidatingWebhookConfiguration 30 | metadata: 31 | name: validating-webhook-configuration 32 | webhooks: 33 | - admissionReviewVersions: 34 | - v1 35 | clientConfig: 36 | service: 37 | name: webhook-service 38 | namespace: system 39 | path: /validate-ndb-nutanix-com-v1alpha1-database 40 | failurePolicy: Fail 41 | name: vdatabase.kb.io 42 | rules: 43 | - apiGroups: 44 | - ndb.nutanix.com 45 | apiVersions: 46 | - v1alpha1 47 | operations: 48 | - CREATE 49 | - UPDATE 50 | resources: 51 | - databases 52 | sideEffects: None 53 | -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: service 7 | app.kubernetes.io/instance: webhook-service 8 | app.kubernetes.io/component: webhook 9 | app.kubernetes.io/created-by: ndb-operator 10 | app.kubernetes.io/part-of: ndb-operator 11 | app.kubernetes.io/managed-by: kustomize 12 | name: webhook-service 13 | namespace: system 14 | spec: 15 | ports: 16 | - port: 443 17 | protocol: TCP 18 | targetPort: 9443 19 | selector: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /controllers/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "math" 23 | "time" 24 | 25 | "github.com/nutanix-cloud-native/ndb-operator/common" 26 | "github.com/nutanix-cloud-native/ndb-operator/common/util" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 30 | ) 31 | 32 | const ( 33 | EVENT_CREATION_STARTED = "CreationStarted" 34 | EVENT_CREATION_FAILED = "CreationFailed" 35 | EVENT_CREATION_COMPLETED = "CreationCompleted" 36 | 37 | EVENT_INVALID_CREDENTIALS = "InvalidCredentials" 38 | 39 | EVENT_REQUEST_GENERATION = "RequestGenerated" 40 | EVENT_REQUEST_GENERATION_FAILURE = "RequestGenerationFailed" 41 | EVENT_NDB_REQUEST_FAILED = "NDBRequestFailed" 42 | 43 | EVENT_DEREGISTRATION_STARTED = "DeregistrationStarted" 44 | EVENT_DEREGISTRATION_FAILED = "DeregistrationFailed" 45 | EVENT_DEREGISTRATION_COMPLETED = "DeregistrationCompleted" 46 | 47 | EVENT_CR_CREATED = "CustomResourceCreated" 48 | EVENT_CR_DELETED = "CustomResourceDeleted" 49 | EVENT_CR_STATUS_UPDATE_FAILED = "CustomResourceStatusUpdateFailed" 50 | 51 | EVENT_EXTERNAL_DELETE = "ExternalDeleteDetected" 52 | 53 | EVENT_RESOURCE_LOOKUP_ERROR = "ResourceLookupError" 54 | 55 | EVENT_SERVICE_SETUP_FAILED = "ServiceSetupFailed" 56 | EVENT_ENDPOINT_SETUP_FAILED = "EndpointSetupFailed" 57 | 58 | EVENT_WAITING_FOR_NDB_RECONCILE = "WaitingForNDBReconcile" 59 | EVENT_WAITING_FOR_IP_ADDRESS = "WaitingForIPAddress" 60 | ) 61 | 62 | // doNotRequeue Finished processing. No need to put back on the reconcile queue. 63 | func doNotRequeue() (ctrl.Result, error) { 64 | return ctrl.Result{}, nil 65 | } 66 | 67 | // requeue Not finished processing. Put back on reconcile queue and continue. 68 | func requeue() (ctrl.Result, error) { 69 | return ctrl.Result{Requeue: true}, nil 70 | } 71 | 72 | // requeueOnErr Failed while processing. Put back on reconcile queue and try again. 73 | func requeueOnErr(err error) (ctrl.Result, error) { 74 | return ctrl.Result{}, err 75 | } 76 | 77 | // requeue after a timeout. Put back on reconcile queue after a timeout and continue. 78 | func requeueWithTimeout(t int) (ctrl.Result, error) { 79 | return ctrl.Result{RequeueAfter: time.Second * time.Duration(math.Abs(float64(t)))}, nil 80 | } 81 | 82 | // Returns the credentials(username, password and caCertificate) for NDB 83 | // Returns an error if reading the secret containing credentials fails 84 | func getNDBCredentialsFromSecret(ctx context.Context, k8sClient client.Client, name, namespace string) (username, password, caCert string, err error) { 85 | log := ctrllog.FromContext(ctx) 86 | log.Info("Reading secret", "Secret Name", name) 87 | secretDataMap, err := util.GetAllDataFromSecret(ctx, k8sClient, name, namespace) 88 | if err != nil { 89 | log.Error(err, "Error occured while fetching NDB secret", "Secret Name", name, "Namespace", namespace) 90 | return 91 | } 92 | username = string(secretDataMap[common.SECRET_DATA_KEY_USERNAME]) 93 | password = string(secretDataMap[common.SECRET_DATA_KEY_PASSWORD]) 94 | caCert = string(secretDataMap[common.SECRET_DATA_KEY_CA_CERTIFICATE]) 95 | if username == "" || password == "" { 96 | errStatement := "NDB username or password is empty." 97 | err = fmt.Errorf("Empty credentials. " + errStatement) 98 | log.Error(err, errStatement, "username empty", username == "", "password empty", password == "") 99 | return 100 | } 101 | return 102 | } 103 | -------------------------------------------------------------------------------- /controllers/ndbserver_controller_helpers.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | 6 | ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1" 7 | "github.com/nutanix-cloud-native/ndb-operator/common" 8 | "github.com/nutanix-cloud-native/ndb-operator/common/util" 9 | "github.com/nutanix-cloud-native/ndb-operator/ndb_api" 10 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 11 | "sigs.k8s.io/controller-runtime/pkg/log" 12 | ) 13 | 14 | // Fetches all databases from the NDB API and converts them to 15 | // NDBServerDatabaseInfo type object (to be consumed by the NDBServer CR) 16 | func getNDBServerDatabasesInfo(ctx context.Context, ndbClient *ndb_client.NDBClient) (databases []ndbv1alpha1.NDBServerDatabaseInfo, err error) { 17 | log := log.FromContext(ctx) 18 | log.Info("Fetching and converting databases from NDB") 19 | databasesResponse, err := ndb_api.GetAllDatabases(ctx, ndbClient) 20 | if err != nil { 21 | log.Error(err, "NDB API error while fetching databases") 22 | return 23 | } 24 | clonesResponse, err := ndb_api.GetAllClones(ctx, ndbClient) 25 | if err != nil { 26 | log.Error(err, "NDB API error while fetching clones") 27 | return 28 | } 29 | allDbs := append(databasesResponse, clonesResponse...) 30 | 31 | databases = make([]ndbv1alpha1.NDBServerDatabaseInfo, len(allDbs)) 32 | for i, db := range allDbs { 33 | databaseInfo := ndbv1alpha1.NDBServerDatabaseInfo{ 34 | Name: db.Name, 35 | Id: db.Id, 36 | Status: db.Status, 37 | TimeMachineId: db.TimeMachineId, 38 | Type: db.Type, 39 | } 40 | if len(db.DatabaseNodes) > 0 { 41 | databaseInfo.DBServerId = db.DatabaseNodes[0].DatabaseServerId 42 | if len(db.DatabaseNodes[0].DbServer.IPAddresses) > 0 { 43 | databaseInfo.IPAddress = db.DatabaseNodes[0].DbServer.IPAddresses[0] 44 | } 45 | } 46 | databases[i] = databaseInfo 47 | } 48 | log.Info("Returning from ndbserver_controller_helpers.getNDBServerDatabasesInfo") 49 | return 50 | } 51 | 52 | // Returns the NDBServerStatus after performing the following steps: 53 | // 1. Checks and fetch data if dbcounter is zero (we fetch data only when counter hits 0). 54 | // 2. TODO: Filter and set the required list of databases (we only want to store the databases managed by the operator). 55 | // 3. Update the counter value. 56 | func getNDBServerStatus(ctx context.Context, status *ndbv1alpha1.NDBServerStatus, ndbClient *ndb_client.NDBClient) *ndbv1alpha1.NDBServerStatus { 57 | log := log.FromContext(ctx) 58 | log.Info("Entered ndbserver_controller_helpers.getNDBServerStatus") 59 | 60 | dbCounter := status.ReconcileCounter.Database 61 | // 1. Fetch dbs only if dbcounter is 0 62 | if dbCounter == 0 { 63 | log.Info("DbCounter 0, fetching databases (NDBServerDatabaseInfo)") 64 | databases, err := getNDBServerDatabasesInfo(ctx, ndbClient) 65 | if err != nil { 66 | log.Error(err, "Error occurred while fetching databases (NDBServerDatabaseInfo)") 67 | status.Status = common.NDB_CR_STATUS_ERROR 68 | } else { 69 | /* 2. TODO: Perform filtration on the databases associated with this NDB CR 70 | databaseList := &ndbv1alpha1.DatabaseList{} 71 | err = r.List(ctx, databaseList) // Also, we'll need to filter the dbs which are solely managed by THIS NDB CR. => Manual filter OR List Opts 72 | if err != nil { 73 | status.Status = common.NDB_CR_STATUS_ERROR 74 | } 75 | log.Info(util.ToString(databaseList)) 76 | filteredDBs := util.Filter(databaseList.Items, FILTER_FUNC ) 77 | */ 78 | status.Databases, err = util.CreateMapForKey(databases, "Id") 79 | if err != nil { 80 | log.Error(err, "Error occurred while creating dbId-db map") 81 | status.Status = common.NDB_CR_STATUS_ERROR 82 | } 83 | } 84 | } 85 | 86 | // 3. Update counters 87 | status.ReconcileCounter = ndbv1alpha1.ReconcileCounter{ 88 | Database: (dbCounter + 1) % common.NDB_RECONCILE_DATABASE_COUNTER, 89 | } 90 | log.Info("Returning from ndbserver_controller_helpers.getNDBServerStatus") 91 | return status 92 | } 93 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | GENERATED by operator-sdk 19 | */ 20 | 21 | package controllers 22 | 23 | import ( 24 | "path/filepath" 25 | "testing" 26 | 27 | . "github.com/onsi/ginkgo" 28 | . "github.com/onsi/gomega" 29 | "k8s.io/client-go/kubernetes/scheme" 30 | "k8s.io/client-go/rest" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | "sigs.k8s.io/controller-runtime/pkg/envtest" 33 | logf "sigs.k8s.io/controller-runtime/pkg/log" 34 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 35 | 36 | ndbv1alpha1 "github.com/nutanix-cloud-native/ndb-operator/api/v1alpha1" 37 | //+kubebuilder:scaffold:imports 38 | ) 39 | 40 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 41 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 42 | 43 | var cfg *rest.Config 44 | var k8sClient client.Client 45 | var testEnv *envtest.Environment 46 | 47 | func TestAPIs(t *testing.T) { 48 | RegisterFailHandler(Fail) 49 | 50 | var r []Reporter 51 | RunSpecsWithDefaultAndCustomReporters(t, "Controller Suite", r) 52 | 53 | } 54 | 55 | var _ = BeforeSuite(func() { 56 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 57 | 58 | By("bootstrapping test environment") 59 | testEnv = &envtest.Environment{ 60 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 61 | ErrorIfCRDPathMissing: true, 62 | } 63 | 64 | var err error 65 | // cfg is defined in this file globally. 66 | cfg, err = testEnv.Start() 67 | Expect(err).NotTo(HaveOccurred()) 68 | Expect(cfg).NotTo(BeNil()) 69 | 70 | err = ndbv1alpha1.AddToScheme(scheme.Scheme) 71 | Expect(err).NotTo(HaveOccurred()) 72 | 73 | //+kubebuilder:scaffold:scheme 74 | 75 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 76 | Expect(err).NotTo(HaveOccurred()) 77 | Expect(k8sClient).NotTo(BeNil()) 78 | 79 | }, 60) 80 | 81 | var _ = AfterSuite(func() { 82 | By("tearing down the test environment") 83 | err := testEnv.Stop() 84 | Expect(err).NotTo(HaveOccurred()) 85 | }) 86 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/nutanix-cloud-native/ndb-operator 2 | 3 | go 1.21.7 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 7 | github.com/joho/godotenv v1.5.1 8 | github.com/onsi/ginkgo v1.16.5 9 | github.com/onsi/ginkgo/v2 v2.9.5 10 | github.com/onsi/gomega v1.27.7 11 | github.com/stretchr/testify v1.8.2 12 | go.uber.org/zap v1.24.0 13 | k8s.io/api v0.28.3 14 | k8s.io/apimachinery v0.28.3 15 | k8s.io/client-go v0.28.3 16 | sigs.k8s.io/controller-runtime v0.15.1 17 | sigs.k8s.io/yaml v1.3.0 18 | ) 19 | 20 | require ( 21 | github.com/beorn7/perks v1.0.1 // indirect 22 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/emicklei/go-restful/v3 v3.9.0 // indirect 25 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 26 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 27 | github.com/fsnotify/fsnotify v1.6.0 // indirect 28 | github.com/go-logr/logr v1.2.4 // indirect 29 | github.com/go-logr/zapr v1.2.4 // indirect 30 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 31 | github.com/go-openapi/jsonreference v0.20.2 // indirect 32 | github.com/go-openapi/swag v0.22.3 // indirect 33 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 34 | github.com/gogo/protobuf v1.3.2 // indirect 35 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 36 | github.com/golang/protobuf v1.5.3 // indirect 37 | github.com/google/gnostic-models v0.6.8 // indirect 38 | github.com/google/go-cmp v0.5.9 // indirect 39 | github.com/google/gofuzz v1.2.0 // indirect 40 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect 41 | github.com/imdario/mergo v0.3.12 // indirect 42 | github.com/josharian/intern v1.0.0 // indirect 43 | github.com/json-iterator/go v1.1.12 // indirect 44 | github.com/mailru/easyjson v0.7.7 // indirect 45 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 47 | github.com/modern-go/reflect2 v1.0.2 // indirect 48 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 49 | github.com/nxadm/tail v1.4.8 // indirect 50 | github.com/pkg/errors v0.9.1 // indirect 51 | github.com/pmezard/go-difflib v1.0.0 // indirect 52 | github.com/prometheus/client_golang v1.15.1 // indirect 53 | github.com/prometheus/client_model v0.4.0 // indirect 54 | github.com/prometheus/common v0.42.0 // indirect 55 | github.com/prometheus/procfs v0.9.0 // indirect 56 | github.com/spf13/pflag v1.0.5 // indirect 57 | github.com/stretchr/objx v0.5.0 // indirect 58 | go.uber.org/atomic v1.7.0 // indirect 59 | go.uber.org/multierr v1.6.0 // indirect 60 | golang.org/x/net v0.17.0 // indirect 61 | golang.org/x/oauth2 v0.8.0 // indirect 62 | golang.org/x/sys v0.13.0 // indirect 63 | golang.org/x/term v0.13.0 // indirect 64 | golang.org/x/text v0.13.0 // indirect 65 | golang.org/x/time v0.3.0 // indirect 66 | golang.org/x/tools v0.9.1 // indirect 67 | gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect 68 | google.golang.org/appengine v1.6.7 // indirect 69 | google.golang.org/protobuf v1.33.0 // indirect 70 | gopkg.in/inf.v0 v0.9.1 // indirect 71 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect 72 | gopkg.in/yaml.v2 v2.4.0 // indirect 73 | gopkg.in/yaml.v3 v3.0.1 // indirect 74 | k8s.io/apiextensions-apiserver v0.27.2 // indirect 75 | k8s.io/component-base v0.27.2 // indirect 76 | k8s.io/klog/v2 v2.100.1 // indirect 77 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 78 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 79 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 80 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 81 | ) 82 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /ndb_api/auth.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 24 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 25 | ) 26 | 27 | // Validates the auth credentials against the 'auth/validate' endpoint 28 | // Returns the response along with an error (if any) 29 | func AuthValidate(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface) (authValidateResponse AuthValidateResponse, err error) { 30 | log := ctrllog.FromContext(ctx) 31 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, "auth/validate", nil, &authValidateResponse); err != nil { 32 | log.Error(err, "Error in AuthValidate") 33 | return 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /ndb_api/auth_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type AuthValidateResponse struct { 20 | Status string `json:"status"` 21 | Message string `json:"message"` 22 | } 23 | -------------------------------------------------------------------------------- /ndb_api/auth_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "errors" 23 | "io" 24 | "net/http" 25 | "reflect" 26 | "testing" 27 | 28 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 29 | ) 30 | 31 | func TestAuthValidate(t *testing.T) { 32 | type args struct { 33 | ctx context.Context 34 | ndbClient ndb_client.NDBClientHTTPInterface 35 | } 36 | // Mocks of the NDB Client interface 37 | mockNDBClient := &MockNDBClientHTTPInterface{} 38 | mockNDBClient.On("NewRequest", http.MethodGet, "auth/validate", nil).Once().Return(nil, errors.New("mock-error-new-request")) 39 | 40 | req := &http.Request{Method: http.MethodGet} 41 | res := &http.Response{ 42 | StatusCode: http.StatusOK, 43 | Body: io.NopCloser(bytes.NewBufferString(`{"status":"TEST-STATUS", "message":"TEST-MESSAGE"}`)), 44 | } 45 | mockNDBClient.On("NewRequest", http.MethodGet, "auth/validate", nil).Once().Return(req, nil) 46 | mockNDBClient.On("Do", req).Once().Return(res, nil) 47 | 48 | tests := []struct { 49 | name string 50 | args args 51 | wantAuthValidateResponse AuthValidateResponse 52 | wantErr bool 53 | }{ 54 | { 55 | name: "Test 1: AuthValidate returns an error when sendRequest", 56 | args: args{ 57 | ctx: context.TODO(), 58 | ndbClient: mockNDBClient, 59 | }, 60 | wantAuthValidateResponse: AuthValidateResponse{}, 61 | wantErr: true, 62 | }, 63 | { 64 | name: "Test 2: AuthValidate returns an auth response when sendRequest does not return an error", 65 | args: args{ 66 | ctx: context.TODO(), 67 | ndbClient: mockNDBClient, 68 | }, 69 | wantAuthValidateResponse: AuthValidateResponse{ 70 | Status: "TEST-STATUS", 71 | Message: "TEST-MESSAGE", 72 | }, 73 | wantErr: false, 74 | }, 75 | } 76 | for _, tt := range tests { 77 | t.Run(tt.name, func(t *testing.T) { 78 | gotAuthValidateResponse, err := AuthValidate(tt.args.ctx, tt.args.ndbClient) 79 | if (err != nil) != tt.wantErr { 80 | t.Errorf("AuthValidate() error = %v, wantErr %v", err, tt.wantErr) 81 | return 82 | } 83 | if !reflect.DeepEqual(gotAuthValidateResponse, tt.wantAuthValidateResponse) { 84 | t.Errorf("AuthValidate() = %v, want %v", gotAuthValidateResponse, tt.wantAuthValidateResponse) 85 | } 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /ndb_api/clone.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package ndb_api 15 | 16 | import ( 17 | "context" 18 | "errors" 19 | "fmt" 20 | "net/http" 21 | 22 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 23 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 24 | ) 25 | 26 | // Fetches all the clones on the NDB instance and retutns a slice of the databases 27 | func GetAllClones(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface) (clones []DatabaseResponse, err error) { 28 | log := ctrllog.FromContext(ctx) 29 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, "clones?detailed=true", nil, &clones); err != nil { 30 | log.Error(err, "Error in GetAllClones") 31 | return 32 | } 33 | return 34 | } 35 | 36 | // Provisions a clone based on the clone provisioning request 37 | // Returns the task info summary response for the operation 38 | func ProvisionClone(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, req *DatabaseCloneRequest) (task *TaskInfoSummaryResponse, err error) { 39 | log := ctrllog.FromContext(ctx) 40 | if req.TimeMachineId == "" { 41 | err = errors.New("empty timeMachineId") 42 | log.Error(err, "Received empty timeMachineId in request") 43 | return 44 | } 45 | cloneEndpoint := "tms/" + req.TimeMachineId + "/clones" 46 | if _, err = sendRequest(ctx, ndbClient, http.MethodPost, cloneEndpoint, req, &task); err != nil { 47 | log.Error(err, "Error in ProvisionClone") 48 | return 49 | } 50 | return 51 | } 52 | 53 | // Fetches clone by id 54 | func GetCloneById(ctx context.Context, ndbClient *ndb_client.NDBClient, id string) (clone *DatabaseResponse, err error) { 55 | log := ctrllog.FromContext(ctx) 56 | // Checking if id is empty, this is necessary otherwise the request becomes a call to get all clones (/clones) 57 | if id == "" { 58 | err = fmt.Errorf("clone id is empty") 59 | log.Error(err, "no clone id provided") 60 | return 61 | } 62 | getCloneIdPath := fmt.Sprintf("clones/%s", id) 63 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getCloneIdPath, nil, &clone); err != nil { 64 | log.Error(err, "Error in GetCloneById") 65 | return 66 | } 67 | return 68 | } 69 | 70 | // Deprovisions a clone instance given a clone id 71 | // Returns the task info summary response for the operation 72 | func DeprovisionClone(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, id string, req *CloneDeprovisionRequest) (task *TaskInfoSummaryResponse, err error) { 73 | log := ctrllog.FromContext(ctx) 74 | if id == "" { 75 | err = fmt.Errorf("id is empty") 76 | log.Error(err, "no clone id provided") 77 | return 78 | } 79 | if _, err = sendRequest(ctx, ndbClient, http.MethodDelete, "clones/"+id, req, &task); err != nil { 80 | log.Error(err, "Error in DeprovisionClone") 81 | return 82 | } 83 | return 84 | } 85 | -------------------------------------------------------------------------------- /ndb_api/clone_helpers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package ndb_api 15 | 16 | import ( 17 | "bytes" 18 | "context" 19 | "errors" 20 | "io" 21 | "net/http" 22 | "reflect" 23 | "testing" 24 | 25 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 26 | ) 27 | 28 | func TestGenerateCloningRequest(t *testing.T) { 29 | type args struct { 30 | ctx context.Context 31 | ndb_client ndb_client.NDBClientHTTPInterface 32 | database DatabaseInterface 33 | reqData map[string]interface{} 34 | } 35 | // Mocks of the interfaces 36 | mockNDBClient := &MockNDBClientHTTPInterface{} 37 | mockDatabase := &MockDatabaseInterface{} 38 | 39 | // Common stubs for all the test cases 40 | mockDatabase.On("GetName").Return("test-clone-name") 41 | mockDatabase.On("IsClone").Return(true) 42 | // Stubs for Test 1 43 | mockDatabase.On("GetCloneSourceDBId").Return("").Once() 44 | 45 | // Stubs for Test 2 46 | mockDatabase.On("GetCloneSourceDBId").Return("test-sourcedb-id") 47 | reqGetDatabaseById := &http.Request{} 48 | resGetDatabaseById := &http.Response{ 49 | StatusCode: http.StatusOK, 50 | Body: io.NopCloser(bytes.NewBufferString(`{"id":"test-sourcedb-id"}`)), 51 | } 52 | mockNDBClient.On("NewRequest", http.MethodGet, "databases/test-sourcedb-id?detailed=true", nil).Return(reqGetDatabaseById, nil) 53 | mockNDBClient.On("Do", reqGetDatabaseById).Return(resGetDatabaseById, nil) 54 | mockDatabase.On("GetProfileResolvers").Once().Return(ProfileResolvers{}) 55 | mockNDBClient.On("NewRequest", http.MethodGet, "profiles", nil).Return(nil, errors.New("profiles-error")).Once() 56 | 57 | tests := []struct { 58 | name string 59 | args args 60 | wantRequestBody *DatabaseCloneRequest 61 | wantErr bool 62 | }{ 63 | { 64 | name: "Test 1: GenerateCloningRequest returns an error if source database is not found", 65 | args: args{ 66 | ctx: context.TODO(), 67 | ndb_client: mockNDBClient, 68 | database: mockDatabase, 69 | reqData: make(map[string]interface{}), 70 | }, 71 | wantRequestBody: nil, 72 | wantErr: true, 73 | }, 74 | { 75 | name: "Test 2: GenerateCloningRequest returns an error when ResolveProfiles returns an error", 76 | args: args{ 77 | ctx: context.TODO(), 78 | ndb_client: mockNDBClient, 79 | database: mockDatabase, 80 | reqData: make(map[string]interface{}), 81 | }, 82 | wantRequestBody: nil, 83 | wantErr: true, 84 | }, 85 | } 86 | for _, tt := range tests { 87 | t.Run(tt.name, func(t *testing.T) { 88 | gotRequestBody, err := GenerateCloningRequest(tt.args.ctx, tt.args.ndb_client, tt.args.database, tt.args.reqData) 89 | if (err != nil) != tt.wantErr { 90 | t.Errorf("GenerateCloningRequest() error = %v, wantErr %v", err, tt.wantErr) 91 | return 92 | } 93 | if !reflect.DeepEqual(gotRequestBody, tt.wantRequestBody) { 94 | t.Errorf("GenerateCloningRequest() = %v, want %v", gotRequestBody, tt.wantRequestBody) 95 | } 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /ndb_api/clone_request_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package ndb_api 15 | 16 | type CloneDeprovisionRequest struct { 17 | SoftRemove bool `json:"softRemove"` 18 | Remove bool `json:"remove"` 19 | Delete bool `json:"delete"` 20 | Forced bool `json:"forced"` 21 | DeleteDataDrives bool `json:"deleteDataDrives"` 22 | DeleteLogicalCluster bool `json:"deleteLogicalCluster"` 23 | RemoveLogicalCluster bool `json:"removeLogicalCluster"` 24 | DeleteTimeMachine bool `json:"deleteTimeMachine"` 25 | } 26 | 27 | type DatabaseCloneRequest struct { 28 | Name string `json:"name"` 29 | Description string `json:"description"` 30 | CreateDbServer bool `json:"createDbserver"` 31 | Clustered bool `json:"clustered"` 32 | NxClusterId string `json:"nxClusterId"` 33 | SSHPublicKey string `json:"sshPublicKey,omitempty"` 34 | DbServerId string `json:"dbserverId,omitempty"` 35 | DbServerClusterId string `json:"dbserverClusterId,omitempty"` 36 | DbserverLogicalClusterId string `json:"dbserverLogicalClusterId,omitempty"` 37 | TimeMachineId string `json:"timeMachineId"` 38 | SnapshotId string `json:"snapshotId,omitempty"` 39 | UserPitrTimestamp string `json:"userPitrTimestamp,omitempty"` 40 | TimeZone string `json:"timeZone"` 41 | LatestSnapshot bool `json:"latestSnapshot"` 42 | NodeCount int `json:"nodeCount"` 43 | Nodes []Node `json:"nodes"` 44 | ActionArguments []ActionArgument `json:"actionArguments"` 45 | Tags interface{} `json:"tags"` 46 | LcmConfig *LcmConfig `json:"lcmConfig,omitempty"` 47 | VmPassword string `json:"vmPassword"` 48 | ComputeProfileId string `json:"computeProfileId"` 49 | NetworkProfileId string `json:"networkProfileId"` 50 | DatabaseParameterProfileId string `json:"databaseParameterProfileId"` 51 | } 52 | 53 | type LcmConfig struct { 54 | DatabaseLCMConfig DatabaseLCMConfig `json:"databaseLCMConfig,omitempty"` 55 | } 56 | 57 | type DatabaseLCMConfig struct { 58 | ExpiryDetails ExpiryDetails `json:"expiryDetails,omitempty"` 59 | RefreshDetails RefreshDetails `json:"refreshDetails,omitempty"` 60 | } 61 | 62 | type ExpiryDetails struct { 63 | ExpireInDays string `json:"expireInDays"` 64 | ExpiryDateTimezone string `json:"expiryDateTimezone"` 65 | DeleteDatabase string `json:"deleteDatabase"` 66 | } 67 | 68 | type RefreshDetails struct { 69 | RefreshInDays string `json:"refreshInDays"` 70 | RefreshTime string `json:"refreshTime"` 71 | RefreshDateTimezone string `json:"refreshDateTimezone"` 72 | } 73 | -------------------------------------------------------------------------------- /ndb_api/common_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type TaskInfoSummaryResponse struct { 20 | Name string `json:"name"` 21 | WorkId string `json:"workId"` 22 | OperationId string `json:"operationId"` 23 | DbServerId string `json:"dbserverId"` 24 | Message string `json:"messgae"` 25 | EntityId string `json:"entityId"` 26 | EntityName string `json:"entityName"` 27 | EntityType string `json:"entityType"` 28 | Status string `json:"status"` 29 | AssociatedOperations []TaskInfoSummaryResponse `json:"associatedOperations"` 30 | DependencyReport interface{} `json:"dependencyReport"` 31 | } 32 | -------------------------------------------------------------------------------- /ndb_api/common_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type DatabaseNode struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | DatabaseServerId string `json:"dbServerId"` 23 | DbServer DatabaseServer `json:"dbserver"` 24 | } 25 | 26 | type DatabaseServer struct { 27 | Id string `json:"id"` 28 | Name string `json:"name"` 29 | IPAddresses []string `json:"ipAddresses"` 30 | NxClusterId string `json:"nxClusterId"` 31 | } 32 | 33 | type TimeMachineInfo struct { 34 | Name string `json:"name"` 35 | Description string `json:"description"` 36 | SlaId string `json:"slaId"` 37 | Schedule Schedule `json:"schedule"` 38 | Tags []string `json:"tags"` 39 | AutoTuneLogDrive bool `json:"autoTuneLogDrive"` 40 | } 41 | 42 | type Schedule struct { 43 | SnapshotTimeOfDay SnapshotTimeOfDay `json:"snapshotTimeOfDay"` 44 | ContinuousSchedule ContinuousSchedule `json:"continuousSchedule"` 45 | WeeklySchedule WeeklySchedule `json:"weeklySchedule"` 46 | MonthlySchedule MonthlySchedule `json:"monthlySchedule"` 47 | QuarterlySchedule QuarterlySchedule `json:"quartelySchedule"` 48 | YearlySchedule YearlySchedule `json:"yearlySchedule"` 49 | } 50 | 51 | type SnapshotTimeOfDay struct { 52 | Hours int `json:"hours"` 53 | Minutes int `json:"minutes"` 54 | Seconds int `json:"seconds"` 55 | } 56 | 57 | type ContinuousSchedule struct { 58 | Enabled bool `json:"enabled"` 59 | LogBackupInterval int `json:"logBackupInterval"` 60 | SnapshotsPerDay int `json:"snapshotsPerDay"` 61 | } 62 | 63 | type WeeklySchedule struct { 64 | Enabled bool `json:"enabled"` 65 | DayOfWeek string `json:"dayOfWeek"` 66 | } 67 | 68 | type MonthlySchedule struct { 69 | Enabled bool `json:"enabled"` 70 | DayOfMonth int `json:"dayOfMonth"` 71 | } 72 | 73 | type QuarterlySchedule struct { 74 | Enabled bool `json:"enabled"` 75 | StartMonth string `json:"startMonth"` 76 | DayOfMonth int `json:"dayOfMonth"` 77 | } 78 | 79 | type YearlySchedule struct { 80 | Enabled bool `json:"enabled"` 81 | DayOfMonth int `json:"dayOfMonth"` 82 | Month string `json:"month"` 83 | } 84 | 85 | type ActionArgument struct { 86 | Name string `json:"name"` 87 | Value string `json:"value"` 88 | } 89 | 90 | type Node struct { 91 | VmName string `json:"vmName"` 92 | ComputeProfileId string `json:"computeProfileId,omitempty"` 93 | NetworkProfileId string `json:"networkProfileId,omitempty"` 94 | NewDbServerTimeZone string `json:"newDbServerTimeZone,omitempty"` 95 | NxClusterId string `json:"nxClusterId,omitempty"` 96 | Properties []string `json:"properties"` 97 | } 98 | 99 | type Property struct { 100 | Name string `json:"name"` 101 | Value string `json:"value"` 102 | Description string `json:"description"` 103 | } 104 | -------------------------------------------------------------------------------- /ndb_api/db.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | 24 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 25 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 26 | ) 27 | 28 | // Fetches all the databases on the NDB instance and retutns a slice of the databases 29 | func GetAllDatabases(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface) (databases []DatabaseResponse, err error) { 30 | log := ctrllog.FromContext(ctx) 31 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, "databases?detailed=true", nil, &databases); err != nil { 32 | log.Error(err, "Error in GetAllDatabases") 33 | return 34 | } 35 | return 36 | } 37 | 38 | // Fetches and returns a database by an Id 39 | func GetDatabaseById(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, id string) (database *DatabaseResponse, err error) { 40 | log := ctrllog.FromContext(ctx) 41 | // Checking if id is empty, this is necessary otherwise the request becomes a call to get all databases (/databases) 42 | if id == "" { 43 | err = fmt.Errorf("database id is empty") 44 | log.Error(err, "no database id provided") 45 | return 46 | } 47 | getDbDetailedPath := fmt.Sprintf("databases/%s?detailed=true", id) 48 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getDbDetailedPath, nil, &database); err != nil { 49 | log.Error(err, "Error in GetDatabaseById") 50 | return 51 | } 52 | return 53 | } 54 | 55 | // Fetches and returns a database by name 56 | func GetDatabaseByName(ctx context.Context, ndbClient *ndb_client.NDBClient, name string) (database *DatabaseResponse, err error) { 57 | log := ctrllog.FromContext(ctx) 58 | // Checking if id is empty, this is necessary otherwise the request becomes a call to get all databases (/databases) 59 | if name == "" { 60 | err = fmt.Errorf("database name is empty") 61 | log.Error(err, "no database name provided") 62 | return 63 | } 64 | getDbDetailedPath := fmt.Sprintf("databases/%s?value-type=name&detailed=true", name) 65 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getDbDetailedPath, nil, &database); err != nil { 66 | log.Error(err, "Error in GetDatabaseByName") 67 | return 68 | } 69 | return 70 | } 71 | 72 | // Provisions a database instance based on the database provisioning request 73 | // Returns the task info summary response for the operation 74 | func ProvisionDatabase(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, req *DatabaseProvisionRequest) (task *TaskInfoSummaryResponse, err error) { 75 | log := ctrllog.FromContext(ctx) 76 | if _, err = sendRequest(ctx, ndbClient, http.MethodPost, "databases/provision", req, &task); err != nil { 77 | log.Error(err, "Error in ProvisionDatabase") 78 | return 79 | } 80 | return 81 | } 82 | 83 | // Deprovisions a database instance given a database id 84 | // Returns the task info summary response for the operation 85 | func DeprovisionDatabase(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, id string, req *DatabaseDeprovisionRequest) (task *TaskInfoSummaryResponse, err error) { 86 | log := ctrllog.FromContext(ctx) 87 | if id == "" { 88 | err = fmt.Errorf("id is empty") 89 | log.Error(err, "no database id provided") 90 | return 91 | } 92 | if _, err = sendRequest(ctx, ndbClient, http.MethodDelete, "databases/"+id, req, &task); err != nil { 93 | log.Error(err, "Error in DeprovisionDatabase") 94 | return 95 | } 96 | return 97 | } 98 | -------------------------------------------------------------------------------- /ndb_api/db_request_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type DatabaseProvisionRequest struct { 20 | DatabaseType string `json:"databaseType"` 21 | Name string `json:"name"` 22 | DatabaseDescription string `json:"databaseDescription"` 23 | SoftwareProfileId string `json:"softwareProfileId"` 24 | SoftwareProfileVersionId string `json:"softwareProfileVersionId"` 25 | ComputeProfileId string `json:"computeProfileId"` 26 | NetworkProfileId string `json:"networkProfileId"` 27 | DbParameterProfileId string `json:"dbParameterProfileId"` 28 | NewDbServerTimeZone string `json:"newDbServerTimeZone"` 29 | CreateDbServer bool `json:"createDbserver"` 30 | NodeCount int `json:"nodeCount"` 31 | NxClusterId string `json:"nxClusterId"` 32 | SSHPublicKey string `json:"sshPublicKey,omitempty"` 33 | Clustered bool `json:"clustered"` 34 | AutoTuneStagingDrive bool `json:"autoTuneStagingDrive"` 35 | TimeMachineInfo TimeMachineInfo `json:"timeMachineInfo"` 36 | ActionArguments []ActionArgument `json:"actionArguments"` 37 | Nodes []Node `json:"nodes"` 38 | DatabaseName string `json:"databaseName,omitempty"` 39 | } 40 | 41 | type DatabaseDeprovisionRequest struct { 42 | Delete bool `json:"delete"` 43 | Remove bool `json:"remove"` 44 | SoftRemove bool `json:"softRemove"` 45 | Forced bool `json:"forced"` 46 | DeleteTimeMachine bool `json:"deleteTimeMachine"` 47 | DeleteLogicalCluster bool `json:"deleteLogicalCluster"` 48 | } 49 | -------------------------------------------------------------------------------- /ndb_api/db_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type DatabaseResponse struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | Status string `json:"status"` 23 | DatabaseNodes []DatabaseNode `json:"databaseNodes"` 24 | Properties []Property `json:"properties"` 25 | TimeMachineId string `json:"timeMachineId"` 26 | Type string `json:"type"` 27 | } 28 | -------------------------------------------------------------------------------- /ndb_api/dbserver.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | 24 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 25 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 26 | ) 27 | 28 | // Deprovisions a database server vm given a server id 29 | // Returns the task info summary response for the operation 30 | func DeprovisionDatabaseServer(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, id string, req *DatabaseServerDeprovisionRequest) (task *TaskInfoSummaryResponse, err error) { 31 | log := ctrllog.FromContext(ctx) 32 | if id == "" { 33 | err = fmt.Errorf("id is empty") 34 | log.Error(err, "no database server id provided") 35 | return 36 | } 37 | if _, err = sendRequest(ctx, ndbClient, http.MethodDelete, "dbservers/"+id, req, &task); err != nil { 38 | log.Error(err, "Error in DeprovisionDatabaseServer") 39 | return 40 | } 41 | return 42 | } 43 | -------------------------------------------------------------------------------- /ndb_api/dbserver_helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | // Returns a request to delete a database server vm 20 | func GenerateDeprovisionDatabaseServerRequest() (req *DatabaseServerDeprovisionRequest) { 21 | req = &DatabaseServerDeprovisionRequest{ 22 | Delete: true, 23 | Remove: false, 24 | SoftRemove: false, 25 | DeleteVgs: true, 26 | DeleteVmSnapshots: true, 27 | } 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /ndb_api/dbserver_request_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type DatabaseServerDeprovisionRequest struct { 20 | Delete bool `json:"delete"` 21 | Remove bool `json:"remove"` 22 | SoftRemove bool `json:"softRemove"` 23 | DeleteVgs bool `json:"deleteVgs"` 24 | DeleteVmSnapshots bool `json:"deleteVmSnapshots"` 25 | } 26 | -------------------------------------------------------------------------------- /ndb_api/dbserver_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "bytes" 21 | "context" 22 | "errors" 23 | "io" 24 | "net/http" 25 | "reflect" 26 | "testing" 27 | 28 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 29 | ) 30 | 31 | func TestDeprovisionDatabaseServer(t *testing.T) { 32 | type args struct { 33 | ctx context.Context 34 | ndbClient ndb_client.NDBClientHTTPInterface 35 | id string 36 | req *DatabaseServerDeprovisionRequest 37 | } 38 | // Mocks of the NDB Client interface 39 | mockNDBClient := &MockNDBClientHTTPInterface{} 40 | 41 | mockNDBClient.On("NewRequest", http.MethodDelete, "dbservers/dbserverid", GenerateDeprovisionDatabaseServerRequest()).Once().Return(nil, errors.New("mock-error-new-request")) 42 | 43 | req := &http.Request{} 44 | res := &http.Response{ 45 | StatusCode: http.StatusOK, 46 | Body: io.NopCloser(bytes.NewBufferString(`{"name":"test-name", "entityId":"test-id"}`)), 47 | } 48 | mockNDBClient.On("NewRequest", http.MethodDelete, "dbservers/dbserverid", GenerateDeprovisionDatabaseServerRequest()).Once().Return(req, nil) 49 | mockNDBClient.On("Do", req).Once().Return(res, nil) 50 | tests := []struct { 51 | name string 52 | args args 53 | wantTask *TaskInfoSummaryResponse 54 | wantErr bool 55 | }{ 56 | { 57 | name: "Test 1: DeprovisionDatabaseServer returns an error when a request with empty id is passed to it", 58 | args: args{ 59 | ctx: context.TODO(), 60 | ndbClient: mockNDBClient, 61 | id: "", 62 | req: nil, 63 | }, 64 | wantTask: nil, 65 | wantErr: true, 66 | }, 67 | { 68 | name: "Test 2: DeprovisionDatabaseServer returns an error when sendRequest returns an error", 69 | args: args{ 70 | ctx: context.TODO(), 71 | ndbClient: mockNDBClient, 72 | id: "dbserverid", 73 | req: GenerateDeprovisionDatabaseServerRequest(), 74 | }, 75 | wantTask: nil, 76 | wantErr: true, 77 | }, 78 | { 79 | name: "Test 3: DeprovisionDatabaseServer returns a TaskInfoSummary response when sendRequest returns a response without error", 80 | args: args{ 81 | ctx: context.TODO(), 82 | ndbClient: mockNDBClient, 83 | id: "dbserverid", 84 | req: GenerateDeprovisionDatabaseServerRequest(), 85 | }, 86 | wantTask: &TaskInfoSummaryResponse{ 87 | Name: "test-name", 88 | EntityId: "test-id", 89 | }, 90 | wantErr: false, 91 | }, 92 | } 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | gotTask, err := DeprovisionDatabaseServer(tt.args.ctx, tt.args.ndbClient, tt.args.id, tt.args.req) 96 | if (err != nil) != tt.wantErr { 97 | t.Errorf("DeprovisionDatabaseServer() error = %v, wantErr %v", err, tt.wantErr) 98 | return 99 | } 100 | if !reflect.DeepEqual(gotTask, tt.wantTask) { 101 | t.Errorf("DeprovisionDatabaseServer() = %v, want %v", gotTask, tt.wantTask) 102 | } 103 | }) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /ndb_api/interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | ) 22 | 23 | // External Interfaces 24 | // Used by other packages that make use of the ndb_api package 25 | // Implementations defined in the packages that use this package 26 | // For example - controller_adapters 27 | 28 | type ProfileResolver interface { 29 | Resolve(ctx context.Context, allProfiles []ProfileResponse, filter func(p ProfileResponse) bool) (profile ProfileResponse, err error) 30 | GetName() string 31 | GetId() string 32 | } 33 | 34 | type ProfileResolvers map[string]ProfileResolver 35 | 36 | type DatabaseInterface interface { 37 | IsClone() bool 38 | GetName() string 39 | GetDescription() string 40 | GetClusterId() string 41 | GetProfileResolvers() ProfileResolvers 42 | GetCredentialSecret() string 43 | GetTimeZone() string 44 | GetInstanceType() string 45 | GetInstanceDatabaseNames() string 46 | GetInstanceSize() int 47 | GetInstanceTMDetails() (string, string, string) 48 | GetTMScheduleForInstance() (Schedule, error) 49 | GetCloneSourceDBId() string 50 | GetCloneSnapshotId() string 51 | GetAdditionalArguments() map[string]string 52 | } 53 | 54 | // Internal Interfaces 55 | // Used internally within the ndb_api package 56 | 57 | type RequestAppender interface { 58 | // Function to add additional arguments to the Provisioning request 59 | appendProvisioningRequest(req *DatabaseProvisionRequest, database DatabaseInterface, reqData map[string]interface{}) (*DatabaseProvisionRequest, error) 60 | // Function to add additional arguments to the Cloning request 61 | appendCloningRequest(req *DatabaseCloneRequest, database DatabaseInterface, reqData map[string]interface{}) (*DatabaseCloneRequest, error) 62 | } 63 | 64 | // Implements RequestAppender 65 | type MSSQLRequestAppender struct{} 66 | 67 | // Implements RequestAppender 68 | type MongoDbRequestAppender struct{} 69 | 70 | // Implements RequestAppender 71 | type PostgresRequestAppender struct{} 72 | 73 | // Implements RequestAppender 74 | type MySqlRequestAppender struct{} 75 | -------------------------------------------------------------------------------- /ndb_api/operation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "net/http" 23 | 24 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 25 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 26 | ) 27 | 28 | // Fetches and returns a operation by an Id 29 | func GetOperationById(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, id string) (operation *OperationResponse, err error) { 30 | log := ctrllog.FromContext(ctx) 31 | // Checking if id is empty, this is necessary otherwise the request becomes a call to get all operations (/operations) 32 | if id == "" { 33 | err = fmt.Errorf("operation id is empty") 34 | log.Error(err, "no operation id provided") 35 | return 36 | } 37 | // ?display=true is added to fetch the operations even when it 38 | // is not yet created in the operation table on NDB. It causes 39 | // the operation details to be fetched from the WORK table instead. 40 | getOperationPath := fmt.Sprintf("operations/%s?display=true", id) 41 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getOperationPath, nil, &operation); err != nil { 42 | log.Error(err, "Error in GetOperationById") 43 | return 44 | } 45 | return 46 | } 47 | -------------------------------------------------------------------------------- /ndb_api/operation_helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | const OPERATION_STATUS_FAILED = "FAILED" 20 | const OPERATION_STATUS_PASSED = "PASSED" 21 | 22 | // Returns an operation status string 23 | func GetOperationStatus(o *OperationResponse) string { 24 | status := "" 25 | // Statuses on NDB 26 | // 2: STOPPED 27 | // 3: SUSPENDED 28 | // 4: FAILED 29 | // 5: PASSED 30 | switch o.Status { 31 | case "2", "3", "4": 32 | status = OPERATION_STATUS_FAILED 33 | case "5": 34 | status = OPERATION_STATUS_PASSED 35 | default: 36 | status = "" 37 | } 38 | return status 39 | } 40 | -------------------------------------------------------------------------------- /ndb_api/operation_helpers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "testing" 21 | 22 | "github.com/stretchr/testify/assert" 23 | ) 24 | 25 | func TestGetOperationStatus(t *testing.T) { 26 | // Test cases for GetOperationStatus 27 | testCases := []struct { 28 | status string 29 | expectedStatus string 30 | }{ 31 | {"2", OPERATION_STATUS_FAILED}, 32 | {"3", OPERATION_STATUS_FAILED}, 33 | {"4", OPERATION_STATUS_FAILED}, 34 | {"5", OPERATION_STATUS_PASSED}, 35 | {"invalidStatus", ""}, 36 | } 37 | 38 | for _, tc := range testCases { 39 | operationResponse := &OperationResponse{Status: tc.status} 40 | result := GetOperationStatus(operationResponse) 41 | assert.Equal(t, tc.expectedStatus, result) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ndb_api/operation_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type OperationResponse struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | Status string `json:"status"` 23 | PercentageComplete string `json:"percentageComplete"` 24 | Message string `json:"message"` 25 | StartTime string `json:"startTime"` 26 | EndTime string `json:"endTime"` 27 | } 28 | -------------------------------------------------------------------------------- /ndb_api/operation_test.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 13 | ) 14 | 15 | func TestGetOperationById(t *testing.T) { 16 | type args struct { 17 | ctx context.Context 18 | ndbClient ndb_client.NDBClientHTTPInterface 19 | id string 20 | } 21 | 22 | // Mocks of the NDB Client interface 23 | mockNDBClient := &MockNDBClientHTTPInterface{} 24 | 25 | mockNDBClient.On("NewRequest", http.MethodGet, "operations/operationid?display=true", nil).Once().Return(nil, errors.New("mock-error-new-request")) 26 | 27 | req := &http.Request{Method: http.MethodGet} 28 | res := &http.Response{ 29 | StatusCode: http.StatusOK, 30 | Body: io.NopCloser(bytes.NewBufferString(`{"id":"operationid", "name":"operation-1"}`)), 31 | } 32 | mockNDBClient.On("NewRequest", http.MethodGet, "operations/operationid?display=true", nil).Once().Return(req, nil) 33 | mockNDBClient.On("Do", req).Once().Return(res, nil) 34 | 35 | tests := []struct { 36 | name string 37 | args args 38 | wantOperation *OperationResponse 39 | wantErr bool 40 | }{ 41 | { 42 | name: "Test 1: GetOperationById returns an error when a request with empty id is passed to it", 43 | args: args{ 44 | ctx: context.TODO(), 45 | ndbClient: mockNDBClient, 46 | id: "", 47 | }, 48 | wantOperation: nil, 49 | wantErr: true, 50 | }, 51 | { 52 | name: "Test 2: GetOperationById returns an error when sendRequest returns an error", 53 | args: args{ 54 | ctx: context.TODO(), 55 | ndbClient: mockNDBClient, 56 | id: "operationid", 57 | }, 58 | wantOperation: nil, 59 | wantErr: true, 60 | }, 61 | { 62 | name: "Test 3: GetOperationById returns an OperationResponse when sendRequest returns a response without error", 63 | args: args{ 64 | ctx: context.TODO(), 65 | ndbClient: mockNDBClient, 66 | id: "operationid", 67 | }, 68 | wantOperation: &OperationResponse{Id: "operationid", Name: "operation-1"}, 69 | wantErr: false, 70 | }, 71 | } 72 | for _, tt := range tests { 73 | t.Run(tt.name, func(t *testing.T) { 74 | gotOperation, err := GetOperationById(tt.args.ctx, tt.args.ndbClient, tt.args.id) 75 | if (err != nil) != tt.wantErr { 76 | t.Errorf("GetOperationById() error = %v, wantErr %v", err, tt.wantErr) 77 | return 78 | } 79 | if !reflect.DeepEqual(gotOperation, tt.wantOperation) { 80 | t.Errorf("GetOperationById() = %v, want %v", gotOperation, tt.wantOperation) 81 | } 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /ndb_api/profile.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 24 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 25 | ) 26 | 27 | // Fetches and returns all the available profiles as a profile slice 28 | func GetAllProfiles(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface) (profiles []ProfileResponse, err error) { 29 | log := ctrllog.FromContext(ctx) 30 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, "profiles", nil, &profiles); err != nil { 31 | log.Error(err, "Error in GetAllProfiles") 32 | return 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /ndb_api/profile_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type ProfileResponse struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | Type string `json:"type"` 23 | EngineType string `json:"engineType"` 24 | LatestVersionId string `json:"latestVersionId"` 25 | Topology string `json:"topology"` 26 | SystemProfile bool `json:"systemProfile"` 27 | Status string `json:"status"` 28 | } 29 | -------------------------------------------------------------------------------- /ndb_api/profile_test.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 13 | ) 14 | 15 | func TestGetAllProfiles(t *testing.T) { 16 | type args struct { 17 | ctx context.Context 18 | ndbClient ndb_client.NDBClientHTTPInterface 19 | } 20 | 21 | // Mocks of the NDB Client interface 22 | mockNDBClient := &MockNDBClientHTTPInterface{} 23 | 24 | mockNDBClient.On("NewRequest", http.MethodGet, "profiles", nil).Once().Return(nil, errors.New("mock-error-new-request")) 25 | 26 | req := &http.Request{Method: http.MethodGet} 27 | res := &http.Response{ 28 | StatusCode: http.StatusOK, 29 | Body: io.NopCloser(bytes.NewBufferString( 30 | `[{"id":"1", "name":"profile-1"},{"id":"2", "name":"profile-2"}]`, 31 | )), 32 | } 33 | mockNDBClient.On("NewRequest", http.MethodGet, "profiles", nil).Once().Return(req, nil) 34 | mockNDBClient.On("Do", req).Once().Return(res, nil) 35 | 36 | tests := []struct { 37 | name string 38 | args args 39 | wantProfiles []ProfileResponse 40 | wantErr bool 41 | }{ 42 | { 43 | name: "Test 1: GetAllProfiles returns an error when sendRequest returns an error", 44 | args: args{ 45 | ctx: context.TODO(), 46 | ndbClient: mockNDBClient, 47 | }, 48 | wantProfiles: nil, 49 | wantErr: true, 50 | }, 51 | { 52 | name: "Test 2: GetAllProfiles returns a slice of profiles when sendRequest does not return an error", 53 | args: args{ 54 | ctx: context.TODO(), 55 | ndbClient: mockNDBClient, 56 | }, 57 | wantProfiles: []ProfileResponse{ 58 | { 59 | Id: "1", 60 | Name: "profile-1", 61 | }, 62 | { 63 | Id: "2", 64 | Name: "profile-2", 65 | }, 66 | }, 67 | wantErr: false, 68 | }, 69 | } 70 | for _, tt := range tests { 71 | t.Run(tt.name, func(t *testing.T) { 72 | gotProfiles, err := GetAllProfiles(tt.args.ctx, tt.args.ndbClient) 73 | if (err != nil) != tt.wantErr { 74 | t.Errorf("GetAllProfiles() error = %v, wantErr %v", err, tt.wantErr) 75 | return 76 | } 77 | if !reflect.DeepEqual(gotProfiles, tt.wantProfiles) { 78 | t.Errorf("GetAllProfiles() = %v, want %v", gotProfiles, tt.wantProfiles) 79 | } 80 | }) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /ndb_api/sla.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "net/http" 22 | 23 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 24 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 25 | ) 26 | 27 | // Fetches and returns all the SLAs as a sla slice 28 | func GetAllSLAs(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface) (slas []SLAResponse, err error) { 29 | log := ctrllog.FromContext(ctx) 30 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, "slas", nil, &slas); err != nil { 31 | log.Error(err, "Error in GetAllSLAs") 32 | return 33 | } 34 | return 35 | } 36 | -------------------------------------------------------------------------------- /ndb_api/sla_helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | 23 | "github.com/nutanix-cloud-native/ndb-operator/common/util" 24 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 25 | ) 26 | 27 | // Fetches all the SLAs from the ndb and returns the SLA matching the name 28 | // Returns an error if not found. 29 | func GetSLAByName(ctx context.Context, ndb_client *ndb_client.NDBClient, name string) (sla SLAResponse, err error) { 30 | slas, err := GetAllSLAs(ctx, ndb_client) 31 | if err != nil { 32 | return 33 | } 34 | sla, err = util.FindFirst(slas, func(s SLAResponse) bool { return s.Name == name }) 35 | if err != nil { 36 | err = fmt.Errorf("SLA %s not found", name) 37 | } 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /ndb_api/sla_helpers_test.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 10 | ) 11 | 12 | // Tests the GetSLAByName function, tests the following cases: 13 | // 1. SLA for a given name exists. 14 | // 2. SLA for a given name does not exist. 15 | // 3. Unable to fetch all SLAs to filter. 16 | func TestGetSLAByName(t *testing.T) { 17 | SLA_RESPONSES := getMockSLAResponses() 18 | tests := []struct { 19 | slaName string 20 | responseMap map[string]interface{} 21 | expectedSLAResponse SLAResponse 22 | expectedError error 23 | }{ 24 | // SLA for a given name exists 25 | { 26 | slaName: "SLA 1", 27 | responseMap: map[string]interface{}{ 28 | "GET /slas": SLA_RESPONSES, 29 | }, 30 | expectedSLAResponse: SLA_RESPONSES[0], 31 | expectedError: nil, 32 | }, 33 | // SLA for a given name does not exist. 34 | { 35 | slaName: "SLA-NOT-PRESENT", 36 | responseMap: map[string]interface{}{ 37 | "GET /slas": SLA_RESPONSES, 38 | }, 39 | expectedSLAResponse: SLAResponse{}, 40 | expectedError: fmt.Errorf("SLA SLA-NOT-PRESENT not found"), 41 | }, 42 | // Unable to fetch all SLAs to filter. 43 | { 44 | slaName: "SLA-X", 45 | responseMap: map[string]interface{}{ 46 | "GET /slas": nil, 47 | }, 48 | expectedSLAResponse: SLAResponse{}, 49 | expectedError: fmt.Errorf("SLA SLA-X not found"), 50 | }, 51 | } 52 | 53 | for _, tc := range tests { 54 | server := GetServerTestHelperWithResponseMap(t, tc.responseMap) 55 | defer server.Close() 56 | ndb_client := ndb_client.NewNDBClient("username", "password", server.URL, "", true) 57 | 58 | sla, err := GetSLAByName(context.Background(), ndb_client, tc.slaName) 59 | if !reflect.DeepEqual(tc.expectedSLAResponse, sla) { 60 | t.Fatalf("expected: %v, got: %v", tc.expectedSLAResponse, sla) 61 | } 62 | if tc.expectedError != err && tc.expectedError.Error() != err.Error() { 63 | t.Fatalf("expected: %v, got: %v", tc.expectedError, err) 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /ndb_api/sla_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type SLAResponse struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | UniqueName string `json:"uniqueName"` 23 | Description string `json:"description"` 24 | DailyRetention int `json:"dailyRetention"` 25 | WeeklyRetention int `json:"weeklyRetention"` 26 | MonthlyRetention int `json:"monthlyRetention"` 27 | QuarterlyRetention int `json:"quarterlyRetention"` 28 | YearlyRetention int `json:"yearlyRetention"` 29 | } 30 | -------------------------------------------------------------------------------- /ndb_api/sla_test.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "io" 8 | "net/http" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 13 | ) 14 | 15 | func TestGetAllSLAs(t *testing.T) { 16 | type args struct { 17 | ctx context.Context 18 | ndbClient ndb_client.NDBClientHTTPInterface 19 | } 20 | 21 | // Mocks of the NDB Client interface 22 | mockNDBClient := &MockNDBClientHTTPInterface{} 23 | 24 | mockNDBClient.On("NewRequest", http.MethodGet, "slas", nil).Once().Return(nil, errors.New("mock-error-new-request")) 25 | 26 | req := &http.Request{Method: http.MethodGet} 27 | res := &http.Response{ 28 | StatusCode: http.StatusOK, 29 | Body: io.NopCloser(bytes.NewBufferString( 30 | `[{"id":"1", "name":"sla-1"},{"id":"2", "name":"sla-2"}]`, 31 | )), 32 | } 33 | mockNDBClient.On("NewRequest", http.MethodGet, "slas", nil).Once().Return(req, nil) 34 | mockNDBClient.On("Do", req).Once().Return(res, nil) 35 | 36 | tests := []struct { 37 | name string 38 | args args 39 | wantSLAs []SLAResponse 40 | wantErr bool 41 | }{ 42 | { 43 | name: "Test 1: GetAllSLAs returns an error when sendRequest returns an error", 44 | args: args{ 45 | ctx: context.TODO(), 46 | ndbClient: mockNDBClient, 47 | }, 48 | wantSLAs: nil, 49 | wantErr: true, 50 | }, 51 | { 52 | name: "Test 2: GetAllSLAs returns a slice of SLAs when sendRequest does not return an error", 53 | args: args{ 54 | ctx: context.TODO(), 55 | ndbClient: mockNDBClient, 56 | }, 57 | wantSLAs: []SLAResponse{ 58 | { 59 | Id: "1", 60 | Name: "sla-1", 61 | }, 62 | { 63 | Id: "2", 64 | Name: "sla-2", 65 | }, 66 | }, 67 | wantErr: false, 68 | }, 69 | } 70 | 71 | for _, tt := range tests { 72 | t.Run(tt.name, func(t *testing.T) { 73 | gotSLAs, err := GetAllSLAs(tt.args.ctx, tt.args.ndbClient) 74 | if (err != nil) != tt.wantErr { 75 | t.Errorf("GetAllSLAs() error = %v, wantErr %v", err, tt.wantErr) 76 | return 77 | } 78 | if !reflect.DeepEqual(gotSLAs, tt.wantSLAs) { 79 | t.Errorf("GetAllSLAs() = %v, want %v", gotSLAs, tt.wantSLAs) 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ndb_api/snapshot_request_types.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | type SnapshotRequest struct { 4 | Name string `json:"name"` 5 | SnapshotLcmConfig SnapshotLcmConfig `json:"lcmConfig"` 6 | } 7 | 8 | type SnapshotLcmConfig struct { 9 | SnapshotLCMConfigDetailed SnapshotLcmConfigDetailed `json:"snapshotLCMConfig"` 10 | } 11 | 12 | type SnapshotLcmConfigDetailed struct { 13 | ExpiryDetails ExpiryDetails `json:"expiryDetails"` 14 | } 15 | -------------------------------------------------------------------------------- /ndb_api/snapshot_response_types.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | type SnapshotResponse struct { 4 | Id string `json:"id"` 5 | Name string `json:"name"` 6 | Description string `json:"description"` 7 | SnapshotId string `json:"snapshotId"` 8 | SnapshotUuid string `json:"snapshotUuid"` 9 | TimeMachineId string `json:"timeMachineId"` 10 | } 11 | -------------------------------------------------------------------------------- /ndb_api/time_machine.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package ndb_api 15 | 16 | import ( 17 | "context" 18 | "fmt" 19 | "net/http" 20 | 21 | "github.com/nutanix-cloud-native/ndb-operator/ndb_client" 22 | ctrllog "sigs.k8s.io/controller-runtime/pkg/log" 23 | ) 24 | 25 | // Creates a snapshot for a time machine 26 | // Returns the task info summary response for the operation TODO 27 | func CreateSnapshotForTM( 28 | ctx context.Context, 29 | ndbClient ndb_client.NDBClientHTTPInterface, 30 | tmId string, 31 | snapshotName string, 32 | expiryDateTimezone string, 33 | ExpireInDays string) (task *TaskInfoSummaryResponse, err error) { 34 | log := ctrllog.FromContext(ctx) 35 | // Checking if id is empty, this is necessary to create a snapshot for the timemachine (/tms/{timemachine_id}/snapshots) 36 | if tmId == "" { 37 | err = fmt.Errorf("timemachine id is empty") 38 | log.Error(err, "no timemachine id provided") 39 | return 40 | } 41 | takeSnapshotPath := fmt.Sprintf("tms/%s/snapshots", tmId) 42 | req := GenerateSnapshotRequest(snapshotName, expiryDateTimezone, ExpireInDays) 43 | if _, err = sendRequest(ctx, ndbClient, http.MethodPost, takeSnapshotPath, req, &task); err != nil { 44 | log.Error(err, "Error in CreateSnapshotForTM") 45 | return 46 | } 47 | return 48 | } 49 | 50 | // Gets snapshots for a time machine 51 | // Returns the task info summary response for the operation 52 | func GetSnapshotsForTM(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, tmId string) (response *TimeMachineGetSnapshotsResponse, err error) { 53 | log := ctrllog.FromContext(ctx) 54 | // Checking if id is empty, this is necessary to get a snapshot for the timemachine (/tms/{timemachine_id}/snapshots) 55 | if tmId == "" { 56 | err = fmt.Errorf("timemachine id is empty") 57 | log.Error(err, "no timemachine id provided") 58 | return 59 | } 60 | getTmSnapshotsPath := fmt.Sprintf("tms/%s/snapshots", tmId) 61 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getTmSnapshotsPath, nil, &response); err != nil { 62 | log.Error(err, "Error in GetSnapshotsForTM") 63 | return 64 | } 65 | return 66 | } 67 | 68 | // Gets TimeMachine by id 69 | func GetTimeMachineById(ctx context.Context, ndbClient ndb_client.NDBClientHTTPInterface, tmId string) (timeMachine *TimeMachineResponse, err error) { 70 | log := ctrllog.FromContext(ctx) 71 | // Checking if id is empty, this is necessary to get a timemachine (/tms/{timemachine_id}) 72 | if tmId == "" { 73 | err = fmt.Errorf("timemachine id is empty") 74 | log.Error(err, "no timemachine id provided") 75 | return 76 | } 77 | getTmDetailedPath := fmt.Sprintf("tms/%s", tmId) 78 | if _, err = sendRequest(ctx, ndbClient, http.MethodGet, getTmDetailedPath, nil, &timeMachine); err != nil { 79 | log.Error(err, "Error in GetTimeMachineById") 80 | return 81 | } 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /ndb_api/time_machine_helpers.go: -------------------------------------------------------------------------------- 1 | package ndb_api 2 | 3 | func GenerateSnapshotRequest(name string, expiryDateTimezone string, ExpireInDays string) *SnapshotRequest { 4 | return &SnapshotRequest{ 5 | Name: name, 6 | SnapshotLcmConfig: SnapshotLcmConfig{ 7 | SnapshotLCMConfigDetailed: SnapshotLcmConfigDetailed{ 8 | ExpiryDetails: ExpiryDetails{ 9 | ExpiryDateTimezone: expiryDateTimezone, 10 | ExpireInDays: ExpireInDays, 11 | }, 12 | }, 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ndb_api/time_machine_helpers_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | http://www.apache.org/licenses/LICENSE-2.0 7 | Unless required by applicable law or agreed to in writing, software 8 | distributed under the License is distributed on an "AS IS" BASIS, 9 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 | See the License for the specific language governing permissions and 11 | limitations under the License. 12 | */ 13 | 14 | package ndb_api 15 | 16 | import ( 17 | "reflect" 18 | "testing" 19 | ) 20 | 21 | func TestGenerateSnapshotRequest(t *testing.T) { 22 | name := "TestSnapshot" 23 | expiryDateTimezone := "UTC" 24 | expireInDays := "7" 25 | 26 | gotSnapshotRequest := GenerateSnapshotRequest(name, expiryDateTimezone, expireInDays) 27 | 28 | wantSnapshotRequest := &SnapshotRequest{ 29 | Name: name, 30 | SnapshotLcmConfig: SnapshotLcmConfig{ 31 | SnapshotLCMConfigDetailed: SnapshotLcmConfigDetailed{ 32 | ExpiryDetails: ExpiryDetails{ 33 | ExpiryDateTimezone: expiryDateTimezone, 34 | ExpireInDays: expireInDays, 35 | }, 36 | }, 37 | }, 38 | } 39 | 40 | if !reflect.DeepEqual(gotSnapshotRequest, wantSnapshotRequest) { 41 | t.Errorf("TestGenerateSnapshotRequest() = %v, want %v", gotSnapshotRequest, wantSnapshotRequest) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ndb_api/time_machine_response_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | type TimeMachineResponse struct { 20 | Id string `json:"id"` 21 | Name string `json:"name"` 22 | Description string `json:"description"` 23 | DatabaseId string `json:"databaseId"` 24 | Status string `json:"Status"` 25 | Sla TimeMachineSLA `json:"sla"` 26 | Schedule TimeMachineSchedule `json:"schedule"` 27 | } 28 | 29 | type TimeMachineSLA struct { 30 | Id string `json:"id"` 31 | Name string `json:"name"` 32 | } 33 | 34 | type TimeMachineSchedule struct { 35 | Id string `json:"id"` 36 | Name string `json:"name"` 37 | SnapshotTimeOfDay TimeMachineSnapshotTimeOfDay `json:"snapshotTimeOfDay"` 38 | ContinuousSchedule TimeMachineContinuousSchedule `json:"continuousSchedule"` 39 | WeeklySchedule TimeMachineWeeklySchedule `json:"weeklySchedule"` 40 | MonthlySchedule TimeMachineMonthlySchedule `json:"monthlySchedule"` 41 | QuarterlySchedule TimeMachineQuarterlySchedule `json:"quarterlySchedule"` 42 | } 43 | 44 | type TimeMachineSnapshotTimeOfDay struct { 45 | Hours int `json:"hours"` 46 | Minutes int `json:"minutes"` 47 | Seconds int `json:"seconds"` 48 | } 49 | 50 | type TimeMachineContinuousSchedule struct { 51 | LogBackupInterval int `json:"logBackupInterval"` 52 | SnapshotsPerDay int `json:"snapshotsPerDay"` 53 | } 54 | 55 | type TimeMachineWeeklySchedule struct { 56 | DayOfWeek string `json:"dayOfWeek"` 57 | } 58 | 59 | type TimeMachineMonthlySchedule struct { 60 | DayOfMonth int `json:"dayOfMonth"` 61 | } 62 | 63 | type TimeMachineQuarterlySchedule struct { 64 | StartMonth string `json:"startMonth"` 65 | } 66 | 67 | type TimeMachineGetSnapshotsResponse struct { 68 | SnapshotsPerNxCluster map[string][]SnapshotsParentInfoPerCluster `json:"snapshotsPerNxCluster"` 69 | } 70 | 71 | type SnapshotsParentInfoPerCluster struct { 72 | Snapshots []Snapshot `json:"snapshots"` 73 | } 74 | 75 | type Snapshot struct { 76 | Id string `json:"id"` 77 | } 78 | -------------------------------------------------------------------------------- /ndb_api/utility_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_api 18 | 19 | import ( 20 | "crypto/sha256" 21 | "crypto/subtle" 22 | "encoding/json" 23 | "net/http" 24 | "net/http/httptest" 25 | "testing" 26 | ) 27 | 28 | const ( 29 | mock_username = "username" 30 | mock_password = "password" 31 | NONE_SLA_ID = "NONE_SLA_ID" 32 | ) 33 | 34 | func checkAuthTestHelper(r *http.Request) bool { 35 | username, password, ok := r.BasicAuth() 36 | 37 | if ok { 38 | usernameHash := sha256.Sum256([]byte(username)) 39 | passwordHash := sha256.Sum256([]byte(password)) 40 | expectedUsernameHash := sha256.Sum256([]byte(mock_username)) 41 | expectedPasswordHash := sha256.Sum256([]byte(mock_password)) 42 | 43 | usernameMatch := (subtle.ConstantTimeCompare(usernameHash[:], expectedUsernameHash[:]) == 1) 44 | passwordMatch := (subtle.ConstantTimeCompare(passwordHash[:], expectedPasswordHash[:]) == 1) 45 | 46 | if usernameMatch && passwordMatch { 47 | return true 48 | } 49 | } 50 | return false 51 | } 52 | 53 | func GetServerTestHelper(t *testing.T) *httptest.Server { 54 | mockResponsesMap := getMockedResponseMap() 55 | return GetServerTestHelperWithResponseMap(t, mockResponsesMap) 56 | } 57 | 58 | // responseMap holds the responses that will be returned by this server. 59 | // The key is in the format [Method endpoint], ex - GET /slas, GET /profiles. 60 | func GetServerTestHelperWithResponseMap(t *testing.T, responseMap map[string]interface{}) *httptest.Server { 61 | return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 62 | if !checkAuthTestHelper(r) { 63 | t.Errorf("Invalid Authentication Credentials") 64 | } else { 65 | var response = responseMap[r.Method+" "+r.URL.Path] 66 | resp, _ := json.Marshal(response) 67 | w.WriteHeader(http.StatusOK) 68 | w.Write(resp) 69 | } 70 | })) 71 | } 72 | -------------------------------------------------------------------------------- /ndb_client/ndb_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2022-2023 Nutanix, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ndb_client 18 | 19 | import ( 20 | "bytes" 21 | "crypto/tls" 22 | "crypto/x509" 23 | "encoding/json" 24 | "io" 25 | "net/http" 26 | ) 27 | 28 | // NDBClientInterface defines the methods for an NDB client. 29 | type NDBClientHTTPInterface interface { 30 | NewRequest(method, endpoint string, requestBody interface{}) (*http.Request, error) 31 | Do(req *http.Request) (*http.Response, error) 32 | } 33 | 34 | type NDBClient struct { 35 | username string 36 | password string 37 | url string 38 | client *http.Client 39 | } 40 | 41 | func NewNDBClient(username, password, url, caCert string, skipVerify bool) *NDBClient { 42 | TLSClientConfig := &tls.Config{InsecureSkipVerify: skipVerify} 43 | if caCert != "" { 44 | caCertPool := x509.NewCertPool() 45 | caCertPool.AppendCertsFromPEM([]byte(caCert)) 46 | TLSClientConfig.RootCAs = caCertPool 47 | } 48 | client := &http.Client{ 49 | Transport: &http.Transport{TLSClientConfig: TLSClientConfig}, 50 | } 51 | return &NDBClient{username, password, url, client} 52 | } 53 | 54 | func (ndbClient *NDBClient) NewRequest(method, endpoint string, requestBody interface{}) (*http.Request, error) { 55 | 56 | url := ndbClient.url + "/" + endpoint 57 | 58 | var body io.Reader 59 | 60 | if requestBody != nil { 61 | // Serialize the request body to JSON. 62 | payload, err := json.Marshal(requestBody) 63 | if err != nil { 64 | return nil, err 65 | } 66 | // Create a reader from the serialized payload. 67 | body = bytes.NewReader(payload) 68 | } 69 | 70 | // Create a new HTTP request with the specified method and URL. 71 | req, err := http.NewRequest(method, url, body) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | // Set headers 77 | req.SetBasicAuth(ndbClient.username, ndbClient.password) 78 | req.Header.Add("Content-Type", "application/json; charset=utf-8") 79 | 80 | return req, nil 81 | } 82 | 83 | func (ndbClient *NDBClient) Do(req *http.Request) (*http.Response, error) { 84 | // Use the HTTP client to send the provided request. 85 | return ndbClient.client.Do(req) 86 | } 87 | --------------------------------------------------------------------------------