├── .devcontainer ├── devcontainer.json └── post-install.sh ├── .dockerignore ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── auto-close.yml │ ├── auto-update.yml │ ├── co-integration-test.yaml │ ├── dev-artifacts-push.yml │ ├── post-merge.yml │ └── pre-merge.yml ├── .gitignore ├── .golangci.yml ├── .mockery.yaml ├── .yamllint ├── CODE_OF_CONDUCT.md ├── Dockerfile.manager ├── Dockerfile.southbound ├── LICENSES └── Apache-2.0.txt ├── Makefile ├── PROJECT ├── README.md ├── REUSE.toml ├── SECURITY.md ├── VERSION ├── api └── v1alpha1 │ ├── condition_consts.go │ ├── groupversion_info.go │ ├── intelcluster_types.go │ ├── intelclustertemplate_types.go │ ├── intelmachine_types.go │ ├── intelmachinebinding_types.go │ ├── intelmachinetemplate_types.go │ └── zz_generated.deepcopy.go ├── cmd ├── manager │ └── main.go └── southbound │ └── main.go ├── config ├── crd │ ├── bases │ │ ├── infrastructure.cluster.x-k8s.io_intelclusters.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelmachines.yaml │ │ └── infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml │ ├── deps │ │ ├── cluster-api-v1.9.3.yaml │ │ └── cluster.edge-orchestrator.intel.com_clusterconnects.yaml │ ├── kustomization.yaml │ └── kustomizeconfig.yaml ├── default │ ├── kustomization.yaml │ ├── manager_metrics_patch.yaml │ └── metrics_service.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── network-policy │ ├── allow-metrics-traffic.yaml │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── clusterconnection_editor_role.yaml │ ├── clusterconnection_viewer_role.yaml │ ├── intelcluster_editor_role.yaml │ ├── intelcluster_viewer_role.yaml │ ├── intelclustertemplate_editor_role.yaml │ ├── intelclustertemplate_viewer_role.yaml │ ├── intelmachine_editor_role.yaml │ ├── intelmachine_viewer_role.yaml │ ├── intelmachinebinding_editor_role.yaml │ ├── intelmachinebinding_viewer_role.yaml │ ├── intelmachinetemplate_editor_role.yaml │ ├── intelmachinetemplate_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── metrics_auth_role.yaml │ ├── metrics_auth_role_binding.yaml │ ├── metrics_reader_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml └── samples │ ├── infrastructure_v1alpha1_clusterconnection.yaml │ ├── infrastructure_v1alpha1_intelcluster.yaml │ ├── infrastructure_v1alpha1_intelclustertemplate.yaml │ ├── infrastructure_v1alpha1_intelmachine.yaml │ ├── infrastructure_v1alpha1_intelmachinebinding.yaml │ ├── infrastructure_v1alpha1_intelmachinetemplate.yaml │ └── kustomization.yaml ├── deployment └── charts │ ├── intel-infra-provider-crds │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ │ ├── infrastructure.cluster.x-k8s.io_intelclusters.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml │ │ ├── infrastructure.cluster.x-k8s.io_intelmachines.yaml │ │ └── infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml │ └── values.yaml │ └── intel-infra-provider │ ├── .helmignore │ ├── Chart.yaml │ ├── templates │ ├── _helpers.tpl │ ├── ingress.yaml │ ├── manager.yaml │ ├── metrics_service.yaml │ ├── network_policy.yaml │ ├── role.yaml │ ├── role_binding.yaml │ ├── service_account.yaml │ ├── service_monitor.yaml │ ├── southbound_api.yaml │ └── southbound_api_service.yaml │ └── values.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt └── fuzz_all.sh ├── internal ├── controller │ ├── intelcluster_controller.go │ ├── intelcluster_controller_fuzz_test.go │ ├── intelcluster_controller_test.go │ ├── intelmachine_controller.go │ ├── intelmachine_controller_fuzz_test.go │ ├── intelmachine_controller_test.go │ └── suite_test.go ├── rego │ └── authz.rego ├── southboundconfig │ ├── config.go │ └── config_test.go ├── southboundhandler │ ├── handler.go │ ├── handler_test.go │ └── types.go └── southboundserver │ ├── server.go │ └── server_test.go ├── mocks ├── m_client │ ├── mock_client.go │ ├── mock_subresourcewriter.go │ └── mock_tenantawareinventoryclient.go ├── m_inventory │ ├── mock_infrastructureprovider.go │ └── mock_infrastructureprovider.go.license └── m_southboundhandler │ ├── mock_southboundhandler.go │ └── mock_southboundhandler.go.license ├── pkg ├── api │ └── proto │ │ ├── cluster_orchestrator_southbound.pb.go │ │ ├── cluster_orchestrator_southbound.pb.go.license │ │ ├── cluster_orchestrator_southbound.pb.validate.go │ │ ├── cluster_orchestrator_southbound.pb.validate.go.license │ │ ├── cluster_orchestrator_southbound.proto │ │ ├── cluster_orchestrator_southbound.proto.license │ │ ├── cluster_orchestrator_southbound_grpc.pb.go │ │ └── cluster_orchestrator_southbound_grpc.pb.go.license ├── auth │ ├── auth_echo │ │ ├── auth_echo.go │ │ ├── auth_echo_test.go │ │ └── test │ │ │ └── authz.rego │ └── auth_multitenancy │ │ ├── auth.go │ │ └── auth_test.go ├── inventory │ ├── inventory_client.go │ ├── inventory_client_test.go │ ├── machine_provider.go │ ├── machine_provider_test.go │ └── types.go ├── logging │ ├── README.md │ ├── logger.go │ └── logger_test.go ├── mocks │ └── mock-secret-client │ │ └── mock_secret_client.go ├── rbac │ ├── rbac.go │ ├── rbac_test.go │ └── test │ │ └── authz.rego ├── scope │ ├── cluster.go │ └── cluster_test.go ├── server-options │ ├── grpc_server_options.go │ ├── grpc_server_options_test.go │ ├── http_server_options.go │ └── http_server_options_test.go ├── tenant │ ├── grpc.go │ ├── grpc_test.go │ ├── rest.go │ ├── rest_test.go │ ├── tenant_test.go │ ├── types.go │ ├── utils.go │ └── utils_test.go ├── testing │ └── testing_utils.go ├── tracing │ ├── trace.go │ ├── trace_test.go │ └── tracing_test.go └── utils │ ├── file.go │ ├── file_test.go │ ├── http.go │ ├── http_test.go │ ├── kubernetes.go │ ├── kubernetes_test.go │ ├── testdata │ ├── dstFile.yml │ ├── fileutil1.yml │ ├── fileutil_test.json │ ├── fileutil_test.yml │ ├── hardlink.lns │ ├── hardlinkobj │ ├── symbol.yaml │ └── testkubeconfigcontent │ ├── tools.go │ ├── tools_test.go │ ├── utils.go │ ├── utils_test.go │ ├── validate.go │ └── validate_test.go ├── requirements.txt ├── test ├── coder │ └── traefik-values.yaml ├── demo │ ├── kind-cluster-with-extramounts.yaml │ ├── rke2-intel-clusterclass-example.yaml │ └── rke2-intel-example.yaml ├── e2e │ ├── e2e_suite_test.go │ └── e2e_test.go ├── grpc-stub-middleware │ └── grpc_stub_middleware.go ├── inventory-stub │ ├── inventory_stub.go │ └── inventory_stub_test.go └── utils │ ├── utils.go │ └── utils_controller.go └── trivy.yaml /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kubebuilder DevContainer", 3 | "image": "golang:1.22", 4 | "features": { 5 | "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 6 | "ghcr.io/devcontainers/features/git:1": {} 7 | }, 8 | 9 | "runArgs": ["--network=host"], 10 | 11 | "customizations": { 12 | "vscode": { 13 | "settings": { 14 | "terminal.integrated.shell.linux": "/bin/bash" 15 | }, 16 | "extensions": [ 17 | "ms-kubernetes-tools.vscode-kubernetes-tools", 18 | "ms-azuretools.vscode-docker" 19 | ] 20 | } 21 | }, 22 | 23 | "onCreateCommand": "bash .devcontainer/post-install.sh" 24 | } 25 | 26 | -------------------------------------------------------------------------------- /.devcontainer/post-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | 4 | curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 5 | chmod +x ./kind 6 | mv ./kind /usr/local/bin/kind 7 | 8 | curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/amd64 9 | chmod +x kubebuilder 10 | mv kubebuilder /usr/local/bin/ 11 | 12 | KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) 13 | curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" 14 | chmod +x kubectl 15 | mv kubectl /usr/local/bin/kubectl 16 | 17 | docker network create -d=bridge --subnet=172.19.0.0/24 kind 18 | 19 | kind version 20 | kubebuilder version 21 | docker --version 22 | go version 23 | kubectl version --client 24 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | * @gcgirish @hyunsun @jdanieck @andybavier @madalazar -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | ### Description 3 | 4 | Please include a summary of the changes and the related issue. List any dependencies that are required for this change. 5 | 6 | Fixes # (issue) 7 | 8 | ### Any Newly Introduced Dependencies 9 | 10 | Please describe any newly introduced 3rd party dependencies in this change. List their name, license information and how they are used in the project. 11 | 12 | ### How Has This Been Tested? 13 | 14 | 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 15 | 16 | ### Checklist: 17 | 18 | - [ ] I agree to use the APACHE-2.0 license for my code changes 19 | - [ ] I have not introduced any 3rd party dependency changes 20 | - [ ] I have performed a self-review of my code -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | version: 2 6 | updates: 7 | - package-ecosystem: "gomod" 8 | directories: 9 | - "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | commit-message: 14 | prefix: "[gomod] " 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: daily 19 | open-pull-requests-limit: 10 20 | commit-message: 21 | prefix: "[gha] " -------------------------------------------------------------------------------- /.github/workflows/auto-close.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: Stale Pull Requests 7 | 8 | # After 30 days of no activity on a PR, the PR should be marked as stale, 9 | # a comment made on the PR informing the author of the new status, 10 | # and closed after 15 days if there is no further activity from the change to stale state. 11 | on: 12 | schedule: 13 | - cron: '30 1 * * *' # run every day 14 | workflow_dispatch: {} 15 | 16 | permissions: {} 17 | 18 | jobs: 19 | stale-auto-close: 20 | permissions: 21 | contents: read 22 | pull-requests: write 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 26 | with: 27 | repo-token: ${{ secrets.GITHUB_TOKEN }} 28 | stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity. Make a comment or update the PR to avoid closing PR after 15 days.' 29 | days-before-pr-stale: 30 30 | days-before-pr-close: 15 31 | remove-pr-stale-when-updated: 'true' 32 | close-pr-message: 'This pull request was automatically closed due to inactivity' -------------------------------------------------------------------------------- /.github/workflows/auto-update.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: Auto Update PR 7 | 8 | # On push to the main branch and support branches, update any branches that are out of date 9 | # and have auto-merge enabled. If the branch is currently out of date with the base branch, 10 | # it must be first manually updated and then will be kept up to date on future runs. 11 | on: 12 | push: 13 | branches: 14 | - main 15 | - release-* 16 | 17 | permissions: {} 18 | 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | update-pull-requests: 25 | permissions: 26 | contents: read 27 | pull-requests: write 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - name: Checkout repository 32 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 33 | with: 34 | persist-credentials: false 35 | 36 | - name: Update pull requests 37 | uses: open-edge-platform/orch-ci/.github/actions/pr_updater@3bdd409ccf738472c6e1547d14628b51c70dbe99 # 0.1.21 38 | with: 39 | github_token: ${{ secrets.SYS_ORCH_GITHUB }} 40 | -------------------------------------------------------------------------------- /.github/workflows/co-integration-test.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: CO Integration test CI Pipeline 7 | 8 | on: 9 | pull_request: 10 | branches: 11 | - main 12 | - release-* 13 | workflow_dispatch: 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | integration-smoke-test: 19 | permissions: 20 | contents: read 21 | runs-on: ubuntu-24.04-16core-64GB 22 | if: true 23 | env: 24 | VERSION: ${{ github.head_ref }} # Use the component branch that triggered the action for the test 25 | steps: 26 | - name: Checkout orch ci 27 | uses: actions/checkout@v4 28 | with: 29 | repository: open-edge-platform/orch-ci 30 | path: ci 31 | ref: "main" 32 | token: ${{ secrets.SYS_ORCH_GITHUB }} 33 | persist-credentials: false 34 | 35 | - name: Checkout cluster-tests for integration tests 36 | uses: actions/checkout@v4 37 | with: 38 | repository: open-edge-platform/cluster-tests 39 | path: cluster-tests 40 | ref: "main" 41 | token: ${{ secrets.SYS_ORCH_GITHUB }} 42 | persist-credentials: false 43 | 44 | - name: Bootstrap CI environment 45 | uses: ./ci/.github/actions/bootstrap 46 | with: 47 | gh_token: ${{ secrets.SYS_ORCH_GITHUB }} 48 | 49 | - name: Run make test with additional config 50 | env: 51 | VERSION: ${{ env.VERSION }} 52 | run: | 53 | cd cluster-tests 54 | ADDITIONAL_CONFIG="{\"components\":[{\"name\":\"cluster-api-provider-intel\", \"skip-local-build\": false, \"git-repo\": {\"version\":\"${VERSION}\"}}]}" make test 55 | -------------------------------------------------------------------------------- /.github/workflows/dev-artifacts-push.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: Push development artifacts to the Release Service 7 | 8 | on: 9 | # manual trigger from the Actions tab 10 | workflow_dispatch: 11 | 12 | env: 13 | VERSION_SUFFIX: -test 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | dev-artifacts-push: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | contents: read 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 25 | with: 26 | persist-credentials: false 27 | 28 | - name: Build Docker image 29 | run: | 30 | make docker-build 31 | 32 | - name: Build Helm chart 33 | run: | 34 | make helm-build 35 | 36 | - name: Configure AWS credentials 37 | uses: aws-actions/configure-aws-credentials@b47578312673ae6fa5b5096b330d9fbac3d116df # v4.0.1 38 | with: 39 | aws-access-key-id: ${{ secrets.NO_AUTH_ECR_PUSH_USERNAME }} 40 | aws-secret-access-key: ${{ secrets.NO_AUTH_ECR_PUSH_PASSWD }} 41 | aws-region: us-west-2 42 | 43 | - name: Login to Amazon ECR 44 | uses: aws-actions/amazon-ecr-login@062b18b96a7aff071d4dc91bc00c4c1a7945b076 # v2.0.1 45 | with: 46 | registries: "080137407410" 47 | 48 | - name: Push Docker image 49 | run: | 50 | make docker-push 51 | 52 | - name: Push Helm chart 53 | run: | 54 | make helm-push 55 | -------------------------------------------------------------------------------- /.github/workflows/post-merge.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: Post-Merge CI Pipeline 7 | 8 | on: 9 | push: 10 | branches: 11 | - main 12 | - release-* 13 | workflow_dispatch: 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | post-merge: 19 | permissions: 20 | contents: read 21 | security-events: write 22 | id-token: write 23 | uses: open-edge-platform/orch-ci/.github/workflows/post-merge.yml@0.1.21 24 | with: 25 | cache_go: true 26 | remove_cache_go: true 27 | run_build: true 28 | run_version_check: true 29 | run_dep_version_check: true 30 | run_version_tag: true 31 | run_docker_build: true 32 | run_docker_push: true 33 | run_helm_build: true 34 | run_helm_push: true 35 | secrets: 36 | SYS_ORCH_GITHUB: ${{ secrets.SYS_ORCH_GITHUB }} 37 | COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} 38 | COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} 39 | NO_AUTH_ECR_PUSH_USERNAME: ${{ secrets.NO_AUTH_ECR_PUSH_USERNAME }} 40 | NO_AUTH_ECR_PUSH_PASSWD: ${{ secrets.NO_AUTH_ECR_PUSH_PASSWD }} 41 | MSTEAMS_WEBHOOK: ${{ secrets.TEAMS_WEBHOOK }} 42 | -------------------------------------------------------------------------------- /.github/workflows/pre-merge.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | 6 | name: Pre-Merge CI Pipeline 7 | 8 | on: 9 | pull_request: 10 | branches: 11 | - main 12 | - release-* 13 | workflow_dispatch: 14 | 15 | permissions: {} 16 | 17 | jobs: 18 | lint: 19 | permissions: 20 | contents: read 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout PR 24 | uses: actions/checkout@v4 25 | with: 26 | ref: ${{ github.head_ref }} 27 | persist-credentials: false 28 | 29 | - name: "Setup" 30 | uses: open-edge-platform/orch-ci/.github/actions/bootstrap@main 31 | with: 32 | gh_token: ${{ secrets.SYS_ORCH_GITHUB }} 33 | bootstrap_tools: "go,gotools,nodejs" 34 | 35 | - name: Lint code 36 | run: make lint 37 | 38 | build: 39 | permissions: 40 | contents: read 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: Checkout PR 44 | uses: actions/checkout@v4 45 | with: 46 | ref: ${{ github.head_ref }} 47 | persist-credentials: false 48 | 49 | - name: "Setup" 50 | uses: open-edge-platform/orch-ci/.github/actions/bootstrap@main 51 | with: 52 | gh_token: ${{ secrets.SYS_ORCH_GITHUB }} 53 | bootstrap_tools: "go,gotools" 54 | 55 | - name: Build code 56 | run: make build 57 | 58 | test: 59 | permissions: 60 | contents: read 61 | runs-on: ubuntu-latest 62 | steps: 63 | - name: Checkout PR 64 | uses: actions/checkout@v4 65 | with: 66 | ref: ${{ github.head_ref }} 67 | persist-credentials: false 68 | 69 | - name: "Setup" 70 | uses: open-edge-platform/orch-ci/.github/actions/bootstrap@main 71 | with: 72 | gh_token: ${{ secrets.SYS_ORCH_GITHUB }} 73 | bootstrap_tools: "go,gotools" 74 | 75 | - name: Test code 76 | run: make test 77 | 78 | pre-merge: 79 | permissions: 80 | contents: read 81 | needs: [lint, build, test] 82 | uses: open-edge-platform/orch-ci/.github/workflows/pre-merge.yml@0.1.21 83 | with: 84 | bootstrap_tools: "base,go" 85 | cache_go: true 86 | remove_cache_go: true 87 | run_security_scans: true 88 | run_version_check: true 89 | run_dep_version_check: true 90 | run_build: false 91 | run_lint: false 92 | run_test: false 93 | run_validate_clean_folder: false 94 | run_docker_build: true 95 | run_docker_push: false 96 | run_helm_build: true 97 | run_helm_push: false 98 | run_artifact: false 99 | version_suffix: "-pr-${{ github.event.number }}" 100 | secrets: inherit # zizmor: ignore[secrets-inherit] 101 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | bin/* 8 | Dockerfile.cross 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Seed corpus directories for fuzzing 14 | internal/southboundhandler/testdata/ 15 | internal/controller/testdata/ 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | # Kubernetes Generated files - skip generated files, except for vendored files 24 | !vendor/**/zz_generated.* 25 | 26 | # editor and IDE paraphernalia 27 | .idea 28 | .vscode 29 | *.swp 30 | *.swo 31 | *~ 32 | 33 | venv-env 34 | coverage.txt 35 | coverage.xml 36 | coverage.html 37 | artifacts/* 38 | *.tgz 39 | 40 | vendor 41 | ci 42 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | run: 5 | timeout: 5m 6 | allow-parallel-runners: true 7 | 8 | issues: 9 | # don't skip warning about doc comments 10 | # don't exclude the default set of lint 11 | exclude-use-default: false 12 | # restore some of the defaults 13 | # (fill in the rest as needed) 14 | exclude-rules: 15 | - path: "api/*" 16 | linters: 17 | - lll 18 | - path: "internal/*" 19 | linters: 20 | - dupl 21 | - lll 22 | # disable some lints for imported packages 23 | - path: "pkg/secret_client/*" 24 | linters: 25 | - dupl 26 | - revive 27 | - lll 28 | - path: "pkg/server-options/*" 29 | linters: 30 | - revive 31 | - staticcheck 32 | - lll 33 | - path: "pkg/utils/*" 34 | linters: 35 | - unparam 36 | - prealloc 37 | - revive 38 | - misspell 39 | - goimports 40 | - errcheck 41 | - lll 42 | - path: "pkg/auth/*" 43 | linters: 44 | - misspell 45 | - errcheck 46 | - lll 47 | - path: "pkg/rbac/*" 48 | linters: 49 | - misspell 50 | - lll 51 | - path: "pkg/logging/*" 52 | linters: 53 | - lll 54 | - errcheck 55 | - path: "pkg/tenant/*" 56 | linters: 57 | - lll 58 | - path: "pkg/data-migration/*" 59 | linters: 60 | - lll 61 | - path: "pkg/inventory/*" 62 | linters: 63 | - lll 64 | - path: "pkg/testing/*" 65 | linters: 66 | - lll 67 | linters: 68 | disable-all: true 69 | enable: 70 | - dupl 71 | - errcheck 72 | - copyloopvar 73 | - ginkgolinter 74 | - goconst 75 | - gocyclo 76 | - gofmt 77 | - goimports 78 | - gosimple 79 | - govet 80 | - ineffassign 81 | - lll 82 | - misspell 83 | - nakedret 84 | - prealloc 85 | - revive 86 | - staticcheck 87 | - typecheck 88 | - unconvert 89 | - unparam 90 | - unused 91 | 92 | linters-settings: 93 | revive: 94 | rules: 95 | - name: comment-spacings 96 | dupl: 97 | # required by Get/Delete Workload, WorkloadMember as dupl 98 | # identifies the unit tests there as duplicates. This is even 99 | # though the testcase members and inventory client calls that 100 | # are made in the unit tests are different 101 | threshold: 200 102 | -------------------------------------------------------------------------------- /.mockery.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | dir: "mocks/m_{{.PackageName | lower}}" 5 | outpkg: "m_{{.PackageName | lower}}" 6 | filename: "mock_{{.InterfaceName | lower}}.go" 7 | mockname: "Mock{{.InterfaceName}}" 8 | with-expecter: true 9 | packages: 10 | github.com/open-edge-platform/cluster-api-provider-intel/pkg/inventory: 11 | interfaces: 12 | InfrastructureProvider: 13 | github.com/open-edge-platform/cluster-api-provider-intel/internal/southboundhandler: 14 | interfaces: 15 | SouthboundHandler: 16 | github.com/open-edge-platform/infra-core/inventory/v2/pkg/client: 17 | interfaces: 18 | TenantAwareInventoryClient: 19 | sigs.k8s.io/controller-runtime/pkg/client: 20 | interfaces: 21 | Client: 22 | SubResourceWriter: -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | extends: default 4 | 5 | rules: 6 | document-start: disable 7 | indentation: disable 8 | line-length: disable 9 | # max: 99 10 | # level: warning 11 | 12 | # Kubebuilder comments don't have leading space 13 | comments: 14 | require-starting-space: false 15 | 16 | ignore: | 17 | .github/ 18 | config/ 19 | deployment/ 20 | vendor 21 | ci/ 22 | -------------------------------------------------------------------------------- /Dockerfile.manager: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Build the manager binary 5 | FROM golang:1.24 AS builder 6 | ARG TARGETOS 7 | ARG TARGETARCH 8 | 9 | WORKDIR /workspace 10 | # Copy the Go Modules manifests 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | 14 | # Copy the go source 15 | COPY cmd/manager/main.go cmd/manager/main.go 16 | COPY api/ api/ 17 | COPY internal/ internal/ 18 | COPY pkg/ pkg/ 19 | COPY test/ test/ 20 | COPY vendor/ vendor/ 21 | COPY Makefile Makefile 22 | COPY VERSION VERSION 23 | 24 | # Build 25 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 26 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 27 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 28 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 29 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} GO_VENDOR=true make build-manager 30 | 31 | # Use distroless as minimal base image to package the manager binary 32 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 33 | FROM gcr.io/distroless/static:nonroot 34 | WORKDIR / 35 | COPY --from=builder /workspace/bin/manager . 36 | USER 65532:65532 37 | 38 | ENTRYPOINT ["/manager"] 39 | -------------------------------------------------------------------------------- /Dockerfile.southbound: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Build the manager binary 5 | FROM golang:1.24 AS builder 6 | ARG TARGETOS 7 | ARG TARGETARCH 8 | 9 | WORKDIR /workspace 10 | # Copy the Go Modules manifests 11 | COPY go.mod go.mod 12 | COPY go.sum go.sum 13 | 14 | # Copy the go source 15 | COPY cmd/southbound/main.go cmd/southbound/main.go 16 | COPY api/ api/ 17 | COPY internal/ internal/ 18 | COPY pkg/ pkg/ 19 | COPY vendor/ vendor/ 20 | COPY test/ test 21 | COPY Makefile Makefile 22 | COPY VERSION VERSION 23 | 24 | # Build 25 | # the GOARCH has not a default value to allow the binary be built according to the host where the command 26 | # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 27 | # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 28 | # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 29 | RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} GO_VENDOR=true make build-southbound 30 | 31 | # Use distroless as minimal base image to package the manager binary 32 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 33 | FROM gcr.io/distroless/static:nonroot 34 | WORKDIR / 35 | COPY --from=builder /workspace/bin/southbound_handler . 36 | COPY --from=builder /workspace/internal/rego ./internal/rego 37 | USER 65532:65532 38 | 39 | ENTRYPOINT ["/southbound_handler"] 40 | -------------------------------------------------------------------------------- /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: cluster.x-k8s.io 6 | layout: 7 | - go.kubebuilder.io/v4 8 | projectName: cluster-api-provider-intel 9 | repo: github.com/open-edge-platform/cluster-api-provider-intel 10 | resources: 11 | - api: 12 | crdVersion: v1 13 | namespaced: true 14 | controller: true 15 | domain: cluster.x-k8s.io 16 | group: infrastructure 17 | kind: IntelMachine 18 | path: github.com/open-edge-platform/cluster-api-provider-intel/api/v1alpha1 19 | version: v1alpha1 20 | - api: 21 | crdVersion: v1 22 | namespaced: true 23 | controller: true 24 | domain: cluster.x-k8s.io 25 | group: infrastructure 26 | kind: IntelCluster 27 | path: github.com/open-edge-platform/cluster-api-provider-intel/api/v1alpha1 28 | version: v1alpha1 29 | - api: 30 | crdVersion: v1 31 | namespaced: true 32 | domain: cluster.x-k8s.io 33 | group: infrastructure 34 | kind: IntelClusterTemplate 35 | path: github.com/open-edge-platform/cluster-api-provider-intel/api/v1alpha1 36 | version: v1alpha1 37 | - api: 38 | crdVersion: v1 39 | namespaced: true 40 | domain: cluster.x-k8s.io 41 | group: infrastructure 42 | kind: IntelMachineTemplate 43 | path: github.com/open-edge-platform/cluster-api-provider-intel/api/v1alpha1 44 | version: v1alpha1 45 | - api: 46 | crdVersion: v1 47 | namespaced: true 48 | domain: cluster.x-k8s.io 49 | group: infrastructure 50 | kind: IntelMachineBinding 51 | path: github.com/open-edge-platform/cluster-api-provider-intel/api/v1alpha1 52 | version: v1alpha1 53 | version: "3" 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Cluster API Provider for Intel 2 | 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/open-edge-platform/cluster-api-provider-intel/badge)](https://scorecard.dev/viewer/?uri=github.com/open-edge-platform/cluster-api-provider-intel) 5 | 6 | ## Overview 7 | 8 | The Cluster API Provider for Intel enables using the [Cluster API](https://cluster-api.sigs.k8s.io/) 9 | framework to create Kubernetes clusters on hosts managed by the [Edge Orchestrator](https://docs.openedgeplatform.intel.com/edge-manage-docs/main/index.html). 10 | Cluster API is a Kubernetes sub-project focused on providing declarative APIs and tooling to simplify provisioning, upgrading, and operating multiple Kubernetes clusters. 11 | 12 | The Cluster API Provider for Intel consists of two components: the Infrastructure Provider 13 | controllers and the Southbound Handler. 14 | 15 | ## Get Started 16 | 17 | The recommended way to try out the Cluster API Provider for Intel is by using the Edge Orchestrator. 18 | Refer to the [Getting Started Guide](https://docs.openedgeplatform.intel.com/edge-manage-docs/main/user_guide/get_started_guide/index.html) to get started with the Edge Orchestrator. 19 | 20 | ## Develop 21 | 22 | If you are interested in contributing to the development of CAPI Provider for Intel, you will need 23 | an environment where you can use it to create and delete clusters. 24 | 25 | The [cluster-tests](https://github.com/open-edge-platform/cluster-tests) repo provides a 26 | lightweight environment for integration testing of the CAPI Provider for Intel as well as other 27 | Edge Orchestrator components related to cluster management. Clone that repo, change into the 28 | cluster-tests directory, and run: 29 | 30 | ``` 31 | make test 32 | ``` 33 | 34 | This command creates a KinD cluster and deploys cert-manager, Cluster API operator, CAPI Provider for Intel, 35 | Cluster Manager, and Cluster Connect Gateway. It then creates and deletes a cluster inside a Kubernetes 36 | pod. Consult the cluster-tests [README](https://github.com/open-edge-platform/cluster-tests/blob/main/README.md) 37 | for details on how to test your code in this environment. 38 | 39 | ## Contribute 40 | 41 | We welcome contributions from the community! To contribute, please open a pull request to have your changes reviewed and merged into the main. To learn how to contribute to the project, see the [Contributor's Guide](https://docs.openedgeplatform.intel.com/edge-manage-docs/main/developer_guide/contributor_guide/index.html). We encourage you to add appropriate unit tests and e2e tests if your contribution introduces a new feature. 42 | 43 | Additionally, ensure the following commands are successful: 44 | 45 | ``` 46 | make test 47 | make lint 48 | make license 49 | ``` 50 | 51 | ## Community and Support 52 | 53 | To learn more about the project, its community, and governance, visit the [Edge Orchestrator Community]. 54 | For support, start with [Troubleshooting] or [contact us]. 55 | 56 | ## License 57 | 58 | Cluster API Provider Intel is licensed under [Apache 2.0 License](LICENSES/Apache-2.0.txt) 59 | 60 | [Edge Orchestrator Community]: https://docs.openedgeplatform.intel.com/edge-manage-docs/main/index.html 61 | [Contact us]: https://github.com/open-edge-platform 62 | [Troubleshooting]: https://docs.openedgeplatform.intel.com/edge-manage-docs/main/developer_guide/troubleshooting/index.html 63 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | version = 1 5 | 6 | [[annotations]] 7 | path = [ 8 | "*.md", 9 | ".github/*", 10 | "VERSION", 11 | "go.mod", 12 | "go.sum", 13 | ".gitignore", 14 | "api/*", 15 | ".dockerignore", 16 | ".devcontainer/*", 17 | "pkg/logging/*.md", 18 | "pkg/utils/testdata/*", 19 | "bin", 20 | "test", 21 | "config/**", 22 | "PROJECT", 23 | "mocks/**", 24 | "hack/*" 25 | ] 26 | SPDX-FileCopyrightText = "2025 Intel Corporation" 27 | SPDX-License-Identifier = "Apache-2.0" 28 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.2.0 2 | -------------------------------------------------------------------------------- /api/v1alpha1/condition_consts.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 7 | 8 | // Conditions and condition Reasons for the IntelMachine object. 9 | 10 | const ( 11 | // HostProvisionedCondition documents the status of the provisioning of the host in Inventory. 12 | HostProvisionedCondition clusterv1.ConditionType = "HostProvisioned" 13 | 14 | // WaitingForClusterInfrastructureReason (Severity=Info) documents an IntelMachine waiting for the cluster 15 | // infrastructure to be ready before starting to provision the host. 16 | WaitingForClusterInfrastructureReason = "WaitingForClusterInfrastructure" 17 | 18 | // WaitingForBootstrapDataReason (Severity=Info) documents an IntelMachine waiting for the bootstrap 19 | // script to be ready before starting to provision the host. 20 | WaitingForBootstrapDataReason = "WaitingForBootstrapData" 21 | 22 | // WaitingforMachineBindingReason (Severity=Warning) documents an IntelMachine waiting for a valid 23 | // IntelMachineBinding that matches the cluster name and machine template name of the IntelMachine. 24 | WaitingForMachineBindingReason = "WaitingForMachineBinding" 25 | 26 | // HostProvisioningFailedReason (Severity=Warning) documents an IntelMachine controller detecting 27 | // an error while provisioning the host. These kinds of errors are usually transient and failed 28 | // provisionings are automatically re-tried by the controller. 29 | HostProvisioningFailedReason = "HostProvisioningFailed" 30 | 31 | // HostDeletedReason (Severity=Error) documents an IntelMachine controller detecting 32 | // the underlying host has been deleted unexpectedly. 33 | HostDeletedReason = "HostDeleted" 34 | ) 35 | 36 | const ( 37 | // BootstrapExecSucceededCondition provides an observation of the IntelMachine bootstrap process. 38 | // It is set based on successful execution of bootstrap commands and on the existence of 39 | // the /run/cluster-api/bootstrap-success.complete file. 40 | // The condition gets generated after HostProvisionedCondition is True. 41 | // 42 | // NOTE: as a difference from other providers, host bootstrap is managed by the Intel Provider's 43 | // Southbound Handler and Cluster Agent (not by cloud-init). 44 | BootstrapExecSucceededCondition clusterv1.ConditionType = "BootstrapExecSucceeded" 45 | 46 | // BootstrappingReason documents (Severity=Info) an IntelMachine currently executing the bootstrap 47 | // script that creates the Kubernetes node on the newly provisioned machine infrastructure. 48 | BootstrappingReason = "Bootstrapping" 49 | 50 | // BootstrapFailedReason documents (Severity=Warning) an IntelMachine controller detecting an error while 51 | // bootstrapping the Kubernetes node on the machine just provisioned; those kind of errors are usually 52 | // transient and failed bootstrap are automatically re-tried by the controller. 53 | BootstrapFailedReason = "BootstrapFailed" 54 | 55 | // BootstrapWaitingReason documents (Severity=Info) an IntelMachine waiting for the bootstrap script to be 56 | // executed on the machine. 57 | BootstrapWaitingReason = "BootstrapWaiting" 58 | ) 59 | 60 | const ( 61 | // SecureTunnelEstablishedCondition reports whether the secure tunnel connection to the IntelCluster is established. 62 | SecureTunnelEstablishedCondition clusterv1.ConditionType = "SecureTunnelEstablished" 63 | 64 | // SecureTunnelNotEstablishedReason (Severity=Warning) indicates that the secure tunnel connection to the IntelCluster is not established or the connection is unhealthy. 65 | SecureTunnelNotEstablishedReason = "ConnectAgentDisconnected" 66 | 67 | // SecureTunnelUnknownReason (Severity=Info) indicates that the secure tunnel connection status has not been checked yet. 68 | SecureTunnelUnknownReason = "ConnectAgentUnknown" 69 | ) 70 | 71 | const ( 72 | // ControlPlaneEndpointReadyCondition reports on whether a control plane endpoint was successfully reconciled 73 | ControlPlaneEndpointReadyCondition clusterv1.ConditionType = "ControlPlaneEnpointReady" 74 | // WaitingForControlPlaneEndpointReason (Severity=Warn) refers to a IntelCluster which is waiting for the control 75 | // plane endpoint to be populated through a ClusterConnection object 76 | WaitingForControlPlaneEndpointReason = "WaitingForControlPlaneEndpoint" 77 | // WaitingForControlPlaneEndpointReason (Severity=Error) refers to a IntelCluster which received a control plane 78 | // endpoint but with invalid values 79 | InvalidControlPlaneEndpointReason = "InvalidControlPlaneEndpoint" 80 | 81 | // WorkloadCreatedReadyCondition reports on whether a workload was successfully created with the infrastructure provider 82 | WorkloadCreatedReadyCondition clusterv1.ConditionType = "WorkloadCreatedReady" 83 | // WaitingForWorkloadToBeProvisonedReason (Severity=Info) refers to a IntelCluster which is waiting for 84 | // the workload to be created by the inventory provider 85 | WaitingForWorkloadToBeProvisonedReason = "WaitingForWorkloadToBeProvisoned" 86 | // InvalidWorkloadReason (Severity=Error) refers to a IntelCluster which received an invalid response from the 87 | // inventory provider when asked to create a workload 88 | InvalidWorkloadReason = "InvalidWorkload" 89 | ) 90 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Package v1alpha1 contains API Schema definitions for the infrastructure v1alpha1 API group. 5 | // +kubebuilder:object:generate=true 6 | // +groupName=infrastructure.cluster.x-k8s.io 7 | package v1alpha1 8 | 9 | import ( 10 | "k8s.io/apimachinery/pkg/runtime/schema" 11 | "sigs.k8s.io/controller-runtime/pkg/scheme" 12 | ) 13 | 14 | var ( 15 | // GroupVersion is group version used to register these objects. 16 | GroupVersion = schema.GroupVersion{Group: "infrastructure.cluster.x-k8s.io", Version: "v1alpha1"} 17 | 18 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme. 19 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 20 | 21 | // AddToScheme adds the types in this group-version to the given scheme. 22 | AddToScheme = SchemeBuilder.AddToScheme 23 | ) 24 | -------------------------------------------------------------------------------- /api/v1alpha1/intelcluster_types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 9 | ) 10 | 11 | const ( 12 | ClusterFinalizer = "intelcluster.infrastructure.cluster.x-k8s.io" 13 | ) 14 | 15 | // IntelClusterSpec defines the desired state of IntelCluster 16 | type IntelClusterSpec struct { 17 | // controlPlaneEndpoint represents the endpoint used to communicate with the control plane 18 | // +optional 19 | ControlPlaneEndpoint clusterv1.APIEndpoint `json:"controlPlaneEndpoint"` 20 | // providerId represents the id the inventory manager assigns to the cluster at creation time 21 | // +optional 22 | ProviderId string `json:"providerId"` 23 | } 24 | 25 | // IntelClusterV1Beta2Status groups all the fields that will be added or modified in IntelCluster with the V1Beta2 version. 26 | // See https://github.com/kubernetes-sigs/cluster-api/blob/main/docs/proposals/20240916-improve-status-in-CAPI-resources.md for more context. 27 | type IntelClusterV1Beta2Status struct { 28 | // conditions represents the observations of an IntelCluster's current state. 29 | // Known condition types are Ready, Provisioned, BootstrapExecSucceeded, Deleting, Paused. 30 | // +optional 31 | // +listType=map 32 | // +listMapKey=type 33 | // +kubebuilder:validation:MaxItems=32 34 | Conditions []metav1.Condition `json:"conditions,omitempty"` 35 | } 36 | 37 | // IntelClusterStatus defines the observed state of IntelCluster 38 | type IntelClusterStatus struct { 39 | // ready denotes that the Intel cluster infrastructure is fully provisioned 40 | // NOTE: this field is part of the Cluster API contract and it is used to orchestrate provisioning. 41 | // The value of this field is never updated after provisioning is completed. Please use conditions 42 | // to check the operational state of the infa cluster. 43 | // +optional 44 | Ready bool `json:"ready"` 45 | Conditions clusterv1.Conditions `json:"conditions,omitempty"` 46 | // v1beta2 groups all the fields that will be added or modified in IntelCluster's status with the V1Beta2 version. 47 | // +optional 48 | V1Beta2 *IntelClusterV1Beta2Status `json:"v1beta2,omitempty"` 49 | } 50 | 51 | // +kubebuilder:object:root=true 52 | // +kubebuilder:subresource:status 53 | // +kubebuilder:resource:path=intelclusters,scope=Namespaced,categories=cluster-api 54 | // +kubebuilder:printcolumn:name="Cluster",type="string",JSONPath=".metadata.labels['cluster\\.x-k8s\\.io/cluster-name']",description="Cluster" 55 | // +kubebuilder:printcolumn:name="ProviderId",type="string",JSONPath=".spec.providerId",description="ProviderId associated with the cluster" 56 | // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.ready",description="IntelCluster is ready for IntelMachine" 57 | // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of IntelCluster" 58 | // +kubebuilder:metadata:labels="cluster.x-k8s.io/v1beta1=v1alpha1" 59 | 60 | // IntelCluster is the Schema for the intelclusters API 61 | type IntelCluster struct { 62 | metav1.TypeMeta `json:",inline"` 63 | metav1.ObjectMeta `json:"metadata,omitempty"` 64 | 65 | Spec IntelClusterSpec `json:"spec,omitempty"` 66 | Status IntelClusterStatus `json:"status,omitempty"` 67 | } 68 | 69 | // GetConditions returns the observations of the operational state of the IntelCluster resource. 70 | func (r *IntelCluster) GetConditions() clusterv1.Conditions { 71 | return r.Status.Conditions 72 | } 73 | 74 | // SetConditions sets the underlying service state of the IntelCluster to the predescribed clusterv1.Conditions. 75 | func (r *IntelCluster) SetConditions(conditions clusterv1.Conditions) { 76 | r.Status.Conditions = conditions 77 | } 78 | 79 | // GetV1Beta2Conditions returns the set of conditions for this object. 80 | func (c *IntelCluster) GetV1Beta2Conditions() []metav1.Condition { 81 | if c.Status.V1Beta2 == nil { 82 | return nil 83 | } 84 | return c.Status.V1Beta2.Conditions 85 | } 86 | 87 | // SetV1Beta2Conditions sets conditions for an API object. 88 | func (c *IntelCluster) SetV1Beta2Conditions(conditions []metav1.Condition) { 89 | if c.Status.V1Beta2 == nil { 90 | c.Status.V1Beta2 = &IntelClusterV1Beta2Status{} 91 | } 92 | c.Status.V1Beta2.Conditions = conditions 93 | } 94 | 95 | // +kubebuilder:object:root=true 96 | 97 | // IntelClusterList contains a list of IntelCluster 98 | type IntelClusterList struct { 99 | metav1.TypeMeta `json:",inline"` 100 | metav1.ListMeta `json:"metadata,omitempty"` 101 | Items []IntelCluster `json:"items"` 102 | } 103 | 104 | func init() { 105 | SchemeBuilder.Register(&IntelCluster{}, &IntelClusterList{}) 106 | } 107 | -------------------------------------------------------------------------------- /api/v1alpha1/intelclustertemplate_types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 9 | ) 10 | 11 | // IntelClusterTemplateSpec defines the desired state of IntelClusterTemplate 12 | type IntelClusterTemplateSpec struct { 13 | Template IntelClusterTemplateResource `json:"template"` 14 | } 15 | 16 | // +kubebuilder:object:root=true 17 | // +kubebuilder:resource:path=intelclustertemplates,scope=Namespaced,categories=cluster-api 18 | // +kubebuilder:storageversion 19 | // +kubebuilder:metadata:labels="cluster.x-k8s.io/v1beta1=v1alpha1" 20 | 21 | // IntelClusterTemplate is the Schema for the intelclustertemplates API 22 | type IntelClusterTemplate struct { 23 | metav1.TypeMeta `json:",inline"` 24 | metav1.ObjectMeta `json:"metadata,omitempty"` 25 | 26 | Spec IntelClusterTemplateSpec `json:"spec,omitempty"` 27 | } 28 | 29 | // +kubebuilder:object:root=true 30 | 31 | // IntelClusterTemplateList contains a list of IntelClusterTemplate 32 | type IntelClusterTemplateList struct { 33 | metav1.TypeMeta `json:",inline"` 34 | metav1.ListMeta `json:"metadata,omitempty"` 35 | Items []IntelClusterTemplate `json:"items"` 36 | } 37 | 38 | func init() { 39 | SchemeBuilder.Register(&IntelClusterTemplate{}, &IntelClusterTemplateList{}) 40 | } 41 | 42 | type IntelClusterTemplateResource struct { 43 | // Standard object's metadata 44 | // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata 45 | // +optional 46 | ObjectMeta clusterv1.ObjectMeta `json:"metadata,omitempty"` 47 | Spec IntelClusterSpec `json:"spec"` 48 | } 49 | -------------------------------------------------------------------------------- /api/v1alpha1/intelmachinebinding_types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | // IntelMachineBindingSpec defines the desired state of IntelMachineBinding. 11 | type IntelMachineBindingSpec struct { 12 | // NodeGUID contains the GUID of the node 13 | NodeGUID string `json:"nodeGUID"` 14 | 15 | // ClusterName contains the name of the cluster to which the node is bound 16 | ClusterName string `json:"clusterName"` 17 | 18 | // IntelMachineTemplateName contains the name of the IntelMachineTemplate for the node 19 | IntelMachineTemplateName string `json:"intelMachineTemplateName"` 20 | } 21 | 22 | // IntelMachineBindingStatus defines the observed state of IntelMachineBinding. 23 | type IntelMachineBindingStatus struct { 24 | // Allocated denotes that the node has been allocated to the cluster 25 | Allocated bool `json:"allocated,omitempty"` 26 | } 27 | 28 | // +kubebuilder:object:root=true 29 | // +kubebuilder:subresource:status 30 | // +kubebuilder:printcolumn:name="Cluster Name",type=string,JSONPath=`.spec.clusterName` 31 | // +kubebuilder:printcolumn:name="Node GUID",type=string,JSONPath=`.spec.nodeGUID` 32 | // +kubebuilder:printcolumn:name="Template Name",type=string,JSONPath=`.spec.intelMachineTemplateName` 33 | // +kubebuilder:printcolumn:name="Allocated",type=boolean,JSONPath=`.status.allocated` 34 | 35 | // IntelMachineBinding is the Schema for the intelmachinebindings API. 36 | type IntelMachineBinding struct { 37 | metav1.TypeMeta `json:",inline"` 38 | metav1.ObjectMeta `json:"metadata,omitempty"` 39 | 40 | Spec IntelMachineBindingSpec `json:"spec,omitempty"` 41 | Status IntelMachineBindingStatus `json:"status,omitempty"` 42 | } 43 | 44 | // +kubebuilder:object:root=true 45 | 46 | // IntelMachineBindingList contains a list of IntelMachineBinding. 47 | type IntelMachineBindingList struct { 48 | metav1.TypeMeta `json:",inline"` 49 | metav1.ListMeta `json:"metadata,omitempty"` 50 | Items []IntelMachineBinding `json:"items"` 51 | } 52 | 53 | func init() { 54 | SchemeBuilder.Register(&IntelMachineBinding{}, &IntelMachineBindingList{}) 55 | } 56 | -------------------------------------------------------------------------------- /api/v1alpha1/intelmachinetemplate_types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package v1alpha1 5 | 6 | import ( 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | ) 9 | 10 | type IntelMachineTemplateSpecTemplate struct { 11 | Spec IntelMachineSpec `json:"spec,omitempty"` 12 | } 13 | 14 | // IntelMachineTemplateSpec defines the desired state of IntelMachineTemplate. 15 | // The Spec.Template field must be present in order to satisfy cAPI. 16 | type IntelMachineTemplateSpec struct { 17 | Template IntelMachineTemplateSpecTemplate `json:"template"` 18 | } 19 | 20 | // IntelMachineTemplateStatus defines the observed state of IntelMachineTemplate. 21 | type IntelMachineTemplateStatus struct { 22 | } 23 | 24 | // +kubebuilder:object:root=true 25 | // +kubebuilder:subresource:status 26 | // +kubebuilder:metadata:labels="cluster.x-k8s.io/v1beta1=v1alpha1" 27 | 28 | // IntelMachineTemplate is the Schema for the intelmachinetemplates API. 29 | type IntelMachineTemplate struct { 30 | metav1.TypeMeta `json:",inline"` 31 | metav1.ObjectMeta `json:"metadata,omitempty"` 32 | 33 | Spec IntelMachineTemplateSpec `json:"spec,omitempty"` 34 | Status IntelMachineTemplateStatus `json:"status,omitempty"` 35 | } 36 | 37 | // +kubebuilder:object:root=true 38 | 39 | // IntelMachineTemplateList contains a list of IntelMachineTemplate. 40 | type IntelMachineTemplateList struct { 41 | metav1.TypeMeta `json:",inline"` 42 | metav1.ListMeta `json:"metadata,omitempty"` 43 | Items []IntelMachineTemplate `json:"items"` 44 | } 45 | 46 | func init() { 47 | SchemeBuilder.Register(&IntelMachineTemplate{}, &IntelMachineTemplateList{}) 48 | } 49 | -------------------------------------------------------------------------------- /cmd/southbound/main.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | 5 | package main 6 | 7 | import ( 8 | "context" 9 | "os" 10 | "os/signal" 11 | "syscall" 12 | 13 | config "github.com/open-edge-platform/cluster-api-provider-intel/internal/southboundconfig" 14 | grpcserver "github.com/open-edge-platform/cluster-api-provider-intel/internal/southboundserver" 15 | 16 | "google.golang.org/grpc" 17 | 18 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 19 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/tracing" 20 | ) 21 | 22 | var log = logging.GetLogger("main") 23 | var traceCleanupFunc func(context.Context) error 24 | 25 | const ( 26 | RbacRealmDirectory = "./internal/rego/authz.rego" 27 | ) 28 | 29 | func handleSignal(grpcServer *grpc.Server) { 30 | sigCh := make(chan os.Signal, 1) 31 | signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) 32 | sig := <-sigCh 33 | log.Info().Msgf("received signal: %v, stopping gRPC Southbound Handler", sig) 34 | grpcServer.Stop() 35 | if traceCleanupFunc != nil { 36 | err := traceCleanupFunc(context.Background()) 37 | if err != nil { 38 | log.Err(err).Msg("Error in tracing cleanup") 39 | } 40 | } 41 | } 42 | 43 | func setTracing(traceURL string) func(context.Context) error { 44 | cleanup, exportErr := tracing.NewTraceExporterGRPC(traceURL, config.TracingServiceName, nil) 45 | if exportErr != nil { 46 | log.Err(exportErr).Msg("Error creating trace exporter") 47 | } 48 | if cleanup != nil { 49 | log.Info().Msg("Tracing enabled") 50 | } else { 51 | log.Info().Msg("Tracing disabled") 52 | } 53 | return cleanup 54 | } 55 | 56 | func main() { 57 | cfg := config.ParseInputArg() 58 | if err := config.ValidateConfig(cfg); err != nil { 59 | log.Fatal().Err(err).Msgf("error validating config") 60 | } 61 | 62 | if cfg.EnableTracing { 63 | traceCleanupFunc = setTracing(cfg.TraceURL) 64 | } 65 | 66 | // Start gRPC server to handle RPCs from Edge Node 67 | gServer, listener := grpcserver.NewGrpcServer(*cfg, RbacRealmDirectory) 68 | if gServer == nil { 69 | log.Fatal().Msg("Failed to create grpc server") 70 | } 71 | log.Info().Msg("Starting gRPC Southbound Handler") 72 | go func() { 73 | if err := grpcserver.RunGrpcServer(gServer, listener); err != nil { 74 | log.Fatal().Msgf("Failed to run grpc server: %v", err) 75 | } 76 | }() 77 | 78 | // Start routine to handle any interrupt signals 79 | handleSignal(gServer) 80 | log.Info().Msg("Exiting gRPC Southbound Handler") 81 | } 82 | -------------------------------------------------------------------------------- /config/crd/bases/infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.4 7 | labels: 8 | cluster.x-k8s.io/v1beta1: v1alpha1 9 | name: intelclustertemplates.infrastructure.cluster.x-k8s.io 10 | spec: 11 | group: infrastructure.cluster.x-k8s.io 12 | names: 13 | categories: 14 | - cluster-api 15 | kind: IntelClusterTemplate 16 | listKind: IntelClusterTemplateList 17 | plural: intelclustertemplates 18 | singular: intelclustertemplate 19 | scope: Namespaced 20 | versions: 21 | - name: v1alpha1 22 | schema: 23 | openAPIV3Schema: 24 | description: IntelClusterTemplate is the Schema for the intelclustertemplates 25 | API 26 | properties: 27 | apiVersion: 28 | description: |- 29 | APIVersion defines the versioned schema of this representation of an object. 30 | Servers should convert recognized schemas to the latest internal value, and 31 | may reject unrecognized values. 32 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 33 | type: string 34 | kind: 35 | description: |- 36 | Kind is a string value representing the REST resource this object represents. 37 | Servers may infer this from the endpoint the client submits requests to. 38 | Cannot be updated. 39 | In CamelCase. 40 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 41 | type: string 42 | metadata: 43 | type: object 44 | spec: 45 | description: IntelClusterTemplateSpec defines the desired state of IntelClusterTemplate 46 | properties: 47 | template: 48 | properties: 49 | metadata: 50 | description: |- 51 | Standard object's metadata 52 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata 53 | properties: 54 | annotations: 55 | additionalProperties: 56 | type: string 57 | description: |- 58 | annotations is an unstructured key value map stored with a resource that may be 59 | set by external tools to store and retrieve arbitrary metadata. They are not 60 | queryable and should be preserved when modifying objects. 61 | More info: http://kubernetes.io/docs/user-guide/annotations 62 | type: object 63 | labels: 64 | additionalProperties: 65 | type: string 66 | description: |- 67 | Map of string keys and values that can be used to organize and categorize 68 | (scope and select) objects. May match selectors of replication controllers 69 | and services. 70 | More info: http://kubernetes.io/docs/user-guide/labels 71 | type: object 72 | type: object 73 | spec: 74 | description: IntelClusterSpec defines the desired state of IntelCluster 75 | properties: 76 | controlPlaneEndpoint: 77 | description: controlPlaneEndpoint represents the endpoint 78 | used to communicate with the control plane 79 | properties: 80 | host: 81 | description: The hostname on which the API server is serving. 82 | type: string 83 | port: 84 | description: The port on which the API server is serving. 85 | format: int32 86 | type: integer 87 | required: 88 | - host 89 | - port 90 | type: object 91 | providerId: 92 | description: providerId represents the id the inventory manager 93 | assigns to the cluster at creation time 94 | type: string 95 | type: object 96 | required: 97 | - spec 98 | type: object 99 | required: 100 | - template 101 | type: object 102 | type: object 103 | served: true 104 | storage: true 105 | -------------------------------------------------------------------------------- /config/crd/bases/infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.4 7 | name: intelmachinebindings.infrastructure.cluster.x-k8s.io 8 | spec: 9 | group: infrastructure.cluster.x-k8s.io 10 | names: 11 | kind: IntelMachineBinding 12 | listKind: IntelMachineBindingList 13 | plural: intelmachinebindings 14 | singular: intelmachinebinding 15 | scope: Namespaced 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.clusterName 19 | name: Cluster Name 20 | type: string 21 | - jsonPath: .spec.nodeGUID 22 | name: Node GUID 23 | type: string 24 | - jsonPath: .spec.intelMachineTemplateName 25 | name: Template Name 26 | type: string 27 | - jsonPath: .status.allocated 28 | name: Allocated 29 | type: boolean 30 | name: v1alpha1 31 | schema: 32 | openAPIV3Schema: 33 | description: IntelMachineBinding is the Schema for the intelmachinebindings 34 | API. 35 | properties: 36 | apiVersion: 37 | description: |- 38 | APIVersion defines the versioned schema of this representation of an object. 39 | Servers should convert recognized schemas to the latest internal value, and 40 | may reject unrecognized values. 41 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 42 | type: string 43 | kind: 44 | description: |- 45 | Kind is a string value representing the REST resource this object represents. 46 | Servers may infer this from the endpoint the client submits requests to. 47 | Cannot be updated. 48 | In CamelCase. 49 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 50 | type: string 51 | metadata: 52 | type: object 53 | spec: 54 | description: IntelMachineBindingSpec defines the desired state of IntelMachineBinding. 55 | properties: 56 | clusterName: 57 | description: ClusterName contains the name of the cluster to which 58 | the node is bound 59 | type: string 60 | intelMachineTemplateName: 61 | description: IntelMachineTemplateName contains the name of the IntelMachineTemplate 62 | for the node 63 | type: string 64 | nodeGUID: 65 | description: NodeGUID contains the GUID of the node 66 | type: string 67 | required: 68 | - clusterName 69 | - intelMachineTemplateName 70 | - nodeGUID 71 | type: object 72 | status: 73 | description: IntelMachineBindingStatus defines the observed state of IntelMachineBinding. 74 | properties: 75 | allocated: 76 | description: Allocated denotes that the node has been allocated to 77 | the cluster 78 | type: boolean 79 | type: object 80 | type: object 81 | served: true 82 | storage: true 83 | subresources: 84 | status: {} 85 | -------------------------------------------------------------------------------- /config/crd/bases/infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.16.4 7 | labels: 8 | cluster.x-k8s.io/v1beta1: v1alpha1 9 | name: intelmachinetemplates.infrastructure.cluster.x-k8s.io 10 | spec: 11 | group: infrastructure.cluster.x-k8s.io 12 | names: 13 | kind: IntelMachineTemplate 14 | listKind: IntelMachineTemplateList 15 | plural: intelmachinetemplates 16 | singular: intelmachinetemplate 17 | scope: Namespaced 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: IntelMachineTemplate is the Schema for the intelmachinetemplates 23 | API. 24 | properties: 25 | apiVersion: 26 | description: |- 27 | APIVersion defines the versioned schema of this representation of an object. 28 | Servers should convert recognized schemas to the latest internal value, and 29 | may reject unrecognized values. 30 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources 31 | type: string 32 | kind: 33 | description: |- 34 | Kind is a string value representing the REST resource this object represents. 35 | Servers may infer this from the endpoint the client submits requests to. 36 | Cannot be updated. 37 | In CamelCase. 38 | More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds 39 | type: string 40 | metadata: 41 | type: object 42 | spec: 43 | description: |- 44 | IntelMachineTemplateSpec defines the desired state of IntelMachineTemplate. 45 | The Spec.Template field must be present in order to satisfy cAPI. 46 | properties: 47 | template: 48 | properties: 49 | spec: 50 | description: IntelMachineSpec defines the desired state of IntelMachine. 51 | properties: 52 | nodeGUID: 53 | description: NodeGUID contains the GUID of the node. 54 | type: string 55 | providerID: 56 | description: ProviderID must match the provider ID as seen 57 | on the node object corresponding to this machine. 58 | type: string 59 | type: object 60 | type: object 61 | required: 62 | - template 63 | type: object 64 | status: 65 | description: IntelMachineTemplateStatus defines the observed state of 66 | IntelMachineTemplate. 67 | type: object 68 | type: object 69 | served: true 70 | storage: true 71 | subresources: 72 | status: {} 73 | -------------------------------------------------------------------------------- /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 | commonLabels: 5 | cluster.x-k8s.io/v1beta1: v1alpha1 6 | 7 | resources: 8 | - bases/infrastructure.cluster.x-k8s.io_intelmachines.yaml 9 | - bases/infrastructure.cluster.x-k8s.io_intelclusters.yaml 10 | - bases/infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml 11 | - bases/infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml 12 | - bases/infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml 13 | - bases/infrastructure.cluster.x-k8s.io_clusterconnections.yaml 14 | # +kubebuilder:scaffold:crdkustomizeresource 15 | 16 | patches: 17 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 18 | # patches here are for enabling the conversion webhook for each CRD 19 | # +kubebuilder:scaffold:crdkustomizewebhookpatch 20 | 21 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 22 | # patches here are for enabling the CA injection for each CRD 23 | # +kubebuilder:scaffold:crdkustomizecainjectionpatch 24 | 25 | # [WEBHOOK] To enable webhook, uncomment the following section 26 | # the following config is for teaching kustomize how to do kustomization for CRDs. 27 | #configurations: 28 | #- kustomizeconfig.yaml 29 | -------------------------------------------------------------------------------- /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/default/manager_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch adds the args to allow exposing the metrics endpoint using HTTPS 2 | - op: add 3 | path: /spec/template/spec/containers/0/args/0 4 | value: --metrics-bind-address=:8443 5 | -------------------------------------------------------------------------------- /config/default/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: controller-manager-metrics-service 9 | namespace: system 10 | spec: 11 | ports: 12 | - name: https 13 | port: 8443 14 | protocol: TCP 15 | targetPort: 8443 16 | selector: 17 | control-plane: controller-manager 18 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: capi-provider-intel-manager 8 | newTag: 0.2.0 9 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: system 9 | --- 10 | apiVersion: apps/v1 11 | kind: Deployment 12 | metadata: 13 | name: controller-manager 14 | namespace: system 15 | labels: 16 | control-plane: controller-manager 17 | app.kubernetes.io/name: cluster-api-provider-intel 18 | app.kubernetes.io/managed-by: kustomize 19 | spec: 20 | selector: 21 | matchLabels: 22 | control-plane: controller-manager 23 | replicas: 1 24 | template: 25 | metadata: 26 | annotations: 27 | kubectl.kubernetes.io/default-container: manager 28 | labels: 29 | control-plane: controller-manager 30 | spec: 31 | # TODO(user): Uncomment the following code to configure the nodeAffinity expression 32 | # according to the platforms which are supported by your solution. 33 | # It is considered best practice to support multiple architectures. You can 34 | # build your manager image using the makefile target docker-buildx. 35 | # affinity: 36 | # nodeAffinity: 37 | # requiredDuringSchedulingIgnoredDuringExecution: 38 | # nodeSelectorTerms: 39 | # - matchExpressions: 40 | # - key: kubernetes.io/arch 41 | # operator: In 42 | # values: 43 | # - amd64 44 | # - arm64 45 | # - ppc64le 46 | # - s390x 47 | # - key: kubernetes.io/os 48 | # operator: In 49 | # values: 50 | # - linux 51 | securityContext: 52 | runAsNonRoot: true 53 | # TODO(user): For common cases that do not require escalating privileges 54 | # it is recommended to ensure that all your Pods/Containers are restrictive. 55 | # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 56 | # Please uncomment the following code if your project does NOT have to work on old Kubernetes 57 | # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ). 58 | # seccompProfile: 59 | # type: RuntimeDefault 60 | containers: 61 | - command: 62 | - /manager 63 | args: 64 | - --leader-elect 65 | - --health-probe-bind-address=:8081 66 | image: controller:latest 67 | name: manager 68 | securityContext: 69 | allowPrivilegeEscalation: false 70 | readOnlyRootFilesystem: true 71 | capabilities: 72 | drop: 73 | - "ALL" 74 | livenessProbe: 75 | httpGet: 76 | path: /healthz 77 | port: 8081 78 | initialDelaySeconds: 15 79 | periodSeconds: 20 80 | readinessProbe: 81 | httpGet: 82 | path: /readyz 83 | port: 8081 84 | initialDelaySeconds: 5 85 | periodSeconds: 10 86 | # TODO(user): Configure the resources accordingly based on the project requirements. 87 | # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 88 | resources: 89 | limits: 90 | cpu: 500m 91 | memory: 128Mi 92 | requests: 93 | cpu: 10m 94 | memory: 64Mi 95 | serviceAccountName: controller-manager 96 | terminationGracePeriodSeconds: 10 97 | -------------------------------------------------------------------------------- /config/network-policy/allow-metrics-traffic.yaml: -------------------------------------------------------------------------------- 1 | # This NetworkPolicy allows ingress traffic 2 | # with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those 3 | # namespaces are able to gathering data from the metrics endpoint. 4 | apiVersion: networking.k8s.io/v1 5 | kind: NetworkPolicy 6 | metadata: 7 | labels: 8 | app.kubernetes.io/name: cluster-api-provider-intel 9 | app.kubernetes.io/managed-by: kustomize 10 | name: allow-metrics-traffic 11 | namespace: system 12 | spec: 13 | podSelector: 14 | matchLabels: 15 | control-plane: controller-manager 16 | policyTypes: 17 | - Ingress 18 | ingress: 19 | # This allows ingress traffic from any namespace with the label metrics: enabled 20 | - from: 21 | - namespaceSelector: 22 | matchLabels: 23 | metrics: enabled # Only from namespaces with this label 24 | ports: 25 | - port: 8443 26 | protocol: TCP 27 | -------------------------------------------------------------------------------- /config/network-policy/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - allow-metrics-traffic.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | # Prometheus Monitor Service (Metrics) 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | labels: 6 | control-plane: controller-manager 7 | app.kubernetes.io/name: cluster-api-provider-intel 8 | app.kubernetes.io/managed-by: kustomize 9 | name: controller-manager-metrics-monitor 10 | namespace: system 11 | spec: 12 | endpoints: 13 | - path: /metrics 14 | port: https # Ensure this is the name of the port that exposes HTTPS metrics 15 | scheme: https 16 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 17 | tlsConfig: 18 | # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables 19 | # certificate verification. This poses a significant security risk by making the system vulnerable to 20 | # man-in-the-middle attacks, where an attacker could intercept and manipulate the communication between 21 | # Prometheus and the monitored services. This could lead to unauthorized access to sensitive metrics data, 22 | # compromising the integrity and confidentiality of the information. 23 | # Please use the following options for secure configurations: 24 | # caFile: /etc/metrics-certs/ca.crt 25 | # certFile: /etc/metrics-certs/tls.crt 26 | # keyFile: /etc/metrics-certs/tls.key 27 | insecureSkipVerify: true 28 | selector: 29 | matchLabels: 30 | control-plane: controller-manager 31 | -------------------------------------------------------------------------------- /config/rbac/clusterconnection_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit clusterconnections. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: clusterconnection-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - clusterconnections 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - clusterconnections/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/clusterconnection_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view clusterconnections. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: clusterconnection-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - clusterconnections 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - clusterconnections/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/intelcluster_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit intelclusters. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelcluster-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelclusters 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - intelclusters/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/intelcluster_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view intelclusters. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelcluster-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelclusters 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - intelclusters/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/intelclustertemplate_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit intelclustertemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelclustertemplate-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelclustertemplates 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - intelclustertemplates/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/intelclustertemplate_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view intelclustertemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelclustertemplate-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelclustertemplates 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - intelclustertemplates/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/intelmachine_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit intelmachines. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachine-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachines 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - intelmachines/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/intelmachine_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view intelmachines. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachine-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachines 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - intelmachines/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/intelmachinebinding_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit intelmachinebindings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachinebinding-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachinebindings 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - intelmachinebindings/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/intelmachinebinding_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view intelmachinebindings. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachinebinding-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachinebindings 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - intelmachinebindings/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /config/rbac/intelmachinetemplate_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit intelmachinetemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachinetemplate-editor-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachinetemplates 14 | verbs: 15 | - create 16 | - delete 17 | - get 18 | - list 19 | - patch 20 | - update 21 | - watch 22 | - apiGroups: 23 | - infrastructure.cluster.x-k8s.io 24 | resources: 25 | - intelmachinetemplates/status 26 | verbs: 27 | - get 28 | -------------------------------------------------------------------------------- /config/rbac/intelmachinetemplate_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view intelmachinetemplates. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: intelmachinetemplate-viewer-role 9 | rules: 10 | - apiGroups: 11 | - infrastructure.cluster.x-k8s.io 12 | resources: 13 | - intelmachinetemplates 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - infrastructure.cluster.x-k8s.io 20 | resources: 21 | - intelmachinetemplates/status 22 | verbs: 23 | - get 24 | -------------------------------------------------------------------------------- /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 | # The following RBAC configurations are used to protect 13 | # the metrics endpoint with authn/authz. These configurations 14 | # ensure that only authorized users and service accounts 15 | # can access the metrics endpoint. Comment the following 16 | # permissions if you want to disable this protection. 17 | # More info: https://book.kubebuilder.io/reference/metrics.html 18 | - metrics_auth_role.yaml 19 | - metrics_auth_role_binding.yaml 20 | - metrics_reader_role.yaml 21 | # For each CRD, "Editor" and "Viewer" roles are scaffolded by 22 | # default, aiding admins in cluster management. Those roles are 23 | # not used by the Project itself. You can comment the following lines 24 | # if you do not want those helpers be installed with your Project. 25 | - clusterconnection_editor_role.yaml 26 | - clusterconnection_viewer_role.yaml 27 | - intelmachinebinding_editor_role.yaml 28 | - intelmachinebinding_viewer_role.yaml 29 | - intelmachinetemplate_editor_role.yaml 30 | - intelmachinetemplate_viewer_role.yaml 31 | - intelclustertemplate_editor_role.yaml 32 | - intelclustertemplate_viewer_role.yaml 33 | - intelcluster_editor_role.yaml 34 | - intelcluster_viewer_role.yaml 35 | - intelmachine_editor_role.yaml 36 | - intelmachine_viewer_role.yaml 37 | 38 | -------------------------------------------------------------------------------- /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 | labels: 6 | app.kubernetes.io/name: cluster-api-provider-intel 7 | app.kubernetes.io/managed-by: kustomize 8 | name: leader-election-role 9 | rules: 10 | - apiGroups: 11 | - "" 12 | resources: 13 | - configmaps 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - create 19 | - update 20 | - patch 21 | - delete 22 | - apiGroups: 23 | - coordination.k8s.io 24 | resources: 25 | - leases 26 | verbs: 27 | - get 28 | - list 29 | - watch 30 | - create 31 | - update 32 | - patch 33 | - delete 34 | - apiGroups: 35 | - "" 36 | resources: 37 | - events 38 | verbs: 39 | - create 40 | - patch 41 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: leader-election-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: Role 11 | name: leader-election-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /config/rbac/metrics_auth_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-auth-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/metrics_auth_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: metrics-auth-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: metrics-auth-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/metrics_reader_role.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/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: manager-role 6 | rules: 7 | - apiGroups: 8 | - cluster.x-k8s.io 9 | resources: 10 | - clusters 11 | - clusters/status 12 | - machines 13 | - machines/status 14 | verbs: 15 | - get 16 | - list 17 | - watch 18 | - apiGroups: 19 | - edge-orchestrator.intel.com 20 | resources: 21 | - clusterconnects 22 | verbs: 23 | - create 24 | - delete 25 | - get 26 | - list 27 | - patch 28 | - update 29 | - watch 30 | - apiGroups: 31 | - infrastructure.cluster.x-k8s.io 32 | resources: 33 | - intelclusters 34 | - intelmachinebindings 35 | - intelmachines 36 | verbs: 37 | - create 38 | - delete 39 | - get 40 | - list 41 | - patch 42 | - update 43 | - watch 44 | - apiGroups: 45 | - infrastructure.cluster.x-k8s.io 46 | resources: 47 | - intelclusters/finalizers 48 | - intelmachines/finalizers 49 | verbs: 50 | - update 51 | - apiGroups: 52 | - infrastructure.cluster.x-k8s.io 53 | resources: 54 | - intelclusters/status 55 | verbs: 56 | - get 57 | - list 58 | - patch 59 | - update 60 | - watch 61 | - apiGroups: 62 | - infrastructure.cluster.x-k8s.io 63 | resources: 64 | - intelmachinebindings/status 65 | - intelmachines/status 66 | verbs: 67 | - get 68 | - patch 69 | - update 70 | - apiGroups: 71 | - infrastructure.cluster.x-k8s.io 72 | resources: 73 | - intelmachinetemplates 74 | verbs: 75 | - get 76 | - list 77 | - watch 78 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: manager-rolebinding 8 | roleRef: 9 | apiGroup: rbac.authorization.k8s.io 10 | kind: ClusterRole 11 | name: manager-role 12 | subjects: 13 | - kind: ServiceAccount 14 | name: controller-manager 15 | namespace: system 16 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: controller-manager 8 | namespace: system 9 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_clusterconnection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: ClusterConnection 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: clusterconnection-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_intelcluster.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: IntelCluster 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: intelcluster-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_intelclustertemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: IntelClusterTemplate 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: intelclustertemplate-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_intelmachine.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: IntelMachine 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: intelmachine-sample 8 | spec: 9 | nodeGUID: 12345678-1234-1234-1234-123456789012 10 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_intelmachinebinding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: IntelMachineBinding 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: intelmachinebinding-sample 8 | spec: 9 | nodeGUID: 12345678-1234-1234-1234-123456789012 10 | clusterName: intelcluster-sample 11 | intelMachineTemplateName: intelmachinetemplate-sample 12 | -------------------------------------------------------------------------------- /config/samples/infrastructure_v1alpha1_intelmachinetemplate.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 2 | kind: IntelMachineTemplate 3 | metadata: 4 | labels: 5 | app.kubernetes.io/name: cluster-api-provider-intel 6 | app.kubernetes.io/managed-by: kustomize 7 | name: intelmachinetemplate-sample 8 | spec: 9 | # TODO(user): Add fields here 10 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples of your project ## 2 | resources: 3 | - infrastructure_v1alpha1_intelmachine.yaml 4 | - infrastructure_v1alpha1_intelcluster.yaml 5 | - infrastructure_v1alpha1_intelmachinetemplate.yaml 6 | - infrastructure_v1alpha1_intelmachinebinding.yaml 7 | - infrastructure_v1alpha1_intelclustertemplate.yaml 8 | - infrastructure_v1alpha1_clusterconnection.yaml 9 | # +kubebuilder:scaffold:manifestskustomizesamples 10 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/.helmignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Patterns to ignore when building packages. 5 | # This supports shell glob matching, relative path matching, and 6 | # negation (prefixed with !). Only one pattern per line. 7 | .DS_Store 8 | # Common VCS dirs 9 | .git/ 10 | .gitignore 11 | .bzr/ 12 | .bzrignore 13 | .hg/ 14 | .hgignore 15 | .svn/ 16 | # Common backup files 17 | *.swp 18 | *.bak 19 | *.tmp 20 | *.orig 21 | *~ 22 | # Various IDEs 23 | .project 24 | .idea/ 25 | *.tmproj 26 | .vscode/ 27 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/Chart.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: v2 5 | name: intel-infra-provider-crds 6 | description: Custom Resource Definitions for Intel Cluster API Provider 7 | type: application 8 | version: 1.2.0 9 | appVersion: 1.2.0 10 | annotations: 11 | revision: cec78684af2dd2146c033e7b65575d44fc189d96 12 | created: "2025-05-23T21:37:06Z" 13 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/templates/infrastructure.cluster.x-k8s.io_intelclusters.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/infrastructure.cluster.x-k8s.io_intelclusters.yaml -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/templates/infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/infrastructure.cluster.x-k8s.io_intelclustertemplates.yaml -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/templates/infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/infrastructure.cluster.x-k8s.io_intelmachinebindings.yaml -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/templates/infrastructure.cluster.x-k8s.io_intelmachines.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/infrastructure.cluster.x-k8s.io_intelmachines.yaml -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/templates/infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml: -------------------------------------------------------------------------------- 1 | ../../../../config/crd/bases/infrastructure.cluster.x-k8s.io_intelmachinetemplates.yaml -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider-crds/values.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Intentionally left empty -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/.helmignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # Patterns to ignore when building packages. 5 | # This supports shell glob matching, relative path matching, and 6 | # negation (prefixed with !). Only one pattern per line. 7 | .DS_Store 8 | # Common VCS dirs 9 | .git/ 10 | .gitignore 11 | .bzr/ 12 | .bzrignore 13 | .hg/ 14 | .hgignore 15 | .svn/ 16 | # Common backup files 17 | *.swp 18 | *.bak 19 | *.tmp 20 | *.orig 21 | *~ 22 | # Various IDEs 23 | .project 24 | .idea/ 25 | *.tmproj 26 | .vscode/ 27 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/Chart.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: v2 5 | name: intel-infra-provider 6 | description: The Intel Cluster API Provider 7 | type: application 8 | version: 1.2.0 9 | appVersion: 1.2.0 10 | annotations: 11 | revision: cec78684af2dd2146c033e7b65575d44fc189d96 12 | created: "2025-05-23T21:37:06Z" 13 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | {{/* 5 | Expand the name of the chart. 6 | */}} 7 | {{- define "intel-infra-provider.name" -}} 8 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} 9 | {{- end }} 10 | 11 | {{/* 12 | Create a default fully qualified app name. 13 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 14 | If release name contains chart name it will be used as a full name. 15 | */}} 16 | {{- define "intel-infra-provider.fullname" -}} 17 | {{- if .Values.fullnameOverride }} 18 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} 19 | {{- else }} 20 | {{- $name := default .Chart.Name .Values.nameOverride }} 21 | {{- if contains $name .Release.Name }} 22 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 23 | {{- else }} 24 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 25 | {{- end }} 26 | {{- end }} 27 | {{- end }} 28 | 29 | {{/* 30 | Create chart name and version as used by the chart label. 31 | */}} 32 | {{- define "intel-infra-provider.chart" -}} 33 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 34 | {{- end }} 35 | 36 | {{/* 37 | Common labels 38 | */}} 39 | {{- define "intel-infra-provider.labels" -}} 40 | helm.sh/chart: {{ include "intel-infra-provider.chart" . }} 41 | {{ include "intel-infra-provider.selectorLabels" . }} 42 | {{- if .Chart.AppVersion }} 43 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 44 | {{- end }} 45 | app.kubernetes.io/managed-by: {{ .Release.Service }} 46 | {{- end }} 47 | 48 | {{/* 49 | Selector labels 50 | */}} 51 | {{- define "intel-infra-provider.selectorLabels" -}} 52 | app.kubernetes.io/name: {{ include "intel-infra-provider.name" . }} 53 | app.kubernetes.io/instance: {{ .Release.Name }} 54 | {{- end }} 55 | 56 | {{/* 57 | manager labels 58 | */}} 59 | {{- define "intel-infra-provider.managerLabels" -}} 60 | {{ include "intel-infra-provider.selectorLabels" . }} 61 | {{- with .Values.manager.labels }} 62 | {{- toYaml . }} 63 | {{- end }} 64 | {{- end }} 65 | 66 | {{/* 67 | manager pod labels 68 | */}} 69 | {{- define "intel-infra-provider.managerPodLabels" -}} 70 | {{ include "intel-infra-provider.selectorLabels" . }} 71 | {{- with .Values.manager.podLabels }} 72 | {{ toYaml . }} 73 | {{- end }} 74 | {{- end }} 75 | 76 | {{/* 77 | southbound api labels 78 | */}} 79 | {{- define "intel-infra-provider.southboundLabels" -}} 80 | {{ include "intel-infra-provider.selectorLabels" . }} 81 | {{- with .Values.southboundApi.labels }} 82 | {{- toYaml . }} 83 | {{- end }} 84 | {{- end }} 85 | 86 | {{/* 87 | southbound api pod labels 88 | */}} 89 | {{- define "intel-infra-provider.southboundPodLabels" -}} 90 | {{ include "intel-infra-provider.selectorLabels" . }} 91 | {{- with .Values.southboundApi.podLabels }} 92 | {{- toYaml . }} 93 | {{- end }} 94 | {{- end }} 95 | 96 | {{/* 97 | Role labels 98 | */}} 99 | {{- define "intel-infra-provider.roleLabels" -}} 100 | {{ include "intel-infra-provider.selectorLabels" . }} 101 | {{- with .Values.rbac.labels }} 102 | {{- toYaml . }} 103 | {{- end }} 104 | {{- end }} 105 | 106 | {{/* 107 | Service account labels 108 | */}} 109 | {{- define "intel-infra-provider.serviceAccountLabels" -}} 110 | {{ include "intel-infra-provider.selectorLabels" . }} 111 | {{- with .Values.rbac.serviceAccount.labels }} 112 | {{- toYaml . }} 113 | {{- end }} 114 | {{- end }} 115 | 116 | {{/* 117 | Service monitor labels 118 | */}} 119 | {{- define "intel-infra-provider.serviceMonitorLabels" -}} 120 | {{ include "intel-infra-provider.selectorLabels" . }} 121 | {{- with .Values.metrics.serviceMonitor.labels }} 122 | {{- toYaml . }} 123 | {{- end }} 124 | {{- end }} 125 | 126 | {{/* 127 | Network Policy labels 128 | */}} 129 | {{- define "intel-infra-provider.networkPolicyLabels" -}} 130 | {{ include "intel-infra-provider.selectorLabels" . }} 131 | {{- with .Values.metrics.networkPolicy.labels }} 132 | {{- toYaml . }} 133 | {{- end }} 134 | {{- end }} 135 | 136 | {{/* 137 | Metrics service labels 138 | */}} 139 | {{- define "intel-infra-provider.metricsServiceLabels" -}} 140 | {{ include "intel-infra-provider.selectorLabels" . }} 141 | {{- with .Values.metrics.service.labels }} 142 | {{ toYaml . }} 143 | {{- end }} 144 | {{- end }} 145 | 146 | {{/* 147 | Create the name of the service account to use 148 | */}} 149 | {{- define "intel-infra-provider.serviceAccountName" -}} 150 | {{- if .Values.rbac.serviceAccount.create }} 151 | {{- default (include "intel-infra-provider.fullname" .) .Values.rbac.serviceAccount.name }} 152 | {{- else }} 153 | {{- default "default" .Values.rbac.serviceAccount.name }} 154 | {{- end }} 155 | {{- end }} 156 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | --- 6 | {{- if .Values.ingress.enabled }} 7 | apiVersion: networking.k8s.io/v1 8 | kind: Ingress 9 | metadata: 10 | name: {{template "intel-infra-provider.fullname" .}}-grpc-ingress 11 | namespace: "{{ .Release.Namespace }}" 12 | annotations: 13 | nginx.ingress.kubernetes.io/use-regex: "true" 14 | nginx.ingress.kubernetes.io/backend-protocol: GRPC 15 | nginx.ingress.kubernetes.io/ssl-redirect: "true" 16 | spec: 17 | ingressClassName: "nginx" 18 | rules: 19 | - host: "{{ .Values.ingress.host }}" 20 | http: 21 | paths: 22 | - backend: 23 | service: 24 | name: "grpc" 25 | port: 26 | number: {{ .Values.southboundApi.service.grpc.port }} 27 | path: / 28 | pathType: "Prefix" 29 | {{- if .Values.ingress.tls }} 30 | tls: 31 | {{- range .Values.ingress.tls }} 32 | - hosts: 33 | {{- range .hosts }} 34 | - {{ . | quote }} 35 | {{- end }} 36 | secretName: "{{ .secretName }}" 37 | {{- end }} 38 | {{- end }} 39 | {{- end }} 40 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/manager.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: {{ include "intel-infra-provider.fullname" . }}-manager 8 | labels: 9 | {{- include "intel-infra-provider.managerLabels" . | nindent 4 }} 10 | spec: 11 | replicas: {{ .Values.manager.replicaCount }} 12 | selector: 13 | matchLabels: 14 | {{- include "intel-infra-provider.selectorLabels" . | nindent 6 }} 15 | template: 16 | metadata: 17 | {{- with .Values.manager.podAnnotations }} 18 | annotations: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | labels: 22 | {{- include "intel-infra-provider.managerPodLabels" . | nindent 8 }} 23 | spec: 24 | {{- with .Values.manager.imagePullSecrets }} 25 | imagePullSecrets: 26 | {{- toYaml . | nindent 8 }} 27 | {{- end }} 28 | serviceAccountName: {{ include "intel-infra-provider.serviceAccountName" . }} 29 | securityContext: 30 | {{- toYaml .Values.manager.podSecurityContext | nindent 8 }} 31 | containers: 32 | - name: intel-infra-provider-manager 33 | securityContext: 34 | {{- toYaml .Values.manager.securityContext | nindent 12 }} 35 | image: "{{ .Values.manager.image.registry.name }}/{{ .Values.manager.image.repository }}:{{ .Values.manager.image.tag | default .Chart.AppVersion }}" 36 | imagePullPolicy: {{ .Values.manager.image.pullPolicy }} 37 | command: 38 | - /manager 39 | args: 40 | - --leader-elect 41 | - --health-probe-bind-address=:8081 42 | {{- if .Values.metrics.service.enabled }} 43 | - --metrics-bind-address=:{{ .Values.metrics.service.port }} 44 | - --metrics-secure=false 45 | {{- end }} 46 | - --inventory-endpoint={{ .Values.manager.inventory.endpoint }} 47 | {{- range $key, $value := .Values.manager.extraArgs }} 48 | - --{{ $key }}={{ $value }} 49 | {{- end }} 50 | env: 51 | {{- with .Values.manager.extraEnv }} 52 | {{- toYaml . | nindent 12 }} 53 | {{- end }} 54 | ports: 55 | {{- if .Values.metrics.service.enabled }} 56 | - name: metrics 57 | containerPort: {{ .Values.metrics.service.port }} 58 | protocol: TCP 59 | {{- end }} 60 | livenessProbe: 61 | httpGet: 62 | path: /healthz 63 | port: 8081 64 | initialDelaySeconds: 15 65 | periodSeconds: 20 66 | readinessProbe: 67 | httpGet: 68 | path: /readyz 69 | port: 8081 70 | initialDelaySeconds: 5 71 | periodSeconds: 10 72 | resources: 73 | {{- toYaml .Values.manager.resources | nindent 12 }} 74 | {{- with .Values.manager.nodeSelector }} 75 | nodeSelector: 76 | {{- toYaml . | nindent 8 }} 77 | {{- end }} 78 | {{- with .Values.manager.affinity }} 79 | affinity: 80 | {{- toYaml . | nindent 8 }} 81 | {{- end }} 82 | {{- with .Values.manager.tolerations }} 83 | tolerations: 84 | {{- toYaml . | nindent 8 }} 85 | {{- end }} 86 | terminationGracePeriodSeconds: {{ .Values.manager.terminationGracePeriodSeconds }} 87 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/metrics_service.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | {{ if .Values.metrics.service.enabled -}} 5 | apiVersion: v1 6 | kind: Service 7 | metadata: 8 | name: {{ include "intel-infra-provider.fullname" . }}-metrics 9 | namespace: {{ .Release.Namespace }} 10 | labels: 11 | {{- include "intel-infra-provider.metricsServiceLabels" . | nindent 4 }} 12 | spec: 13 | ports: 14 | - name: metrics 15 | port: {{ .Values.metrics.service.port }} 16 | protocol: TCP 17 | targetPort: {{ .Values.metrics.service.port }} 18 | selector: 19 | {{- include "intel-infra-provider.managerPodLabels" . | nindent 4 -}} 20 | {{- end -}} 21 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/network_policy.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | {{- if .Values.metrics.networkPolicy.enabled -}} 5 | # This NetworkPolicy allows ingress traffic 6 | # with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those 7 | # namespaces are able to gathering data from the metrics endpoint. 8 | apiVersion: networking.k8s.io/v1 9 | kind: NetworkPolicy 10 | metadata: 11 | name: {{ include "intel-infra-provider.fullname" . }} 12 | namespace: {{ .Release.Namespace }} 13 | labels: 14 | {{- include "intel-infra-provider.networkPolicyLabels" . | nindent 4 }} 15 | spec: 16 | podSelector: 17 | matchLabels: 18 | {{- include "intel-infra-provider.selectorLabels" . | nindent 6 }} 19 | policyTypes: 20 | - Ingress 21 | ingress: 22 | # This allows ingress traffic from any namespace with the label metrics: enabled 23 | - from: 24 | - namespaceSelector: 25 | matchLabels: 26 | metrics: enabled # Only from namespaces with this label 27 | ports: 28 | - port: {{ .Values.metrics.service.port }} 29 | protocol: TCP 30 | {{- end -}} 31 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/role.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | --- 5 | apiVersion: rbac.authorization.k8s.io/v1 6 | kind: ClusterRole 7 | metadata: 8 | name: {{ include "intel-infra-provider.fullname" . }}-controller 9 | labels: 10 | {{- include "intel-infra-provider.roleLabels" . | nindent 4 }} 11 | rules: 12 | - apiGroups: 13 | - cluster.x-k8s.io 14 | resources: 15 | - clusters 16 | - clusters/status 17 | - machines 18 | - machines/status 19 | verbs: 20 | - get 21 | - list 22 | - watch 23 | - apiGroups: 24 | - infrastructure.cluster.x-k8s.io 25 | resources: 26 | - intelclusters 27 | - intelmachinebindings 28 | - intelmachines 29 | verbs: 30 | - create 31 | - delete 32 | - get 33 | - list 34 | - patch 35 | - update 36 | - watch 37 | - apiGroups: 38 | - infrastructure.cluster.x-k8s.io 39 | resources: 40 | - intelclusters/finalizers 41 | - intelmachines/finalizers 42 | verbs: 43 | - update 44 | - apiGroups: 45 | - infrastructure.cluster.x-k8s.io 46 | resources: 47 | - intelclusters/status 48 | verbs: 49 | - get 50 | - list 51 | - patch 52 | - update 53 | - watch 54 | - apiGroups: 55 | - infrastructure.cluster.x-k8s.io 56 | resources: 57 | - intelmachinebindings/status 58 | - intelmachines/status 59 | verbs: 60 | - get 61 | - patch 62 | - update 63 | - apiGroups: 64 | - infrastructure.cluster.x-k8s.io 65 | resources: 66 | - intelmachinetemplates 67 | verbs: 68 | - get 69 | - list 70 | - watch 71 | - apiGroups: 72 | - "" 73 | resources: 74 | - configmaps 75 | verbs: 76 | - get 77 | - list 78 | - watch 79 | - create 80 | - update 81 | - patch 82 | - delete 83 | - apiGroups: 84 | - coordination.k8s.io 85 | resources: 86 | - leases 87 | verbs: 88 | - get 89 | - list 90 | - watch 91 | - create 92 | - update 93 | - patch 94 | - delete 95 | - apiGroups: 96 | - "" 97 | resources: 98 | - events 99 | verbs: 100 | - create 101 | - patch 102 | - apiGroups: 103 | - "" 104 | resources: 105 | - secrets 106 | verbs: 107 | - get 108 | - list 109 | - watch 110 | - nonResourceURLs: 111 | - "/metrics" 112 | verbs: 113 | - get 114 | - apiGroups: 115 | - authentication.k8s.io 116 | resources: 117 | - tokenreviews 118 | verbs: 119 | - create 120 | - apiGroups: 121 | - authorization.k8s.io 122 | resources: 123 | - subjectaccessreviews 124 | verbs: 125 | - create 126 | - apiGroups: 127 | - cluster.edge-orchestrator.intel.com 128 | resources: 129 | - clusterconnects 130 | verbs: 131 | - create 132 | - delete 133 | - get 134 | - list 135 | - patch 136 | - update 137 | - watch 138 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/role_binding.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: rbac.authorization.k8s.io/v1 5 | kind: ClusterRoleBinding 6 | metadata: 7 | labels: 8 | {{- include "intel-infra-provider.roleLabels" . | nindent 4 }} 9 | name: {{ include "intel-infra-provider.fullname" . }}-controller 10 | roleRef: 11 | apiGroup: rbac.authorization.k8s.io 12 | kind: ClusterRole 13 | name: {{ include "intel-infra-provider.fullname" . }}-controller 14 | subjects: 15 | - kind: ServiceAccount 16 | name: {{ include "intel-infra-provider.serviceAccountName" . }} 17 | namespace: {{ .Release.Namespace }} 18 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/service_account.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | {{ if .Values.rbac.serviceAccount.create -}} 5 | apiVersion: v1 6 | kind: ServiceAccount 7 | metadata: 8 | name: {{ include "intel-infra-provider.serviceAccountName" . }} 9 | namespace: {{ .Release.Namespace }} 10 | labels: 11 | {{- include "intel-infra-provider.serviceAccountLabels" . | nindent 4 }} 12 | {{- with .Values.rbac.serviceAccount.annotations }} 13 | annotations: 14 | {{- toYaml . | nindent 4 }} 15 | {{- end }} 16 | {{- end -}} 17 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/service_monitor.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | {{- if .Values.metrics.serviceMonitor.enabled -}} 5 | # Prometheus Monitor Service (Metrics) 6 | apiVersion: monitoring.coreos.com/v1 7 | kind: ServiceMonitor 8 | metadata: 9 | name: {{ include "intel-infra-provider.fullname" . }} 10 | namespace: {{ .Release.Namespace }} 11 | labels: 12 | {{- include "intel-infra-provider.serviceMonitorLabels" . | nindent 4 }} 13 | spec: 14 | endpoints: 15 | - path: /metrics 16 | port: metrics 17 | scheme: http 18 | namespaceSelector: 19 | matchNames: 20 | - {{ .Release.Namespace }} 21 | selector: 22 | matchExpressions: 23 | - key: prometheus.io/service-monitor 24 | operator: NotIn 25 | values: 26 | - "false" 27 | matchLabels: 28 | {{- include "intel-infra-provider.metricsServiceLabels" . | nindent 6 }} 29 | {{- end -}} 30 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/southbound_api.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: {{ include "intel-infra-provider.fullname" . }}-southbound 8 | labels: 9 | {{- include "intel-infra-provider.southboundLabels" . | nindent 4 }} 10 | app: southbound-api 11 | spec: 12 | replicas: {{ .Values.southboundApi.replicaCount }} 13 | selector: 14 | matchLabels: 15 | {{- include "intel-infra-provider.selectorLabels" . | nindent 6 }} 16 | template: 17 | metadata: 18 | {{- with .Values.southboundApi.podAnnotations }} 19 | annotations: 20 | {{- toYaml . | nindent 8 }} 21 | {{- end }} 22 | labels: 23 | app: southbound-api 24 | {{- include "intel-infra-provider.southboundPodLabels" . | nindent 8 }} 25 | spec: 26 | {{- with .Values.southboundApi.imagePullSecrets }} 27 | imagePullSecrets: 28 | {{- toYaml . | nindent 8 }} 29 | {{- end }} 30 | serviceAccountName: {{ include "intel-infra-provider.serviceAccountName" . }} 31 | securityContext: 32 | {{- toYaml .Values.southboundApi.podSecurityContext | nindent 8 }} 33 | containers: 34 | - name: intel-infra-provider-southbound 35 | securityContext: 36 | {{- toYaml .Values.southboundApi.securityContext | nindent 12 }} 37 | image: "{{ .Values.southboundApi.image.registry.name }}/{{ .Values.southboundApi.image.repository }}:{{ .Values.southboundApi.image.tag | default .Chart.AppVersion }}" 38 | imagePullPolicy: {{ .Values.southboundApi.image.pullPolicy }} 39 | command: 40 | - /southbound_handler 41 | args: 42 | {{- range $key, $value := .Values.southboundApi.extraArgs }} 43 | - --{{ $key }}={{ $value }} 44 | {{- end }} 45 | livenessProbe: 46 | grpc: 47 | port: 50020 48 | initialDelaySeconds: 15 49 | periodSeconds: 20 50 | readinessProbe: 51 | grpc: 52 | port: 50020 53 | initialDelaySeconds: 5 54 | periodSeconds: 10 55 | env: 56 | - name: OIDC_SERVER_URL 57 | value: {{ .Values.oidc.oidc_server_url }} 58 | - name: OIDC_CLIENT_ID 59 | value: {{ .Values.oidc.oidc_client_id }} 60 | - name: OIDC_TLS_INSECURE_SKIP_VERIFY 61 | value: "{{ .Values.oidc.oidc_tls_insecure_skip_verify }}" 62 | - name: RATE_LIMITER_QPS 63 | value: "{{ .Values.southboundApi.clientRateLimiter.qps }}" 64 | - name: RATE_LIMITER_BURST 65 | value: "{{ .Values.southboundApi.clientRateLimiter.burst }}" 66 | ports: 67 | - name: grpc 68 | containerPort: {{ .Values.southboundApi.service.grpc.port }} 69 | protocol: TCP 70 | resources: 71 | {{- toYaml .Values.southboundApi.resources | nindent 12 }} 72 | {{- with .Values.southboundApi.nodeSelector }} 73 | nodeSelector: 74 | {{- toYaml . | nindent 8 }} 75 | {{- end }} 76 | {{- with .Values.southboundApi.affinity }} 77 | affinity: 78 | {{- toYaml . | nindent 8 }} 79 | {{- end }} 80 | {{- with .Values.southboundApi.tolerations }} 81 | tolerations: 82 | {{- toYaml . | nindent 8 }} 83 | {{- end }} 84 | terminationGracePeriodSeconds: {{ .Values.southboundApi.terminationGracePeriodSeconds }} 85 | -------------------------------------------------------------------------------- /deployment/charts/intel-infra-provider/templates/southbound_api_service.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | --- 4 | apiVersion: v1 5 | kind: Service 6 | metadata: 7 | name: {{template "intel-infra-provider.fullname" .}}-grpc 8 | namespace: {{.Release.Namespace}} 9 | labels: 10 | {{- include "intel-infra-provider.southboundLabels" . | nindent 4 }} 11 | spec: 12 | selector: 13 | app: southbound-api 14 | type: {{.Values.southboundApi.service.type}} 15 | ports: 16 | - name: "grpc" 17 | port: {{.Values.southboundApi.service.grpc.port}} 18 | {{- if .Values.traefikReverseProxy.enabled }} 19 | --- 20 | apiVersion: traefik.containo.us/v1alpha1 21 | kind: Middleware 22 | metadata: 23 | name: {{template "intel-infra-provider.fullname" .}}-grpc 24 | namespace: "{{ .Values.traefikReverseProxy.gatewayNamespace }}" 25 | spec: 26 | headers: 27 | customRequestHeaders: 28 | Host: "{{template "intel-infra-provider.fullname" .}}-southbound.{{ .Release.Namespace }}.svc" 29 | --- 30 | apiVersion: traefik.containo.us/v1alpha1 31 | kind: IngressRoute 32 | metadata: 33 | name: {{template "intel-infra-provider.fullname" .}}-grpc 34 | namespace: "{{ .Values.traefikReverseProxy.gatewayNamespace }}" 35 | spec: 36 | entryPoints: 37 | - websecure 38 | routes: 39 | - match: "Host(`{{ .Values.traefikReverseProxy.host.grpc.name }}`) && PathPrefix(`/`)" 40 | kind: Rule 41 | middlewares: 42 | - name: validate-jwt 43 | - name: {{template "intel-infra-provider.fullname" .}}-grpc 44 | services: 45 | - name: {{template "intel-infra-provider.fullname" .}}-grpc 46 | namespace: {{.Release.Namespace}} 47 | port: {{.Values.southboundApi.service.grpc.port}} 48 | scheme: h2c 49 | tls: 50 | secretName: "{{ .Values.traefikReverseProxy.host.grpc.secretName }}" 51 | {{- if .Values.traefikReverseProxy.host.grpc.tlsOption }} 52 | options: 53 | name: "{{ .Values.traefikReverseProxy.host.grpc.tlsOption }}" 54 | namespace: "{{ .Values.traefikReverseProxy.gatewayNamespace }}" 55 | {{- end }} 56 | {{- end }} 57 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /hack/fuzz_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 4 | # SPDX-License-Identifier: Apache-2.0 5 | 6 | ## Modified from Ethan Davidson 7 | ## https://stackoverflow.com/questions/71584005/ 8 | ## how-to-run-multi-fuzz-test-cases-wirtten-in-one-source-file-with-go1-18 9 | 10 | # clean all subprocesses on ctl-c 11 | 12 | trap "trap - SIGTERM && kill -- -$$ || true" SIGINT SIGTERM 13 | 14 | set -e 15 | 16 | fuzzTime="${1:-1}" # read from argument list or fallback to default - 1 minute 17 | 18 | files=$(grep -r --include='**_test.go' --files-with-matches 'func Fuzz' pkg internal) 19 | 20 | cat <_en-agent-rw' permission 12 | regex.match("^(([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}_)en-agent-rw)", role) 13 | } 14 | 15 | hasReadAccess := true if { 16 | some role in input["realm_access/roles"] # iteration 17 | 18 | # Check if the request has the '_en-agent-rw' permission 19 | regex.match("^(([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}_)en-agent-rw)", role) 20 | } 21 | 22 | # TODO: This will be removed in subsequent versions. Still needed for grpc_server tests to pass. 23 | hasWriteAccess := true if { 24 | some role in input["realm_access/roles"] # iteration 25 | # We expect: 26 | # - with MT: [PROJECT_UUID]_node-agent-readwrite-role 27 | # - without MT: node-agent-readwrite-role 28 | regex.match("^(([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}_)?node-agent-readwrite-role)", role) 29 | } 30 | 31 | hasReadAccess := true if { 32 | some role in input["realm_access/roles"] # iteration 33 | # We expect: 34 | # - with MT: [PROJECT_UUID]_node-agent-readwrite-role 35 | # - without MT: node-agent-readwrite-role 36 | regex.match("^(([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}_)?node-agent-readwrite-role)", role) 37 | } 38 | -------------------------------------------------------------------------------- /internal/southboundconfig/config.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package southboundconfig 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "os" 10 | "strconv" 11 | 12 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 13 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/utils" 14 | ) 15 | 16 | const ( 17 | defaultGrpcAddr = "0.0.0.0" 18 | defaultGrpcPort = "50020" 19 | defaultReadinessProbeGrpcEP = "127.0.0.1:50020" 20 | defaultTraceURL = "observability-opentelemetry-collector.orch-platform.svc.cluster.local:4317" 21 | TracingServiceName = "mc-ecm" 22 | ) 23 | 24 | var ( 25 | log = logging.GetLogger("config") 26 | EnableTracing bool 27 | ) 28 | 29 | type Config struct { 30 | GrpcAddr string 31 | GrpcPort string 32 | ReadinessProbeGrpcEP string 33 | EnableTracing bool 34 | TraceURL string 35 | UseGrpcStubMiddleware bool 36 | } 37 | 38 | // ValidateConfig validates the parsed Configuration 39 | func ValidateConfig(cfg *Config) error { 40 | if cfg == nil { 41 | return fmt.Errorf("configuration is nil") 42 | } 43 | if err := utils.IsValidHost(cfg.GrpcAddr); err != nil { 44 | return err 45 | } 46 | if err := utils.IsValidPort(cfg.GrpcPort); err != nil { 47 | return err 48 | } 49 | if err := utils.IsValidIPV4Port(cfg.ReadinessProbeGrpcEP); err != nil { 50 | return err 51 | } 52 | if cfg.EnableTracing { 53 | if !utils.IsValidHostnamePort(cfg.TraceURL) { 54 | return fmt.Errorf("not a valid hostname:port: %v", cfg.TraceURL) 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func ParseBooleanEnvConfigVar(envName string) bool { 62 | boolEnv, err := strconv.ParseBool(os.Getenv(envName)) 63 | if err != nil { 64 | log.Fatal().Err(err).Msgf("failed to parse boolean value from env var '%s'", envName) 65 | } 66 | return boolEnv 67 | } 68 | 69 | // ParseInputArg parses the input flags and returns a *Config struct 70 | func ParseInputArg() *Config { 71 | grpcAddr := flag.String("grpcAddr", defaultGrpcAddr, "Address for grpc server") 72 | grpcPort := flag.String("grpcPort", defaultGrpcPort, "Port for grpc server") 73 | readinessProbeGrpcEP := flag.String("readinessProbeGrpcEP", defaultReadinessProbeGrpcEP, "Healthz port for grpc server") 74 | enableTracing := flag.Bool("enableTracing", false, "Flag to enable tracing") 75 | traceURL := flag.String("traceURL", defaultTraceURL, "Tracing URL for OTLP protocol") 76 | useGrpcStubMiddleware := flag.Bool("useGrpcStubMiddleware", false, "Flag to enable gRPC stub middleware. Use for CO E2E testing only") 77 | 78 | flag.Parse() 79 | 80 | // Set the global tracing variable to be used by other modules in the package for configuring tracing. 81 | // This is a dirty hack as config information isn't passed around to various modules. 82 | EnableTracing = *enableTracing 83 | 84 | config := &Config{ 85 | GrpcAddr: *grpcAddr, 86 | GrpcPort: *grpcPort, 87 | ReadinessProbeGrpcEP: *readinessProbeGrpcEP, 88 | EnableTracing: *enableTracing, 89 | TraceURL: *traceURL, 90 | UseGrpcStubMiddleware: *useGrpcStubMiddleware, 91 | } 92 | 93 | return config 94 | } 95 | -------------------------------------------------------------------------------- /internal/southboundconfig/config_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package southboundconfig 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | // TestParseInputArg tests arguments are parsed correctly 15 | func TestParseInputArg(t *testing.T) { 16 | // TODO: refactor to use table drive test and avoid using default values (because they will be set without specifying the flags on the command line) 17 | os.Args = append(os.Args, fmt.Sprintf("--grpcAddr=%v", defaultGrpcAddr)) 18 | os.Args = append(os.Args, fmt.Sprintf("--grpcPort=%v", defaultGrpcPort)) 19 | os.Args = append(os.Args, fmt.Sprintf("--traceURL=%v", defaultTraceURL)) 20 | 21 | cfg := ParseInputArg() 22 | 23 | assert.Equal(t, defaultGrpcAddr, cfg.GrpcAddr) 24 | assert.Equal(t, defaultGrpcPort, cfg.GrpcPort) 25 | assert.Equal(t, defaultTraceURL, cfg.TraceURL) 26 | } 27 | 28 | func TestValidateConfig(t *testing.T) { 29 | args := []struct { 30 | name string 31 | input *Config 32 | wantErr bool 33 | }{ 34 | { 35 | name: "Test ValidateConfig success", 36 | input: &Config{ 37 | GrpcAddr: defaultGrpcAddr, 38 | GrpcPort: defaultGrpcPort, 39 | ReadinessProbeGrpcEP: defaultReadinessProbeGrpcEP, 40 | EnableTracing: true, 41 | TraceURL: defaultTraceURL, 42 | }, 43 | wantErr: false, 44 | }, 45 | { 46 | name: "Test ValidateConfig Failure - Invalid port data type", 47 | input: &Config{ 48 | GrpcAddr: defaultGrpcAddr, 49 | GrpcPort: "abc", 50 | ReadinessProbeGrpcEP: defaultReadinessProbeGrpcEP, 51 | }, 52 | wantErr: true, 53 | }, 54 | { 55 | name: "Test ValidateConfig Failure - Invalid port range", 56 | input: &Config{ 57 | GrpcAddr: defaultGrpcAddr, 58 | GrpcPort: "-1", 59 | ReadinessProbeGrpcEP: defaultReadinessProbeGrpcEP, 60 | }, 61 | wantErr: true, 62 | }, 63 | { 64 | name: "Test ValidateConfig Failure - Invalid host", 65 | input: &Config{ 66 | GrpcAddr: "", 67 | ReadinessProbeGrpcEP: defaultReadinessProbeGrpcEP, 68 | GrpcPort: defaultGrpcPort, 69 | }, 70 | wantErr: true, 71 | }, 72 | { 73 | name: "Test ValidateConfig Failure - Invalid Readiness Probe GprcEP", 74 | input: &Config{ 75 | GrpcAddr: defaultGrpcAddr, 76 | GrpcPort: defaultGrpcPort, 77 | ReadinessProbeGrpcEP: "1.2.3", 78 | }, 79 | wantErr: true, 80 | }, 81 | { 82 | name: "Test ValidateConfig Failure - nil config", 83 | input: nil, 84 | wantErr: true, 85 | }, 86 | } 87 | 88 | for _, v := range args { 89 | t.Run(v.name, func(t *testing.T) { 90 | err := ValidateConfig(v.input) 91 | if (err != nil) != v.wantErr { 92 | t.Errorf("ValidateConfig() err %v, wantErr %v", err, v.wantErr) 93 | return 94 | } 95 | }) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /internal/southboundhandler/types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package southboundhandler 5 | 6 | import ( 7 | "context" 8 | 9 | pb "github.com/open-edge-platform/cluster-api-provider-intel/pkg/api/proto" 10 | ) 11 | 12 | type SouthboundHandler interface { 13 | Register(ctx context.Context, nodeGUID string) (*pb.ShellScriptCommand, *pb.ShellScriptCommand, pb.RegisterClusterResponse_Result, error) 14 | UpdateStatus(ctx context.Context, nodeGUID string, status pb.UpdateClusterStatusRequest_Code) (pb.UpdateClusterStatusResponse_ActionRequest, error) 15 | } 16 | -------------------------------------------------------------------------------- /mocks/m_inventory/mock_infrastructureprovider.go.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /mocks/m_southboundhandler/mock_southboundhandler.go.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /pkg/api/proto/cluster_orchestrator_southbound.pb.go.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /pkg/api/proto/cluster_orchestrator_southbound.pb.validate.go.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /pkg/api/proto/cluster_orchestrator_southbound.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package cluster_orchestrator_southbound_proto; 3 | 4 | import "validate/validate.proto"; 5 | 6 | option go_package = ".;cluster_orchestrator_southbound"; 7 | 8 | service ClusterOrchestratorSouthbound { 9 | // RegisterCluster is called from Edge Node to receive cluster installation script 10 | rpc RegisterCluster(RegisterClusterRequest) returns (RegisterClusterResponse) {} 11 | 12 | // UpdateClusterStatus is called from Edge Cluster to set the status cluster deployment 13 | rpc UpdateClusterStatus(UpdateClusterStatusRequest) returns (UpdateClusterStatusResponse) {} 14 | 15 | // GetClusterNumByTemplateIdentifier is called from Cluster Manager service 16 | rpc GetClusterNumByTemplateIdentifier(GetClusterNumByTemplateIdentifierRequest) returns (GetClusterNumByTemplateIdentifierResponse) {} 17 | } 18 | 19 | // RegisterClusterRequest contains Edge Node identity assigned by Inventory 20 | message RegisterClusterRequest { 21 | string node_guid = 1 [(validate.rules).string = { pattern: "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"}]; 22 | } 23 | 24 | // RegisterClusterResponse contains shell script to be executed by Cluster Agent to install cluster 25 | message RegisterClusterResponse { 26 | ShellScriptCommand install_cmd = 1; 27 | ShellScriptCommand uninstall_cmd = 2; 28 | enum Result { 29 | SUCCESS = 0; 30 | ERROR = 1; 31 | } 32 | Result res = 3; 33 | } 34 | 35 | // ShellScriptCommand is a command to be executed by Cluster Agent to install/uninstall LKPE. 36 | // command is to be executed in shell, like this `sh -c command` 37 | message ShellScriptCommand { 38 | // example1: "curl -fL https://DOMAIN.NAME/system-agent-install.sh | sudo sh -s - --server https://DOMAIN.NAME --label 'cattle.io/os=linux' --token 86f9cqfnvvlmwmvvmsptmr5wqj9d6bqpxkmxbvjw2txklhbglcdtff --ca-checksum b50da8bfa2cbcc13e209b9ffbab4b39c699e0aa2b3fe50f44ec4477c54725ea3 --etcd --controlplane --worker" 39 | // example2: "/usr/local/bin/rancher-system-agent-uninstall.sh; /usr/local/bin/rke2-uninstall.sh" 40 | string command = 1; 41 | } 42 | 43 | // UpdateClusterStatusRequest is used by Cluster Agent to represent its internal state machine 44 | message UpdateClusterStatusRequest { 45 | enum Code { 46 | INACTIVE = 0; 47 | REGISTERING = 1; 48 | INSTALL_IN_PROGRESS = 2; 49 | ACTIVE = 3; 50 | DEREGISTERING = 4; 51 | UNINSTALL_IN_PROGRESS = 5; 52 | ERROR = 6; 53 | } 54 | Code code = 1; 55 | string node_guid = 2 [(validate.rules).string = { pattern: "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$"}]; 56 | } 57 | 58 | // UpdateClusterStatusResponse is used to request Cluster Agent to transition to new internal state 59 | message UpdateClusterStatusResponse { 60 | enum ActionRequest { 61 | NONE = 0; 62 | REGISTER = 1; 63 | DEREGISTER = 2; 64 | } 65 | ActionRequest action_request = 1; 66 | } 67 | 68 | message GetClusterNumByTemplateIdentifierRequest { 69 | //it's format is "template name" + "-" + "template version" 70 | string templateIdentifier = 1; 71 | } 72 | 73 | message GetClusterNumByTemplateIdentifierResponse { 74 | enum Result { 75 | SUCCESS = 0; 76 | ERROR = 1; 77 | } 78 | Result res = 1; 79 | 80 | int32 clusterNum = 2; 81 | } 82 | 83 | -------------------------------------------------------------------------------- /pkg/api/proto/cluster_orchestrator_southbound.proto.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /pkg/api/proto/cluster_orchestrator_southbound_grpc.pb.go.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- /pkg/auth/auth_echo/test/authz.rego: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | package authz 5 | 6 | import future.keywords.in 7 | 8 | hasAdminAccess { 9 | some role in input["realm_access/roles"] # iteration 10 | ["lp-admin-role"][_] == role 11 | } 12 | 13 | hasWriteAccess { 14 | some role in input["realm_access/roles"] # iteration 15 | ["lp-admin-role", "lp-read-write-role"][_] == role 16 | } 17 | 18 | hasReadAccess { 19 | some role in input["realm_access/roles"] # iteration 20 | ["lp-admin-role", "lp-read-write-role", "lp-read-only-role"][_] == role 21 | } -------------------------------------------------------------------------------- /pkg/inventory/types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package inventory 5 | 6 | import "errors" 7 | 8 | type InfrastructureProvider interface { 9 | CreateWorkload(in CreateWorkloadInput) CreateWorkloadOutput 10 | DeleteWorkload(in DeleteWorkloadInput) DeleteWorkloadOutput 11 | GetWorkload(in GetWorkloadInput) GetWorkloadOutput 12 | GetInstanceByMachineId(in GetInstanceByMachineIdInput) GetInstanceByMachineIdOutput 13 | AddInstanceToWorkload(in AddInstanceToWorkloadInput) AddInstanceToWorkloadOutput 14 | DeleteInstanceFromWorkload(in DeleteInstanceFromWorkloadInput) DeleteInstanceFromWorkloadOutput 15 | } 16 | 17 | type Workload struct { 18 | Id string 19 | } 20 | 21 | type Instance struct { 22 | Id string 23 | SerialNo string 24 | Os string 25 | MachineId string 26 | } 27 | 28 | type Host struct { 29 | Id string 30 | } 31 | 32 | type CreateWorkloadInput struct { 33 | TenantId string 34 | ClusterName string 35 | } 36 | 37 | type CreateWorkloadOutput struct { 38 | WorkloadId string 39 | Err error 40 | } 41 | 42 | type DeleteWorkloadInput struct { 43 | TenantId string 44 | WorkloadId string 45 | } 46 | 47 | type DeleteWorkloadOutput struct { 48 | Err error 49 | } 50 | 51 | type GetWorkloadInput struct { 52 | TenantId string 53 | WorkloadId string 54 | } 55 | 56 | type GetWorkloadOutput struct { 57 | Workload *Workload 58 | Err error 59 | } 60 | 61 | type GetInstanceByMachineIdInput struct { 62 | TenantId string 63 | MachineId string 64 | } 65 | 66 | type GetInstanceByMachineIdOutput struct { 67 | Host *Host 68 | Instance *Instance 69 | Err error 70 | } 71 | 72 | type AddInstanceToWorkloadInput struct { 73 | TenantId string 74 | WorkloadId string 75 | InstanceId string 76 | } 77 | 78 | type AddInstanceToWorkloadOutput struct { 79 | Err error 80 | } 81 | 82 | type DeleteInstanceFromWorkloadInput struct { 83 | TenantId string 84 | WorkloadId string 85 | InstanceId string 86 | } 87 | 88 | type DeleteInstanceFromWorkloadOutput struct { 89 | Err error 90 | } 91 | 92 | var ( 93 | ErrInvalidInstanceIdInput = errors.New("invalid instance id value") 94 | ErrInvalidWorkloadIdInput = errors.New("invalid workload id value") 95 | ErrInvalidMachineIdInput = errors.New("invalid machine id value") 96 | ErrInvalidTenantIdInput = errors.New("invalid tenant id value") 97 | ErrInvalidClusterNameInput = errors.New("invalid cluster name value") 98 | ErrInvalidInventoryResource = errors.New("invalid inventory resource") 99 | ErrInvalidInstance = errors.New("invalid instance") 100 | ErrInvalidWorkload = errors.New("invalid workload") 101 | ErrInvalidWorkloadInput = errors.New("invalid workload input values") 102 | ErrInvalidWorkloadMembers = errors.New("invalid workload members") 103 | ErrInvalidWorkloadMembersInput = errors.New("invalid workload members input values") 104 | ErrInvalidHost = errors.New("invalid host") 105 | ErrFailedInventoryGetHostByUuid = errors.New("failed inventory getHostByUUID call") 106 | ErrFailedInventoryGetResource = errors.New("failed inventory get resource call") 107 | ErrFailedInventoryCreateResource = errors.New("failed inventory create resource call") 108 | ErrFailedInventoryDeleteResource = errors.New("failed inventory delete resource call") 109 | ) 110 | -------------------------------------------------------------------------------- /pkg/logging/README.md: -------------------------------------------------------------------------------- 1 | # Logging Package 2 | 3 | This package implements a common logging package for Infrastructure as a Service applications, based 4 | on [zerolog](https://github.com/rs/zerolog). 5 | 6 | ## Controlling the Log Level 7 | 8 | This logging package defines a CLI `flag` named `-globalLogLevel`. As the name 9 | suggests, it sets the global log level exposed by zerolog. Apps that want to 10 | expose this flag, must call `flag.Parse()` in their `main` function. This is the 11 | preferred approach to ensure a consistent UX. Should an app need to deviate, it 12 | can call `zerolog.SetGlobalLevel(...)` as required. 13 | 14 | ## Output Formatting 15 | 16 | By default, logging output is in machine-readable JSON format. For use cases 17 | where a more human-readable format is desired, the `HUMAN` environment variable 18 | should be set. 19 | 20 | ## Security Logging 21 | 22 | Logging package exposes a tag called `McSec` which can be used to identify 23 | security events happening across MC components 24 | 25 | ```go 26 | // zlog is MCLogger, printing a security event 27 | zlog.McSec().Info().Msgf("Client %s authorized", client.UUID) 28 | 29 | // zlog is MCCtxLogger, printing a security event 30 | zlog := zlog.TraceCtx(ctx) 31 | zlog.McSec().Info().Msgf("Client %s authorized", client.UUID) 32 | ``` 33 | 34 | ## Error Logging 35 | 36 | Logging package exposes utilities to append `error` into the logs which can be easily 37 | scraped by external tools 38 | 39 | ```go 40 | // zlog is MCLogger, printing a security event and error 41 | err := errors.Errorfc(codes.PermissionDenied, "Permission denied for client: %s", "1") 42 | zlog.McSec().McErr(err).Msg("") 43 | 44 | // zlog is MCCtxLogger, printing a security event and error 45 | zlog := zlog.TraceCtx(ctx) 46 | zlog.McSec().McErr(err).Msg("") 47 | 48 | // zlog is MCLogger, printing a security event and error string 49 | zlog.McSec().McError("Permission denied for client: %s", "1").Msg("CreateResource") 50 | 51 | // zlog is MCCtxLogger, printing a security event and error string 52 | zlog := zlog.TraceCtx(ctx) 53 | zlog.McSec().McError("Permission denied for client: %s", "1").Msg("CreateResource") 54 | ``` 55 | -------------------------------------------------------------------------------- /pkg/logging/logger.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2022 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package logging 5 | 6 | import ( 7 | "context" 8 | "flag" 9 | "fmt" 10 | "os" 11 | "time" 12 | 13 | "github.com/rs/zerolog" 14 | "go.opentelemetry.io/otel/trace" 15 | ) 16 | 17 | // RFC3339Micro represents a time format similar to time.RFC3339Nano but only accurate to microseconds and keeping trailing zeros 18 | const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00" 19 | 20 | //nolint:gochecknoinits // Using init for defining flags is a valid exception. 21 | func init() { 22 | flag.Func( 23 | "globalLogLevel", 24 | "Sets the application-wide logging level. Must be a valid zerolog.Level. Defaults to 'info'", 25 | handleLogLevel, 26 | ) 27 | 28 | zerolog.TimeFieldFormat = RFC3339Micro 29 | zerolog.TimestampFieldName = "timestamp" 30 | 31 | // use UTC time 32 | zerolog.TimestampFunc = func() time.Time { 33 | return time.Now().UTC() 34 | } 35 | 36 | // Use shorter file name 37 | // For instance without below CallerMarshalFunc you will see file name like below 38 | // -> "/Users/$USER/Workspace/$PACKAGE_NAME/pkg/$PATH_TO_FILE/http_server_options.go:219" 39 | // But with the below CallerMarshalFunc, the file name will be 40 | // -> http_server_options.go:219 41 | // Since we also log the 'component' name in the logs we already have isolation if there are multiple files with the same 42 | // name across different components. 43 | // Logging shorter filename consumes less space and resource and also makes the log look neat! 44 | zerolog.CallerMarshalFunc = callerMarshal 45 | } 46 | 47 | func handleLogLevel(l string) error { 48 | level, err := zerolog.ParseLevel(l) 49 | if err != nil { 50 | return err 51 | } 52 | zerolog.SetGlobalLevel(level) 53 | return nil 54 | } 55 | 56 | type MCLogger struct { 57 | zerolog.Logger 58 | } 59 | 60 | type MCCtxLogger struct { 61 | zerolog.Logger 62 | } 63 | 64 | type spanlogHook struct { 65 | span trace.Span 66 | } 67 | 68 | func (h spanlogHook) Run(_ *zerolog.Event, _ zerolog.Level, msg string) { 69 | if h.span.IsRecording() { 70 | h.span.AddEvent(msg) 71 | } 72 | } 73 | 74 | func callerMarshal(_ uintptr, file string, line int) string { 75 | short := file 76 | for i := len(file) - 1; i > 0; i-- { 77 | if file[i] == '/' { 78 | short = file[i+1:] 79 | break 80 | } 81 | } 82 | file = short 83 | return fmt.Sprintf("%s:%d", short, line) 84 | } 85 | 86 | func GetLogger(component string) MCLogger { 87 | var logger zerolog.Logger 88 | 89 | // When 'HUMAN' env is set it dumps logs in human friendly readable format. 90 | // More details here https://betterstack.com/community/guides/logging/zerolog/#prettifying-your-logs-in-development. 91 | if _, present := os.LookupEnv("HUMAN"); present { 92 | logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339Nano}) 93 | } else { 94 | logger = zerolog.New(os.Stdout) 95 | } 96 | 97 | // Overwrite the default log level set in 'zerolog.New' to whatever is the Global Level 98 | logger = logger.Level(zerolog.GlobalLevel()) 99 | logger = logger.With().Caller().Timestamp().Str("component", component).Logger() 100 | return MCLogger{logger} 101 | } 102 | 103 | func (l MCLogger) TraceCtx(ctx context.Context) MCCtxLogger { 104 | span := trace.SpanFromContext(ctx) 105 | newlogger := l.With(). 106 | Str("span_id", span.SpanContext().SpanID().String()). 107 | Str("trace_id", span.SpanContext().TraceID().String()). 108 | Logger() 109 | newlogger = newlogger.Hook(spanlogHook{span}) 110 | return MCCtxLogger{newlogger} 111 | } 112 | 113 | // McSec is a logging decorator for MCLogger intended to be used for security related events. 114 | func (l *MCLogger) McSec() *MCLogger { 115 | return &MCLogger{l.With().Str("MCSec", "true").Logger()} 116 | } 117 | 118 | // McSec is a logging decorator MCCtxLogger intended to be used for security related events. 119 | func (l *MCCtxLogger) McSec() *MCCtxLogger { 120 | return &MCCtxLogger{l.With().Str("MCSec", "true").Logger()} 121 | } 122 | 123 | // McErr is an extension for MCLogger intended to be used for error logging. 124 | func (l *MCLogger) McErr(err error) *zerolog.Event { 125 | mcLogger := &MCLogger{l.With().Err(err).Logger()} 126 | return mcLogger.Error() 127 | } 128 | 129 | // McErr is an extension for MCCtxLogger intended to be used for error logging. 130 | func (l *MCCtxLogger) McErr(err error) *zerolog.Event { 131 | mcLogger := &MCCtxLogger{l.With().Err(err).Logger()} 132 | return mcLogger.Error() 133 | } 134 | 135 | // McError is an extension for MCLogger intended to be used for logging of inline errors. 136 | func (l *MCLogger) McError(format string, args ...interface{}) *zerolog.Event { 137 | logger := &MCLogger{l.With().Str("error", fmt.Sprintf(format, args...)).Logger()} 138 | return logger.Error() 139 | } 140 | 141 | // McError is an extension for MCCtxLogger intended to be used for logging of inline errors. 142 | func (l *MCCtxLogger) McError(format string, args ...interface{}) *zerolog.Event { 143 | logger := &MCCtxLogger{l.With().Str("error", fmt.Sprintf(format, args...)).Logger()} 144 | return logger.Error() 145 | } 146 | -------------------------------------------------------------------------------- /pkg/mocks/mock-secret-client/mock_secret_client.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | // 3 | // SPDX-License-Identifier: Apache-2.0 4 | package mock_secret_client 5 | 6 | import ( 7 | "fmt" 8 | 9 | "github.com/hashicorp/vault/api" 10 | "github.com/rs/zerolog/log" 11 | ) 12 | 13 | type MockSecretClient struct{} 14 | 15 | func (MockSecretClient) Initialize() error { 16 | return nil 17 | } 18 | 19 | func (MockSecretClient) Put(secretPath string, secretkvMap map[string]interface{}) error { 20 | log.Info().Msgf("PUT request received for %v", secretPath) 21 | return nil 22 | } 23 | 24 | func (MockSecretClient) Patch(secretPath string, secretkvMap map[string]interface{}) error { 25 | log.Info().Msgf("patch request received for %v", secretPath) 26 | return nil 27 | } 28 | 29 | func (MockSecretClient) Get(secretPath string) (*api.KVSecret, error) { 30 | switch secretPath { 31 | case "co-cm-db-pwd": 32 | return &api.KVSecret{ 33 | Data: map[string]interface{}{ 34 | "db-pwd": "password", 35 | }, 36 | VersionMetadata: nil, 37 | CustomMetadata: nil, 38 | Raw: nil, 39 | }, nil 40 | case "test-repo": 41 | return &api.KVSecret{ 42 | Data: map[string]interface{}{ 43 | "cacert": "test-repo-ca", 44 | }, 45 | VersionMetadata: nil, 46 | CustomMetadata: nil, 47 | Raw: nil, 48 | }, nil 49 | default: 50 | return nil, fmt.Errorf("unknown key %v", secretPath) 51 | } 52 | } 53 | 54 | func (MockSecretClient) Delete(secretPath string) error { 55 | log.Info().Msgf("delete request received for %v", secretPath) 56 | return nil 57 | } 58 | 59 | func (MockSecretClient) Undelete(secretPath string, versions []int) error { 60 | log.Info().Msgf("Undelete request received for %v", secretPath) 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /pkg/rbac/test/authz.rego: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | package authz 5 | 6 | import future.keywords.in 7 | 8 | hasWriteAccess := true if { 9 | some role in input["realm_access/roles"] # iteration 10 | ["clusters-write-role"][_] == role 11 | } 12 | 13 | hasReadAccess := true if { 14 | some role in input["realm_access/roles"] # iteration 15 | ["clusters-read-role"][_] == role 16 | } -------------------------------------------------------------------------------- /pkg/server-options/grpc_server_options.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package server_options 5 | 6 | import ( 7 | "context" 8 | 9 | grpcmw "github.com/grpc-ecosystem/go-grpc-middleware" 10 | "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging" 11 | 12 | "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" 13 | "go.opentelemetry.io/otel/trace" 14 | "google.golang.org/grpc" 15 | 16 | mclog "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 17 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/tenant" 18 | ) 19 | 20 | // InterceptorLogger logs the traceID and spanID along with whatever message is passed 21 | func InterceptorLogger(l mclog.MCLogger) logging.Logger { 22 | return logging.LoggerFunc(func(ctx context.Context, lvl logging.Level, msg string, _ ...any) { 23 | method, _ := grpc.Method(ctx) 24 | l.Debug(). 25 | Str("method", method). 26 | Str("trace", trace.SpanFromContext(ctx).SpanContext().TraceID().String()). 27 | Str("span", trace.SpanFromContext(ctx).SpanContext().SpanID().String()). 28 | Msg(msg) 29 | }) 30 | } 31 | 32 | func ExemptPathUnaryInterceptor(exemptPaths []string, interceptor grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { 33 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 34 | // Check if the method is in the exempt paths 35 | log.Debug().Str("method", info.FullMethod).Msg("Checking if method is exempt") 36 | for _, path := range exemptPaths { 37 | if info.FullMethod == path { 38 | // Skip the interceptor and directly call the handler 39 | return handler(ctx, req) 40 | } 41 | } 42 | // Apply the actual interceptor 43 | return interceptor(ctx, req, info, handler) 44 | } 45 | } 46 | 47 | func GetGrpcServerOpts(enableTracing bool) []grpc.ServerOption { 48 | var serverOptions []grpc.ServerOption 49 | 50 | var unaryInterceptors []grpc.UnaryServerInterceptor 51 | var streamInterceptors []grpc.StreamServerInterceptor 52 | 53 | if enableTracing { 54 | // Generate an event at the start and end of the call 55 | opts := []logging.Option{logging.WithLogOnEvents(logging.StartCall, logging.FinishCall)} 56 | 57 | // Enable options to insert otel context as well as log the trace and span ID at entry/exit of the call 58 | unaryInterceptors = append(unaryInterceptors, logging.UnaryServerInterceptor(InterceptorLogger(log), opts...)) 59 | streamInterceptors = append(streamInterceptors, logging.StreamServerInterceptor(InterceptorLogger(log), opts...)) 60 | serverOptions = append(serverOptions, grpc.StatsHandler(otelgrpc.NewServerHandler())) 61 | } 62 | 63 | // Add the tenant interceptor 64 | tenantInterceptor := tenant.ActiveProjectIdGrpcInterceptor() 65 | 66 | // Wrap the tenant interceptor to exempt health check paths 67 | // See https://github.com/grpc/grpc/blob/master/doc/health-checking.md 68 | exemptPaths := []string{ 69 | "/cluster_orchestrator_southbound_proto.ClusterOrchestratorSouthbound/Check", 70 | "/cluster_orchestrator_southbound_proto.ClusterOrchestratorSouthbound/Watch", 71 | "/grpc.health.v1.Health/Check", 72 | "/grpc.health.v1.Health/Watch", 73 | } 74 | wrappedTenantInterceptor := ExemptPathUnaryInterceptor(exemptPaths, tenantInterceptor) 75 | unaryInterceptors = append(unaryInterceptors, wrappedTenantInterceptor) 76 | 77 | serverOptions = append(serverOptions, 78 | grpc.UnaryInterceptor(grpcmw.ChainUnaryServer(unaryInterceptors...)), 79 | grpc.StreamInterceptor(grpcmw.ChainStreamServer(streamInterceptors...))) 80 | 81 | return serverOptions 82 | } 83 | -------------------------------------------------------------------------------- /pkg/server-options/grpc_server_options_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package server_options 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "testing" 10 | "time" 11 | 12 | example "github.com/gogo/grpc-example/proto" 13 | "github.com/gogo/grpc-example/server" 14 | "google.golang.org/grpc" 15 | "google.golang.org/grpc/credentials/insecure" 16 | "google.golang.org/grpc/test/bufconn" 17 | 18 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/tracing" 19 | ) 20 | 21 | func TestGrpcServerOptions(t *testing.T) { 22 | cleanup, exportErr := tracing.NewTraceExporterGRPC( 23 | "observability-opentelemetry-collector.orch-platform.svc.cluster.local:4317", 24 | "test-service", nil, 25 | ) 26 | if exportErr != nil { 27 | t.Errorf("Error creating trace exporter: %v", exportErr) 28 | return 29 | } 30 | if cleanup != nil { 31 | t.Log("Tracing enabled") 32 | } else { 33 | t.Errorf("Tracing could not be enabled") 34 | return 35 | } 36 | defer func() { 37 | _ = cleanup(context.Background()) 38 | }() 39 | srvOpts := GetGrpcServerOpts(true) 40 | if len(srvOpts) == 0 { 41 | t.Errorf("no serveroptions were returned") 42 | } 43 | buffer := 1024 * 1024 44 | lis := bufconn.Listen(buffer) 45 | baseServer := grpc.NewServer(srvOpts...) 46 | example.RegisterUserServiceServer(baseServer, server.New()) 47 | go func() { 48 | if err := baseServer.Serve(lis); err != nil { 49 | t.Errorf("error serving server: %v", err) 50 | return 51 | } 52 | }() 53 | conn, err := grpc.DialContext(context.Background(), "", 54 | grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { 55 | return lis.Dial() 56 | }), grpc.WithTransportCredentials(insecure.NewCredentials())) 57 | if err != nil { 58 | t.Errorf("error connecting to server: %v", err) 59 | return 60 | } 61 | 62 | closer := func() { 63 | err := lis.Close() 64 | if err != nil { 65 | t.Errorf("error closing listener: %v", err) 66 | return 67 | } 68 | baseServer.Stop() 69 | } 70 | defer closer() 71 | client := example.NewUserServiceClient(conn) 72 | // The below API call will log the trace and span ID at entry/exist of the call. 73 | _, err = client.ListUsers(context.Background(), &example.ListUsersRequest{ 74 | CreatedSince: nil, 75 | OlderThan: nil, 76 | }) 77 | t.Logf("return val: %v", err) 78 | 79 | // Extra time allowed for the logger to complete its task 80 | time.Sleep(5 * time.Second) 81 | 82 | } 83 | -------------------------------------------------------------------------------- /pkg/tenant/grpc.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "context" 8 | "strings" 9 | 10 | "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | 15 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/rbac" 16 | ) 17 | 18 | // ActiveProjectIdGrpcInterceptor returns an interceptor to extract the active project id from jwt and provide it in the context. 19 | func ActiveProjectIdGrpcInterceptor() grpc.UnaryServerInterceptor { 20 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 21 | projectId, err := extractProjectIdFromJwtRoles(ctx) 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | log.Trace().Msgf("project id intercepted in grpc request: '%s'", projectId) 27 | return handler(AddActiveProjectIdToContext(ctx, projectId), req) 28 | } 29 | } 30 | 31 | // extractProjectIdFromJwtRoles infers the project id from the m2mClientRole in the jwt found in the context. 32 | func extractProjectIdFromJwtRoles(ctx context.Context) (string, error) { 33 | projectId := DefaultProjectId 34 | 35 | // error ignored to handle case from ctm where jwt not supplied 36 | token, _ := rbac.ExtractAuthorizationFromMd(ctx) 37 | if token == "" { 38 | log.Debug().Msg("jwt token not found in grpc request, extracting project id from context metadata") 39 | projectId = metautils.ExtractIncoming(ctx).Get(ActiveProjectIdHeaderKey) 40 | if projectId == "" || !isValidUuid(projectId) { 41 | return DefaultProjectId, status.New(codes.Unauthenticated, "project id not supplied").Err() 42 | } 43 | return projectId, nil 44 | } 45 | 46 | // TODO! refactor to avoid double authentication 47 | md, _ := rbac.VerifyContextClaims(ctx) 48 | roles, ok := md[jwtRolesKey] 49 | if !ok { 50 | return DefaultProjectId, status.New(codes.Unauthenticated, "no roles found in jwt").Err() 51 | } 52 | 53 | for _, role := range roles { 54 | if !strings.Contains(role, roleProjectIdSeparator+m2mClientRole) { 55 | continue 56 | } 57 | 58 | tid := strings.Split(role, roleProjectIdSeparator)[0] 59 | if !isValidUuid(tid) { 60 | continue 61 | } 62 | 63 | if projectId == DefaultProjectId { 64 | projectId = tid 65 | } 66 | 67 | if projectId != tid { 68 | return DefaultProjectId, status.New(codes.Unauthenticated, "mismatched project ids found in jwt roles").Err() 69 | } 70 | } 71 | 72 | if projectId == DefaultProjectId { 73 | return DefaultProjectId, status.New(codes.Unauthenticated, "project id not available in request context").Err() 74 | } 75 | 76 | return projectId, nil 77 | } 78 | -------------------------------------------------------------------------------- /pkg/tenant/rest.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "net/http" 8 | 9 | "github.com/labstack/echo/v4" 10 | ) 11 | 12 | var ( 13 | projectIdNotProvidedError = echo.NewHTTPError(http.StatusUnauthorized, "request unauthorized, active project id not provided") 14 | projectIdInvalidError = echo.NewHTTPError(http.StatusUnauthorized, "request unauthorized, active project id invalid") 15 | ) 16 | 17 | // ActiveProjectIdEchoMiddleware returns an echo middleware function that extracts the active project id from the request 18 | // header and adds it to the echo request context. 19 | func ActiveProjectIdEchoMiddleware() echo.MiddlewareFunc { 20 | return func(next echo.HandlerFunc) echo.HandlerFunc { 21 | return func(c echo.Context) error { 22 | // skip project id check for healthz route 23 | if c.Path() == "/v1/healthz" { 24 | return next(c) 25 | } 26 | 27 | projectId, err := getActiveProjectIdFromRequest(c.Request()) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | ctx := AddActiveProjectIdToContext(c.Request().Context(), projectId) 33 | c.SetRequest(c.Request().WithContext(ctx)) 34 | 35 | log.Trace().Msgf("project id intercepted in http request: '%s'", projectId) 36 | return next(c) 37 | } 38 | } 39 | } 40 | 41 | // getActiveProjectIdFromRequest extracts the active project id from the request header using the ActiveProjectIdHeaderKey. 42 | func getActiveProjectIdFromRequest(request *http.Request) (string, error) { 43 | activeProjectIds := request.Header.Values(ActiveProjectIdHeaderKey) 44 | switch len(activeProjectIds) { 45 | case 0: 46 | log.Error().Msgf("received request without an active project id") 47 | return DefaultProjectId, projectIdNotProvidedError 48 | case 1: 49 | default: 50 | log.Warn().Msgf("received request with multiple active project ids %s, using: %s", activeProjectIds, activeProjectIds[0]) 51 | } 52 | 53 | if !isValidUuid(activeProjectIds[0]) { 54 | log.Error().Msgf("received request with invalid active project id: '%s'", activeProjectIds[0]) 55 | return DefaultProjectId, projectIdInvalidError 56 | } 57 | 58 | return activeProjectIds[0], nil 59 | } 60 | -------------------------------------------------------------------------------- /pkg/tenant/rest_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "net/http" 8 | "testing" 9 | 10 | "github.com/labstack/echo/v4" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestGetActiveProjectIdFromRequest(t *testing.T) { 15 | cases := []struct { 16 | name string 17 | inputRequest *http.Request 18 | expectedStr string 19 | expectedErr error 20 | }{ 21 | { 22 | name: "valid project id header in request", 23 | inputRequest: &http.Request{Header: http.Header{ActiveProjectIdHeaderKey: []string{validProjectId1}}}, 24 | expectedStr: validProjectId1, 25 | }, 26 | { 27 | name: "multiple project id headers in request valid", 28 | inputRequest: &http.Request{Header: http.Header{ActiveProjectIdHeaderKey: []string{validProjectId1, invalidProjectId}}}, 29 | expectedStr: validProjectId1, 30 | }, 31 | { 32 | name: "invalid project id header in request", 33 | inputRequest: &http.Request{Header: http.Header{ActiveProjectIdHeaderKey: []string{invalidProjectId}}}, 34 | expectedStr: DefaultProjectId, 35 | expectedErr: projectIdInvalidError, 36 | }, 37 | { 38 | name: "multiple project id headers in request invalid", 39 | inputRequest: &http.Request{Header: http.Header{ActiveProjectIdHeaderKey: []string{invalidProjectId, validProjectId1}}}, 40 | expectedStr: DefaultProjectId, 41 | expectedErr: projectIdInvalidError, 42 | }, 43 | { 44 | name: "no project id header in request", 45 | inputRequest: &http.Request{}, 46 | expectedStr: DefaultProjectId, 47 | expectedErr: projectIdNotProvidedError, 48 | }, 49 | } 50 | 51 | for _, tc := range cases { 52 | t.Run(tc.name, func(t *testing.T) { 53 | projectId, err := getActiveProjectIdFromRequest(tc.inputRequest) 54 | assert.Equal(t, tc.expectedStr, projectId) 55 | assert.Equal(t, tc.expectedErr, err) 56 | }) 57 | } 58 | } 59 | 60 | func TestActiveProjectIdEchoMiddleware(t *testing.T) { 61 | cases := []struct { 62 | name string 63 | headerActiveProjectIds []string 64 | expectedProjectId string 65 | expectedErr error 66 | }{ 67 | { 68 | name: "successfully extract project id from request with single active project id", 69 | headerActiveProjectIds: []string{validProjectId1}, 70 | expectedProjectId: validProjectId1, 71 | }, 72 | { 73 | name: "successfully extract project id from request with multiple active project ids", 74 | headerActiveProjectIds: []string{validProjectId2, validProjectId1, invalidProjectId}, 75 | expectedProjectId: validProjectId2, 76 | }, 77 | { 78 | name: "fail to extract project id from request with single invalid active project id", 79 | headerActiveProjectIds: []string{invalidProjectId}, 80 | expectedErr: projectIdInvalidError, 81 | }, 82 | { 83 | name: "fail to extract project id from request with multiple active project ids where the first id is invalid", 84 | headerActiveProjectIds: []string{invalidProjectId, validProjectId1, validProjectId2}, 85 | expectedErr: projectIdInvalidError, 86 | }, 87 | { 88 | name: "fail to extract project id from request without active project id header", 89 | headerActiveProjectIds: []string{}, 90 | expectedErr: projectIdNotProvidedError, 91 | }, 92 | } 93 | 94 | for _, tc := range cases { 95 | t.Run(tc.name, func(t *testing.T) { 96 | ctx := echo.New().NewContext(&http.Request{Header: map[string][]string{ActiveProjectIdHeaderKey: tc.headerActiveProjectIds}}, nil) 97 | 98 | middleware := ActiveProjectIdEchoMiddleware() 99 | handler := middleware(func(e echo.Context) error { 100 | assert.Equal(t, tc.expectedProjectId, e.Request().Context().Value(ActiveProjectIdContextKey)) 101 | return nil 102 | }) 103 | 104 | err := handler(ctx) 105 | if tc.expectedErr != nil { 106 | assert.Equal(t, err, tc.expectedErr) 107 | } else { 108 | assert.NoError(t, err) 109 | } 110 | }) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /pkg/tenant/tenant_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | "google.golang.org/grpc/metadata" 12 | 13 | testutils "github.com/open-edge-platform/cluster-api-provider-intel/pkg/testing" 14 | ) 15 | 16 | var ( 17 | validProjectId1 = "7642fcd0-d997-4ad4-b7a3-95e2cf8e3095" 18 | validProjectId2 = "df95e679-bac4-460c-99ff-e0b17b8562c4" 19 | invalidProjectId = "invalid-id" 20 | 21 | validRole = roleProjectIdSeparator + m2mClientRole 22 | invalidRole = roleProjectIdSeparator + "cluster-read-role" 23 | ) 24 | 25 | func ctxWithJwtRoles(t *testing.T, roles ...string) context.Context { 26 | _, jwt, err := testutils.CreateJWT(t) 27 | require.NoError(t, err) 28 | 29 | return metadata.NewIncomingContext(context.Background(), metadata.MD{ 30 | testutils.AuthKey: []string{testutils.BearerPrefixLowercase + jwt}, 31 | jwtRolesKey: roles, 32 | }) 33 | } 34 | 35 | func ctxWithProjectIdKey(projectId string) context.Context { 36 | return metadata.NewIncomingContext(context.Background(), metadata.MD{ 37 | ActiveProjectIdHeaderKey: []string{projectId}, 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/tenant/types.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "regexp" 8 | 9 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 10 | ) 11 | 12 | type ContextKey string // SA1029 13 | 14 | const ( 15 | ActiveProjectIdHeaderKey = "Activeprojectid" 16 | ActiveProjectIdContextKey = ContextKey(ActiveProjectIdHeaderKey) 17 | DefaultProjectId = "00000000-0000-0000-0000-000000000000" 18 | 19 | m2mClientRole = "en-agent-rw" 20 | jwtRolesKey = "realm_access/roles" 21 | roleProjectIdSeparator = "_" 22 | 23 | // relaxed uuid regex, replace with strict regex if required 24 | uuidPattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" 25 | ) 26 | 27 | var ( 28 | log = logging.GetLogger("tenant") 29 | 30 | uuidRegex = regexp.MustCompile(uuidPattern) 31 | ) 32 | -------------------------------------------------------------------------------- /pkg/tenant/utils.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "context" 8 | ) 9 | 10 | func AddActiveProjectIdToContext(ctx context.Context, activeProjectId string) context.Context { 11 | if !isValidUuid(activeProjectId) { 12 | log.Panic().Msgf("cannot add invalid active project id to context: '%s'", activeProjectId) 13 | } 14 | 15 | return context.WithValue(ctx, ActiveProjectIdContextKey, activeProjectId) 16 | } 17 | 18 | func GetActiveProjectIdFromContext(ctx context.Context) string { 19 | activeProjectId, ok := ctx.Value(ActiveProjectIdContextKey).(string) 20 | if !ok || !isValidUuid(activeProjectId) { 21 | log.Panic().Msg("no valid active project id found in context") 22 | } 23 | 24 | return activeProjectId 25 | } 26 | 27 | func isValidUuid(uuid string) bool { 28 | return uuidRegex.MatchString(uuid) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/tenant/utils_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tenant 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAddActiveProjectIdToContext(t *testing.T) { 14 | cases := []struct { 15 | name string 16 | inputCtx context.Context 17 | inputProjectId string 18 | }{ 19 | { 20 | name: "add valid project id to empty context", 21 | inputCtx: context.Background(), 22 | inputProjectId: validProjectId1, 23 | }, 24 | { 25 | name: "add valid project id to existing project context", 26 | inputCtx: context.WithValue(context.Background(), ActiveProjectIdContextKey, validProjectId1), 27 | inputProjectId: validProjectId2, 28 | }, 29 | { 30 | name: "add invalid project id to context", 31 | inputCtx: context.Background(), 32 | inputProjectId: invalidProjectId, 33 | }, 34 | } 35 | 36 | for _, tc := range cases { 37 | t.Run(tc.name, func(t *testing.T) { 38 | if tc.inputProjectId == invalidProjectId { 39 | assert.Panics(t, func() { 40 | _ = AddActiveProjectIdToContext(tc.inputCtx, tc.inputProjectId) 41 | }) 42 | return 43 | } 44 | 45 | ctx := AddActiveProjectIdToContext(tc.inputCtx, tc.inputProjectId) 46 | assert.Equal(t, tc.inputProjectId, ctx.Value(ActiveProjectIdContextKey)) 47 | }) 48 | } 49 | } 50 | 51 | func TestGetActiveProjectIdFromContext(t *testing.T) { 52 | cases := []struct { 53 | name string 54 | inputCtx context.Context 55 | expectedProjectId string 56 | }{ 57 | { 58 | name: "valid project id value in context", 59 | inputCtx: context.WithValue(context.Background(), ActiveProjectIdContextKey, validProjectId1), 60 | expectedProjectId: validProjectId1, 61 | }, 62 | { 63 | name: "invalid project id value in context", 64 | inputCtx: context.WithValue(context.Background(), ActiveProjectIdContextKey, 7), 65 | expectedProjectId: DefaultProjectId, 66 | }, 67 | { 68 | name: "no project id value in context", 69 | inputCtx: context.Background(), 70 | expectedProjectId: DefaultProjectId, 71 | }, 72 | } 73 | 74 | for _, tc := range cases { 75 | t.Run(tc.name, func(t *testing.T) { 76 | if tc.expectedProjectId == DefaultProjectId { 77 | assert.Panics(t, func() { 78 | _ = GetActiveProjectIdFromContext(tc.inputCtx) 79 | }) 80 | return 81 | } 82 | 83 | assert.Equal(t, tc.expectedProjectId, GetActiveProjectIdFromContext(tc.inputCtx)) 84 | }) 85 | } 86 | } 87 | 88 | func TestIsValidUuid(t *testing.T) { 89 | cases := []struct { 90 | name string 91 | inputUuid string 92 | expected bool 93 | }{ 94 | { 95 | name: "valid uuid 1", 96 | inputUuid: validProjectId1, 97 | expected: true, 98 | }, 99 | { 100 | name: "valid uuid 2", 101 | inputUuid: validProjectId2, 102 | expected: true, 103 | }, 104 | { 105 | name: "invalid uuid", 106 | inputUuid: invalidProjectId, 107 | expected: false, 108 | }, 109 | } 110 | 111 | for _, tc := range cases { 112 | t.Run(tc.name, func(t *testing.T) { 113 | assert.Equal(t, tc.expected, isValidUuid(tc.inputUuid)) 114 | }) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pkg/tracing/trace_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tracing 5 | 6 | import ( 7 | "context" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | "github.com/stretchr/testify/require" 12 | "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | var ( 17 | tracingAddressNil = "" 18 | tracingAddress = "localhost:2348" 19 | tracingServiceNil = "" 20 | tracingService = "test" 21 | tracingSpanName = "span" 22 | tracingAttribs = map[string]string{ 23 | "key": "value", 24 | } 25 | traceClient = otlptracehttp.NewClient() 26 | ) 27 | 28 | func TestTracing(t *testing.T) { 29 | ctx := context.Background() 30 | 31 | shutdownTraceHTTP, err := NewTraceExporterHTTP(tracingAddressNil, tracingService, tracingAttribs) 32 | require.Error(t, err) 33 | assert.Nil(t, shutdownTraceHTTP) 34 | shutdownTraceHTTP, err = NewTraceExporterHTTP(tracingAddress, tracingServiceNil, tracingAttribs) 35 | require.Error(t, err) 36 | assert.Nil(t, shutdownTraceHTTP) 37 | shutdownTraceHTTP, err = NewTraceExporterHTTP(tracingAddress, tracingService, tracingAttribs) 38 | require.NoError(t, err) 39 | assert.NotNil(t, shutdownTraceHTTP) 40 | err = shutdownTraceHTTP(ctx) 41 | require.NoError(t, err) 42 | assert.NotNil(t, shutdownTraceHTTP) 43 | 44 | shutdownTraceHTTP, err = NewTraceExporterHTTP(tracingAddress, tracingService, nil) 45 | require.NoError(t, err) 46 | assert.NotNil(t, shutdownTraceHTTP) 47 | err = shutdownTraceHTTP(ctx) 48 | require.NoError(t, err) 49 | assert.NotNil(t, shutdownTraceHTTP) 50 | 51 | shutdownTraceGRPC, err := NewTraceExporterGRPC(tracingAddressNil, tracingService, tracingAttribs) 52 | assert.Error(t, err) 53 | assert.Nil(t, shutdownTraceGRPC) 54 | shutdownTraceGRPC, err = NewTraceExporterGRPC(tracingAddress, tracingServiceNil, tracingAttribs) 55 | assert.Error(t, err) 56 | assert.Nil(t, shutdownTraceGRPC) 57 | shutdownTraceGRPC, err = NewTraceExporterGRPC(tracingAddress, tracingService, tracingAttribs) 58 | assert.NoError(t, err) 59 | assert.NotNil(t, shutdownTraceGRPC) 60 | 61 | shutdownTraceGRPC, err = NewTraceExporterGRPC(tracingAddress, tracingService, nil) 62 | assert.NoError(t, err) 63 | assert.NotNil(t, shutdownTraceGRPC) 64 | 65 | shutdownTrace, err := newTraceExporter(nil, nil) 66 | assert.Error(t, err) 67 | assert.Nil(t, shutdownTrace) 68 | 69 | shutdownTrace, err = newTraceExporter(traceClient, nil) 70 | assert.Error(t, err) 71 | assert.Nil(t, shutdownTrace) 72 | 73 | StartTrace(ctx, tracingService, tracingSpanName) 74 | 75 | _, error := StartTraceFromRemote(ctx, tracingService, tracingSpanName) 76 | assert.NoError(t, error) 77 | 78 | defer StopTrace(ctx) 79 | 80 | var optsClient []grpc.DialOption 81 | optsClient = EnableGrpcClientTracing(optsClient) 82 | assert.NotNil(t, optsClient) 83 | 84 | var optsServer []grpc.ServerOption 85 | optsServer = EnableGrpcServerTracing(optsServer) 86 | assert.NotNil(t, optsServer) 87 | } 88 | -------------------------------------------------------------------------------- /pkg/tracing/tracing_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package tracing 5 | 6 | import ( 7 | "flag" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestMain(m *testing.M) { 13 | // Only needed to suppress the error 14 | flag.String( 15 | "policyBundle", 16 | "/rego/policy_bundle.tar.gz", 17 | "Path of policy rego file", 18 | ) 19 | flag.Parse() 20 | 21 | run := m.Run() // run all tests 22 | os.Exit(run) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/utils/file.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "io" 8 | "net/http" 9 | "net/url" 10 | "os" 11 | "syscall" 12 | 13 | "github.com/bnkamalesh/errors" 14 | ) 15 | 16 | type Filelink string 17 | 18 | const ( 19 | Symbollink Filelink = "symbol" 20 | Hardlink Filelink = "hard" 21 | Normalfile Filelink = "normal" 22 | Wrongfile Filelink = "" 23 | ) 24 | 25 | func RemoveFile(name string) error { 26 | err := os.Remove(name) 27 | if err != nil && !os.IsNotExist(err) { 28 | return errors.InternalErrf(err, "Failed to remove %s", name) 29 | } 30 | return nil 31 | } 32 | 33 | func CheckFileLink(filename string) Filelink { 34 | fi, err := os.Lstat(filename) 35 | if err != nil { 36 | log.Error().Msgf("Check link failure: %v", err) 37 | return Wrongfile 38 | } 39 | s, ok := fi.Sys().(*syscall.Stat_t) 40 | if !ok { 41 | log.Error().Msgf("Check stat value failure: %v", err) 42 | return Wrongfile 43 | } 44 | if fi.Mode()&os.ModeSymlink != 0 { 45 | _, err = os.Readlink(filename) 46 | if err != nil { 47 | log.Error().Msgf("Read symbol link error : %v", err) 48 | return Wrongfile 49 | } 50 | return Symbollink 51 | } 52 | 53 | nlink := uint32(s.Nlink) //nolint:gosec // unknown why this cast exists, leaving as is 54 | if nlink > 1 { 55 | return Hardlink 56 | } else { 57 | return Normalfile 58 | } 59 | } 60 | 61 | func IsValidFile(filename string) bool { 62 | flinktype := CheckFileLink(filename) 63 | if flinktype == Normalfile || flinktype == Symbollink { 64 | return true 65 | } 66 | return false 67 | } 68 | 69 | func FileExists(filename string) bool { 70 | _, err := os.Stat(filename) 71 | return !os.IsNotExist(err) 72 | } 73 | 74 | func CopyFile(dstName, srcName string) (written int64, err error) { 75 | src, err := os.Open(srcName) 76 | if err != nil { 77 | log.Error().Msgf("error opening source file: %v", err) 78 | return 0, errors.InternalErrf(err, "error opening source file: %v", srcName) 79 | } 80 | defer src.Close() 81 | 82 | validfile := IsValidFile(srcName) 83 | 84 | if !validfile { 85 | return 0, errors.Validationf("not a valid src file: %v", srcName) 86 | } 87 | if FileExists(dstName) { 88 | validfile = IsValidFile(dstName) 89 | if !validfile { 90 | return 0, errors.Validationf("not a valid dst file: %v", dstName) 91 | } 92 | } 93 | 94 | info, err := os.Stat(srcName) 95 | if err != nil { 96 | return 0, errors.InternalErrf(err, "error fetching file information for file: %v", srcName) 97 | } 98 | 99 | dst, err := os.OpenFile(dstName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode()) 100 | if err != nil { 101 | return 0, errors.InternalErrf(err, "error opening dst file: %v", dstName) 102 | } 103 | defer dst.Close() 104 | written, err = io.Copy(dst, src) 105 | if err != nil { 106 | return 0, errors.InternalErrf(err, "error copying file from source %v to destination %v", 107 | srcName, dstName) 108 | } 109 | return written, nil 110 | } 111 | 112 | // DownloadFile Downloads file through url and copies it to the specified path 113 | func DownloadFile(filepath string, fileurl string) error { 114 | log.Info().Msgf("Downloading: %s to %s", fileurl, filepath) 115 | ufile, _ := url.Parse(fileurl) 116 | switch ufile.Scheme { 117 | case "http", "https": 118 | resp, err := http.Get(fileurl) //nolint: noctx, gosec // FIXME: This is not the right way per linter issue. Check more 119 | // here https://github.com/sonatard/noctx to fix it. More effort and change in this PR and hence skipping this. 120 | if err != nil { 121 | return errors.InternalErrf(err, "Failed to get %s", fileurl) 122 | } 123 | // close http client before exiting the function 124 | defer func() { 125 | if err = resp.Body.Close(); err != nil { 126 | log.Warn().Msgf("error closing http client: %v", err) 127 | } 128 | }() 129 | 130 | // Create the file 131 | out, err := os.Create(filepath) 132 | if err != nil { 133 | return errors.InputBodyErrf(err, "Failed to create %s", filepath) 134 | } 135 | defer func() { 136 | _ = out.Close() 137 | }() 138 | // Write the body to file 139 | if _, err = io.Copy(out, resp.Body); err != nil { 140 | return errors.InternalErrf(err, "error copying file") 141 | } 142 | return nil 143 | case "file": 144 | _, err := CopyFile(filepath, ufile.Path) 145 | return err 146 | 147 | default: 148 | return errors.Validationf("unknown url schema: %v", ufile.Scheme) 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /pkg/utils/kubernetes.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "github.com/bnkamalesh/errors" 8 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 9 | "k8s.io/client-go/dynamic" 10 | "k8s.io/client-go/kubernetes" 11 | restclient "k8s.io/client-go/rest" 12 | ) 13 | 14 | const ( 15 | HELMNAMESPACEENV = "POD_NAMESPACE" 16 | ) 17 | 18 | var log = logging.GetLogger("utils") 19 | 20 | func NewInClusterClient() (kubernetes.Interface, error) { 21 | config, err := restclient.InClusterConfig() 22 | if err != nil { 23 | return nil, errors.InternalErr(err, "failed to acquire cluster config") 24 | } 25 | client, err := kubernetes.NewForConfig(config) 26 | if err != nil { 27 | return nil, errors.InternalErr(err, "failed to gen new k8s client") 28 | } 29 | return client, nil 30 | } 31 | 32 | type KubernetesAPI struct { 33 | Client kubernetes.Interface 34 | } 35 | 36 | func (kapi *KubernetesAPI) NewInClusterClient() error { 37 | config, err := restclient.InClusterConfig() 38 | if err != nil { 39 | return errors.InternalErr(err, "failed to generate cluster config") 40 | } 41 | kapi.Client, err = kubernetes.NewForConfig(config) 42 | if err != nil { 43 | return errors.InternalErr(err, "failed to gen new k8s client") 44 | } 45 | return nil 46 | } 47 | 48 | func NewInClusterDynamicClient() (dynamic.Interface, error) { 49 | restconf, err := restclient.InClusterConfig() 50 | if err != nil { 51 | return nil, errors.InternalErr(err, "failed to get restconf") 52 | } 53 | restConfigValue := *restconf 54 | 55 | // QPS indicates the maximum QPS to the master from this client. 56 | // Burst is maximum burst for throttle. 57 | // https://pkg.go.dev/k8s.io/client-go@v0.29.0/rest#Config 58 | restConfigValue.QPS = float32(10) 59 | restConfigValue.Burst = int(100) 60 | restconf = &restConfigValue 61 | 62 | dynamicClient, err := dynamic.NewForConfig(restconf) 63 | if err != nil { 64 | return nil, errors.InternalErr(err, "failed to get kube config") 65 | } 66 | return dynamicClient, nil 67 | } 68 | -------------------------------------------------------------------------------- /pkg/utils/testdata/dstFile.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/open-edge-platform/cluster-api-provider-intel/d930fe69ee7206e9ec9af819e6677c0fba7cbc5c/pkg/utils/testdata/dstFile.yml -------------------------------------------------------------------------------- /pkg/utils/testdata/fileutil1.yml: -------------------------------------------------------------------------------- 1 | test if file is valid 2 | -------------------------------------------------------------------------------- /pkg/utils/testdata/fileutil_test.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "TEST", 3 | "hosts": ["TEST", "1.0.0.1"] 4 | } 5 | 6 | -------------------------------------------------------------------------------- /pkg/utils/testdata/fileutil_test.yml: -------------------------------------------------------------------------------- 1 | test string -------------------------------------------------------------------------------- /pkg/utils/testdata/hardlink.lns: -------------------------------------------------------------------------------- 1 | test if file is valid 2 | -------------------------------------------------------------------------------- /pkg/utils/testdata/hardlinkobj: -------------------------------------------------------------------------------- 1 | test if file is valid 2 | -------------------------------------------------------------------------------- /pkg/utils/testdata/symbol.yaml: -------------------------------------------------------------------------------- 1 | fileutil1.yml -------------------------------------------------------------------------------- /pkg/utils/tools.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "crypto/tls" 10 | "encoding/base64" 11 | "io" 12 | "net/http" 13 | "net/url" 14 | "regexp" 15 | "strings" 16 | 17 | "github.com/bnkamalesh/errors" 18 | ) 19 | 20 | type SchemeType string 21 | 22 | const ( 23 | HttpScheme SchemeType = "https://" 24 | defaultIstioQuitEndpoint string = "http://localhost:15020/quitquitquit" 25 | ) 26 | 27 | var ( 28 | hostMatch = regexp.MustCompile(`[^a-zA-Z0-9 ]+`) 29 | ) 30 | 31 | // IsUrl checks if the present 32 | func IsUrl(u string) bool { 33 | _, err := url.ParseRequestURI(u) 34 | if err != nil { 35 | log.Error().Msgf("It is not a valid url format) %v", err) 36 | return false 37 | } 38 | return true 39 | } 40 | 41 | // ConvertUrlToFilepath converts URL to File path 42 | // example: 43 | // before: https://registry.intel.com/hello 44 | // after: registryintelcom 45 | func ConvertUrlToSecretpath(urls string) (string, error) { 46 | log.Info().Msgf("ConvertUrlToFilepath: %v", urls) 47 | withScheme := strings.HasPrefix(urls, "http") 48 | var filepath string 49 | if withScheme { 50 | log.Info().Msgf("withScheme") 51 | up, err := url.Parse(urls) 52 | if err != nil { 53 | return "", errors.ValidationErr(err, "error parsing URL") 54 | } 55 | hostname := up.Host 56 | filepath = hostMatch.ReplaceAllString(hostname, "") 57 | } else { 58 | log.Info().Msgf("without scheme") 59 | if strings.Contains(urls, "/") { 60 | filepathArray := strings.Split(urls, "/") 61 | filepath = hostMatch.ReplaceAllString(filepathArray[0], "") 62 | } else { 63 | return "", errors.New("error parsing URL, URL doesn't contain /") 64 | } 65 | } 66 | 67 | return filepath, nil 68 | } 69 | 70 | // RetrieveFQDN inspect and remove the sheme of http protocol 71 | // example: 72 | // before: https://registry.intel.com/hello 73 | // after: registry.intel.com 74 | func RetrieveFQDN(urls string) (string, error) { 75 | log.Info().Msgf("ConvertUrlToFilepath: %v", urls) 76 | withScheme := strings.HasPrefix(urls, "http") 77 | 78 | var fqdn string 79 | 80 | if withScheme { 81 | log.Info().Msgf("withScheme") 82 | fqdn = urls 83 | } else { 84 | log.Info().Msgf("without scheme") 85 | if strings.Contains(urls, "/") { 86 | fqdnhArray := strings.Split(urls, "/") 87 | fqdn = "http://" + fqdnhArray[0] 88 | } else { 89 | fqdn = "http://" + urls 90 | } 91 | 92 | } 93 | 94 | up, err := url.Parse(fqdn) 95 | if err != nil { 96 | return "", errors.ValidationErr(err, "error parsing URL") 97 | } 98 | return up.Host + up.Path, nil 99 | } 100 | 101 | // Get CA from URL 102 | // example: 103 | // Get CA from https://registry.intel.com/hello 104 | // return: encodedCA, error(nil) 105 | func GetCA(caPath string) (string, error) { 106 | if IsUrl(caPath) { 107 | log.Info().Msgf("need to put CA to Vault secret path") 108 | 109 | transport := &http.Transport{ 110 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint: gosec // need to allow insecure for svc-to-svc communication 111 | } 112 | client := &http.Client{Transport: transport} 113 | resp, err := client.Get(caPath) //nolint: noctx // FIXME: This is not the right way per linter issue. Check more 114 | // here https://github.com/sonatard/noctx to fix it. More effort and change in this PR and hence skipping this. 115 | if err != nil { 116 | log.Err(err).Msgf("get CA from CA path error: %v", err) 117 | return "", err 118 | } 119 | 120 | defer func() { 121 | _ = resp.Body.Close() 122 | }() 123 | 124 | body, err := io.ReadAll(resp.Body) 125 | if err != nil { 126 | log.Err(err).Msgf("get CA from CA path error: %v", err) 127 | return "", err 128 | } 129 | ca := base64.StdEncoding.EncodeToString(body) 130 | return ca, nil 131 | } else { 132 | // TODO process secret path type caPath 133 | return "", nil 134 | } 135 | } 136 | 137 | // Terminate Sidecar 138 | // This is a WA to terminate Istio sidecar which block the kubernetes jobs complate 139 | // https://github.com/istio/istio/issues/11659 140 | // The issue was fixed in kubernetes 1.28 141 | 142 | func TerminateSideCar(istioQuitEndpoint string) error { 143 | bodyReader := bytes.NewReader([]byte(``)) 144 | 145 | if istioQuitEndpoint == "" { 146 | istioQuitEndpoint = defaultIstioQuitEndpoint 147 | } 148 | 149 | req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, istioQuitEndpoint, bodyReader) 150 | if err != nil { 151 | log.Error().Err(err).Msg("failed to send quit to istio proxy.") 152 | return err 153 | } 154 | client := &http.Client{} 155 | resp, err := client.Do(req) 156 | if err != nil { 157 | log.Error().Err(err).Msg("failed to send quit to istio proxy.") 158 | return err 159 | } 160 | defer resp.Body.Close() 161 | log.Debug().Msgf("Close istio proxy with res %+v.", resp) 162 | return nil 163 | } 164 | -------------------------------------------------------------------------------- /pkg/utils/tools_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "net/http" 8 | "net/http/httptest" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestIsUrl(t *testing.T) { 15 | type args struct { 16 | u string 17 | } 18 | tests := []struct { 19 | name string 20 | args args 21 | want bool 22 | }{ 23 | { 24 | name: "success", 25 | args: args{ 26 | u: "https://www.google.com", 27 | }, 28 | want: true, 29 | }, 30 | { 31 | name: "failure", 32 | args: args{ 33 | u: "1234", 34 | }, 35 | want: false, 36 | }, 37 | } 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | assert.Equalf(t, tt.want, IsUrl(tt.args.u), "IsUrl(%v)", tt.args.u) 41 | }) 42 | } 43 | } 44 | 45 | func TestConvertUrlToSecretpath(t *testing.T) { 46 | type args struct { 47 | urls string 48 | } 49 | tests := []struct { 50 | name string 51 | args args 52 | want string 53 | wantErr bool 54 | }{ 55 | { 56 | name: "Valid URL with scheme", 57 | args: args{ 58 | urls: "https://registry.intel.com/hello", 59 | }, 60 | want: "registryintelcom", 61 | wantErr: false, 62 | }, 63 | { 64 | name: "Valid URL without scheme", 65 | args: args{ 66 | urls: "registry.intel.com/hello", 67 | }, 68 | want: "registryintelcom", 69 | wantErr: false, 70 | }, 71 | { 72 | name: "Invalid URL", 73 | args: args{ 74 | urls: "12312312radfas@", 75 | }, 76 | want: "", 77 | wantErr: true, 78 | }, 79 | } 80 | for _, tt := range tests { 81 | t.Run(tt.name, func(t *testing.T) { 82 | got, err := ConvertUrlToSecretpath(tt.args.urls) 83 | if (err != nil) != tt.wantErr { 84 | t.Errorf("ConvertUrlToSecretpath() error = %v, wantErr %v", err, tt.wantErr) 85 | return 86 | } 87 | assert.Equalf(t, tt.want, got, "ConvertUrlToSecretpath(%v)", tt.args.urls) 88 | }) 89 | } 90 | } 91 | 92 | func TestRetrieveFQDN(t *testing.T) { 93 | type args struct { 94 | urls string 95 | } 96 | tests := []struct { 97 | name string 98 | args args 99 | want string 100 | wantErr bool 101 | }{ 102 | { 103 | name: "success with scheme", 104 | args: args{ 105 | urls: "https://registry.intel.com", 106 | }, 107 | want: "registry.intel.com", 108 | wantErr: false, 109 | }, 110 | { 111 | name: "success without scheme", 112 | args: args{ 113 | urls: "registry.intel.com", 114 | }, 115 | want: "registry.intel.com", 116 | wantErr: false, 117 | }, 118 | { 119 | name: "success without scheme", 120 | args: args{ 121 | urls: "registry.intel.com/hello", 122 | }, 123 | want: "registry.intel.com", 124 | wantErr: false, 125 | }, 126 | } 127 | for _, tt := range tests { 128 | t.Run(tt.name, func(t *testing.T) { 129 | got, err := RetrieveFQDN(tt.args.urls) 130 | if (err != nil) != tt.wantErr { 131 | t.Errorf("RetrieveFQDN() error = %v, wantErr %v", err, tt.wantErr) 132 | return 133 | } 134 | assert.Equalf(t, tt.want, got, "RetrieveFQDN(%v)", tt.args.urls) 135 | }) 136 | } 137 | } 138 | 139 | func TestGetCASuccesfull(t *testing.T) { 140 | server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 141 | rw.Write([]byte("https://registry.com/hello")) 142 | })) 143 | // Close the server when test finishes 144 | defer server.Close() 145 | 146 | // Use Client & URL from our local test server 147 | got, err := GetCA(server.URL) 148 | if err != nil { 149 | t.Errorf("GetCA() error = %v, wantErr %v", err, err) 150 | return 151 | } 152 | assert.Equalf(t, "aHR0cHM6Ly9yZWdpc3RyeS5jb20vaGVsbG8=", got, server.URL) 153 | 154 | } 155 | 156 | func TestGetCAWrongURL(t *testing.T) { 157 | //pass 12345 as a URL 158 | got, err := GetCA("12345") 159 | if err != nil { 160 | t.Errorf("GetCA() error = %v, wantErr %v", err, err) 161 | return 162 | } 163 | assert.Equal(t, "", got) 164 | 165 | } 166 | 167 | func TestTerminateSideCarEmptyIstioEndpoint(t *testing.T) { 168 | 169 | err := TerminateSideCar("") 170 | assert.Error(t, err) 171 | 172 | } 173 | 174 | func TestTerminateSideCar(t *testing.T) { 175 | server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 176 | rw.Write([]byte("https://registry.com/hello")) 177 | })) 178 | 179 | defer server.Close() 180 | 181 | err := TerminateSideCar(server.URL) 182 | assert.Equalf(t, err, nil, "No errors") 183 | 184 | } 185 | -------------------------------------------------------------------------------- /pkg/utils/validate.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "fmt" 8 | "net" 9 | url2 "net/url" 10 | "path/filepath" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | const ( 17 | HttpSchemePath = "https://" 18 | // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123 19 | hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*?$` 20 | ) 21 | 22 | // IsValidIPV4Port assumes the uRL is of the format : for a valid url. 23 | // The other assumption is that the host is a valid IP4 24 | // TODO: Enhance this function later if other formats are valid 25 | func IsValidIPV4Port(uRL string) error { 26 | out := strings.Split(uRL, ":") 27 | if len(out) != 2 { 28 | return fmt.Errorf("invalid url format: %v", uRL) 29 | } 30 | // validate ip 31 | if net.ParseIP(out[0]) == nil { 32 | return fmt.Errorf("invalid ip address in the url: %v", uRL) 33 | } 34 | // validate port 35 | if err := IsValidPort(out[1]); err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | // IsValidHost TODO: This is a stupid validation. Fix this ASAP 42 | func IsValidHost(host string) error { 43 | if host == "" { 44 | return fmt.Errorf("host is nil") 45 | } 46 | return nil 47 | } 48 | 49 | func IsValidPort(port string) error { 50 | p, err := strconv.Atoi(port) 51 | // validate port format 52 | if err != nil { 53 | return fmt.Errorf("invalid port format: %v", port) 54 | } 55 | // validate port range 56 | if err = IsValidPortInt(p); err != nil { 57 | return err 58 | } 59 | 60 | return nil 61 | } 62 | 63 | func IsValidPortInt(port int) error { 64 | // validate port range 65 | if port > 65535 || port < 1 { 66 | return fmt.Errorf("invalid port range: %v", port) 67 | } 68 | 69 | return nil 70 | } 71 | 72 | func IsValidDNSName(dnsName string) error { 73 | var domainRegexp = regexp.MustCompile(`^(?i)[a-z0-9-]+(\.[a-z0-9-]+)+\.?$`) 74 | if !domainRegexp.MatchString(dnsName) { 75 | return fmt.Errorf("%v is not a valid dns name", dnsName) 76 | } 77 | return nil 78 | } 79 | 80 | // IsValidHostnamePort validates a : combination for fields typically used for socket address. 81 | func IsValidHostnamePort(val string) bool { 82 | host, port, err := net.SplitHostPort(val) 83 | if err != nil { 84 | return false 85 | } 86 | var portNum int64 87 | // Port must be any port <= 65535. 88 | if portNum, err = strconv.ParseInt(port, 10, 32); err != nil || portNum > 65535 || portNum < 1 { 89 | return false 90 | } 91 | 92 | // If host is specified, it should match a DNS name 93 | if host != "" { 94 | if !regexp.MustCompile(hostnameRegexStringRFC1123).MatchString(host) { 95 | return false 96 | } 97 | } 98 | return true 99 | } 100 | 101 | func IsValidUrl(url string) error { 102 | withScheme := strings.HasPrefix(url, "http") 103 | var cto string 104 | if withScheme { 105 | cto = url 106 | } else { 107 | cto = HttpSchemePath + url 108 | } 109 | if _, err := url2.ParseRequestURI(cto); err != nil { 110 | return err 111 | } 112 | return nil 113 | } 114 | 115 | // IsValidNamespace TODO: fix this rudimentary validation 116 | func IsValidNamespace(ns string) error { 117 | if ns == "" { 118 | return fmt.Errorf("namespace not specified") 119 | } 120 | return nil 121 | } 122 | 123 | func IsAbsFilePath(path string) error { 124 | if !filepath.IsAbs(path) { 125 | return fmt.Errorf("%s is not absolute file path", path) 126 | } 127 | return nil 128 | } 129 | -------------------------------------------------------------------------------- /pkg/utils/validate_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package utils 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestIsHostnamePort(t *testing.T) { 11 | 12 | type testInput struct { 13 | data string 14 | expected bool 15 | } 16 | testData := []testInput{ 17 | {"bad..domain.name:234", false}, 18 | {"extra.dot.com.", false}, 19 | {"localhost:1234", true}, 20 | {"192.168.1.1:1234", true}, 21 | {":1234", true}, 22 | {"domain.com:1334", true}, 23 | {"this.domain.com:234", true}, 24 | {"domain:75000", false}, 25 | {"missing.port", false}, 26 | {"https://google.com:1234", false}, 27 | {"http://google.com:1234", false}, 28 | {"http://google.com", false}, 29 | } 30 | for _, td := range testData { 31 | res := IsValidHostnamePort(td.data) 32 | if td.expected != res { 33 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.expected, res) 34 | } 35 | } 36 | } 37 | 38 | func TestIsValidIPPort(t *testing.T) { 39 | 40 | type testInput struct { 41 | data string 42 | wantErr bool 43 | } 44 | testData := []testInput{ 45 | {"192.168.1.1:1234", false}, 46 | {":1234", true}, 47 | {"domain:75000", true}, 48 | {"1.2.3.4:75000", true}, 49 | {"1.2.3.4:abc", true}, 50 | {"missing.port", true}, 51 | {"https://google.com:1234", true}, 52 | {"http://google.com:1234", true}, 53 | {"localhost:1234", true}, 54 | {"0.0.0.0:1234", false}, 55 | {"0.0.0.0", true}, 56 | {"1.2.3.4", true}, 57 | {"0.0.0.0:1234", false}, 58 | {"::ffff:192.0.2.1", true}, 59 | {"::ffff:192.0.2.1:1234", true}, 60 | } 61 | for _, td := range testData { 62 | err := IsValidIPV4Port(td.data) 63 | if (err != nil) != td.wantErr { 64 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.wantErr, err) 65 | } 66 | } 67 | } 68 | 69 | func TestIsValidHost(t *testing.T) { 70 | type testInput struct { 71 | data string 72 | wantErr bool 73 | } 74 | testData := []testInput{ 75 | {"192.168.1.1:1234", false}, 76 | {":1234", false}, 77 | {":1234", false}, 78 | {"", true}, 79 | } 80 | for _, td := range testData { 81 | err := IsValidHost(td.data) 82 | if (err != nil) != td.wantErr { 83 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.wantErr, err) 84 | } 85 | } 86 | } 87 | 88 | func TestIsValidDNSName(t *testing.T) { 89 | type testInput struct { 90 | data string 91 | wantErr bool 92 | } 93 | testData := []testInput{ 94 | {"google.com", false}, 95 | {"something", true}, 96 | {"", true}, 97 | {"1234", true}, 98 | {"_123", true}, 99 | {"localhost:1234", true}, 100 | } 101 | for _, td := range testData { 102 | err := IsValidDNSName(td.data) 103 | if (err != nil) != td.wantErr { 104 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.wantErr, err) 105 | } 106 | } 107 | } 108 | 109 | func TestIsValidUrl(t *testing.T) { 110 | type testInput struct { 111 | data string 112 | wantErr bool 113 | } 114 | testData := []testInput{ 115 | {"google.com", false}, 116 | /* // TODO: How are these valid URLs!! The test isn't failing with these inputs 117 | {"1234", true}, 118 | {"", true}, 119 | */ 120 | } 121 | for _, td := range testData { 122 | err := IsValidUrl(td.data) 123 | if (err != nil) != td.wantErr { 124 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.wantErr, err) 125 | } 126 | } 127 | } 128 | 129 | func TestIsValidNamespace(t *testing.T) { 130 | type testInput struct { 131 | data string 132 | wantErr bool 133 | } 134 | testData := []testInput{ 135 | {"", true}, 136 | {"something", false}, 137 | } 138 | for _, td := range testData { 139 | err := IsValidNamespace(td.data) 140 | if (err != nil) != td.wantErr { 141 | t.Errorf("Test failed for data: %v, want: %v got: %v", td.data, td.wantErr, err) 142 | } 143 | } 144 | } 145 | 146 | func TestIsAbsFilePath(t *testing.T) { 147 | cases := []struct { 148 | name string 149 | path string 150 | wantErr bool 151 | }{ 152 | { 153 | name: "valid path", 154 | path: "/programs/course1/hello1.go", 155 | wantErr: false, 156 | }, 157 | { 158 | name: "invalid path - case 1", 159 | path: "../programs/course1/hello1.go", 160 | wantErr: true, 161 | }, 162 | { 163 | name: "invalid path - case 2", 164 | path: "C:/programs/course1/hello1.go", 165 | wantErr: true, 166 | }, 167 | } 168 | 169 | for _, tc := range cases { 170 | 171 | err := IsAbsFilePath(tc.path) 172 | 173 | if (err != nil) != tc.wantErr { 174 | t.Errorf("want: %v, got: %v", tc.wantErr, err) 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | # license checking 6 | python-debian==0.1.44 7 | reuse~=1.0.0 8 | 9 | # lint yaml 10 | yamllint~=1.27.1 11 | -------------------------------------------------------------------------------- /test/coder/traefik-values.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | traefikReverseProxy: 5 | host: 6 | grpc: 7 | name: "cluster-orch-node.kind.internal" 8 | secretName: "tls-orch" 9 | enabled: true 10 | 11 | manager: 12 | inventory: 13 | endpoint: "inventory.orch-infra.svc.cluster.local:50051" 14 | oidc: 15 | oidc_server_url: "http://platform-keycloak.orch-platform.svc/realms/master" 16 | -------------------------------------------------------------------------------- /test/demo/kind-cluster-with-extramounts.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | kind: Cluster 5 | apiVersion: kind.x-k8s.io/v1alpha4 6 | name: kind 7 | nodes: 8 | - role: control-plane 9 | extraMounts: 10 | - hostPath: /var/run/docker.sock 11 | containerPath: /var/run/docker.sock 12 | -------------------------------------------------------------------------------- /test/demo/rke2-intel-clusterclass-example.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: v1 5 | kind: Namespace 6 | metadata: 7 | name: ${NAMESPACE} 8 | --- 9 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 10 | kind: IntelMachineBinding 11 | metadata: 12 | name: demo-cluster-clusterclass-${NAMESPACE} 13 | namespace: ${NAMESPACE} 14 | spec: 15 | clusterName: demo-cluster-clusterclass 16 | intelMachineTemplateName: baseline-clusterclass-v0.0.1-controlplane 17 | nodeGUID: ${NODEGUID} 18 | --- 19 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 20 | kind: IntelMachineTemplate 21 | metadata: 22 | name: baseline-clusterclass-v0.0.1-controlplane 23 | namespace: ${NAMESPACE} 24 | spec: 25 | template: 26 | spec: {} 27 | --- 28 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 29 | kind: RKE2ControlPlaneTemplate 30 | metadata: 31 | name: baseline-clusterclass-v0.0.1 32 | namespace: ${NAMESPACE} 33 | spec: 34 | template: 35 | spec: 36 | agentConfig: 37 | additionalUserData: {} 38 | format: cloud-config 39 | kubelet: 40 | extraArgs: 41 | - --topology-manager-policy=best-effort 42 | - --cpu-manager-policy=static 43 | - --reserved-cpus=1 44 | - --max-pods=250 45 | - --tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 46 | infrastructureRef: 47 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 48 | kind: IntelMachineTemplate 49 | name: baseline-clusterclass-v0.0.1-controlplane 50 | machineTemplate: 51 | infrastructureRef: {} 52 | metadata: {} 53 | manifestsConfigMapReference: {} 54 | nodeDrainTimeout: 2m0s 55 | privateRegistriesConfig: 56 | mirrors: 57 | rs-proxy.rs-proxy.svc.cluster.local:8443: 58 | endpoint: 59 | - https://localhost.internal:9443 60 | rolloutStrategy: 61 | rollingUpdate: 62 | maxSurge: 1 63 | type: RollingUpdate 64 | serverConfig: 65 | cni: calico 66 | cniMultusEnable: true 67 | disableComponents: 68 | kubernetesComponents: 69 | - cloudController 70 | etcd: 71 | backupConfig: 72 | retention: "5" 73 | scheduleCron: 0 */5 * * * 74 | customConfig: 75 | extraArgs: 76 | - cipher-suites=[TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256] 77 | kubeAPIServer: 78 | extraArgs: 79 | - --feature-gates=PortForwardWebsockets=true 80 | - --tls-cipher-suites=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 81 | kubeControllerManager: {} 82 | kubeScheduler: {} 83 | version: "" 84 | --- 85 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 86 | kind: IntelClusterTemplate 87 | metadata: 88 | name: baseline-clusterclass-v0.0.1 89 | namespace: ${NAMESPACE} 90 | spec: 91 | template: 92 | metadata: {} 93 | spec: {} 94 | --- 95 | apiVersion: cluster.x-k8s.io/v1beta1 96 | kind: ClusterClass 97 | metadata: 98 | name: baseline-clusterclass-v0.0.1 99 | namespace: ${NAMESPACE} 100 | spec: 101 | controlPlane: 102 | machineHealthCheck: 103 | unhealthyConditions: 104 | - status: Unknown 105 | timeout: 5m0s 106 | type: Ready 107 | - status: "False" 108 | timeout: 5m0s 109 | type: Ready 110 | machineInfrastructure: 111 | ref: 112 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 113 | kind: IntelMachineTemplate 114 | name: baseline-clusterclass-v0.0.1-controlplane 115 | namespace: ${NAMESPACE} 116 | metadata: {} 117 | ref: 118 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 119 | kind: RKE2ControlPlaneTemplate 120 | name: baseline-clusterclass-v0.0.1 121 | namespace: ${NAMESPACE} 122 | infrastructure: 123 | ref: 124 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 125 | kind: IntelClusterTemplate 126 | name: baseline-clusterclass-v0.0.1 127 | namespace: ${NAMESPACE} 128 | workers: {} 129 | --- 130 | apiVersion: cluster.x-k8s.io/v1beta1 131 | kind: Cluster 132 | metadata: 133 | name: demo-cluster-clusterclass 134 | namespace: ${NAMESPACE} 135 | spec: 136 | clusterNetwork: 137 | pods: 138 | cidrBlocks: 139 | - 192.168.0.0/16 140 | services: 141 | cidrBlocks: 142 | - 10.128.0.0/12 143 | topology: 144 | class: baseline-clusterclass-v0.0.1 145 | controlPlane: 146 | metadata: {} 147 | replicas: 1 148 | version: v1.30.6+rke2r1 149 | -------------------------------------------------------------------------------- /test/demo/rke2-intel-example.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | apiVersion: v1 5 | kind: Namespace 6 | metadata: 7 | name: ${NAMESPACE} 8 | --- 9 | apiVersion: cluster.x-k8s.io/v1beta1 10 | kind: Cluster 11 | metadata: 12 | name: intel-rke2-test 13 | namespace: ${NAMESPACE} 14 | spec: 15 | clusterNetwork: 16 | pods: 17 | cidrBlocks: 18 | - 10.45.0.0/16 19 | serviceDomain: cluster.local 20 | services: 21 | cidrBlocks: 22 | - 10.46.0.0/16 23 | controlPlaneRef: 24 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 25 | kind: RKE2ControlPlane 26 | name: intel-rke2-test-control-plane-and-worker 27 | infrastructureRef: 28 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 29 | kind: IntelCluster 30 | name: intel-rke2-test 31 | --- 32 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 33 | kind: IntelCluster 34 | metadata: 35 | name: intel-rke2-test 36 | namespace: ${NAMESPACE} 37 | spec: {} 38 | --- 39 | apiVersion: controlplane.cluster.x-k8s.io/v1beta1 40 | kind: RKE2ControlPlane 41 | metadata: 42 | name: intel-rke2-test-control-plane-and-worker 43 | namespace: ${NAMESPACE} 44 | spec: 45 | agentConfig: 46 | nodeLabels: 47 | infrastructureRef: 48 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 49 | kind: IntelMachineTemplate 50 | name: controlplane-and-worker 51 | nodeDrainTimeout: 2m 52 | registrationMethod: control-plane-endpoint 53 | replicas: 1 54 | rolloutStrategy: 55 | rollingUpdate: 56 | maxSurge: 1 57 | type: RollingUpdate 58 | serverConfig: 59 | disableComponents: 60 | kubernetesComponents: 61 | - cloudController 62 | version: v1.30.6+rke2r1 63 | --- 64 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 65 | kind: IntelMachineTemplate 66 | metadata: 67 | name: controlplane-and-worker 68 | namespace: ${NAMESPACE} 69 | spec: 70 | template: {} 71 | --- 72 | apiVersion: bootstrap.cluster.x-k8s.io/v1beta1 73 | kind: RKE2ConfigTemplate 74 | metadata: 75 | name: intel-rke2-test-agent 76 | namespace: ${NAMESPACE} 77 | spec: 78 | template: 79 | spec: 80 | agentConfig: {} 81 | --- 82 | apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1 83 | kind: IntelMachineBinding 84 | metadata: 85 | name: intel-machinebinding 86 | namespace: ${NAMESPACE} 87 | spec: 88 | nodeGUID: ${NODEGUID} 89 | clusterName: intel-rke2-test 90 | intelMachineTemplateName: controlplane-and-worker 91 | -------------------------------------------------------------------------------- /test/e2e/e2e_suite_test.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package e2e 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "testing" 11 | 12 | . "github.com/onsi/ginkgo/v2" 13 | . "github.com/onsi/gomega" 14 | 15 | "github.com/open-edge-platform/cluster-api-provider-intel/test/utils" 16 | ) 17 | 18 | var ( 19 | // Optional Environment Variables: 20 | // - PROMETHEUS_INSTALL_SKIP=true: Skips Prometheus Operator installation during test setup. 21 | // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup. 22 | // These variables are useful if Prometheus or CertManager is already installed, avoiding 23 | // re-installation and conflicts. 24 | skipPrometheusInstall = os.Getenv("PROMETHEUS_INSTALL_SKIP") == "true" 25 | skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true" 26 | // isPrometheusOperatorAlreadyInstalled will be set true when prometheus CRDs be found on the cluster 27 | isPrometheusOperatorAlreadyInstalled = false 28 | // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster 29 | isCertManagerAlreadyInstalled = false 30 | 31 | // projectImage is the name of the image which will be build and loaded 32 | // with the code source changes to be tested. 33 | projectImage = "example.com/cluster-api-provider-intel:v0.0.1" 34 | ) 35 | 36 | // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, 37 | // temporary environment to validate project changes with the the purposed to be used in CI jobs. 38 | // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs 39 | // CertManager and Prometheus. 40 | func TestE2E(t *testing.T) { 41 | RegisterFailHandler(Fail) 42 | _, _ = fmt.Fprintf(GinkgoWriter, "Starting cluster-api-provider-intel integration test suite\n") 43 | RunSpecs(t, "e2e suite") 44 | } 45 | 46 | var _ = BeforeSuite(func() { 47 | By("Ensure that Prometheus is enabled") 48 | _ = utils.UncommentCode("config/default/kustomization.yaml", "#- ../prometheus", "#") 49 | 50 | By("generating files") 51 | cmd := exec.Command("make", "generate") 52 | _, err := utils.Run(cmd) 53 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to run make generate") 54 | 55 | By("generating manifests") 56 | cmd = exec.Command("make", "manifests") 57 | _, err = utils.Run(cmd) 58 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to run make manifests") 59 | 60 | By("building the manager(Operator) image") 61 | cmd = exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage)) 62 | _, err = utils.Run(cmd) 63 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") 64 | 65 | // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is 66 | // built and available before running the tests. Also, remove the following block. 67 | By("loading the manager(Operator) image on Kind") 68 | err = utils.LoadImageToKindClusterWithName(projectImage) 69 | ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") 70 | 71 | // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing. 72 | // To prevent errors when tests run in environments with Prometheus or CertManager already installed, 73 | // we check for their presence before execution. 74 | // Setup Prometheus and CertManager before the suite if not skipped and if not already installed 75 | if !skipPrometheusInstall { 76 | By("checking if prometheus is installed already") 77 | isPrometheusOperatorAlreadyInstalled = utils.IsPrometheusCRDsInstalled() 78 | if !isPrometheusOperatorAlreadyInstalled { 79 | _, _ = fmt.Fprintf(GinkgoWriter, "Installing Prometheus Operator...\n") 80 | Expect(utils.InstallPrometheusOperator()).To(Succeed(), "Failed to install Prometheus Operator") 81 | } else { 82 | _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: Prometheus Operator is already installed. Skipping installation...\n") 83 | } 84 | } 85 | if !skipCertManagerInstall { 86 | By("checking if cert manager is installed already") 87 | isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled() 88 | if !isCertManagerAlreadyInstalled { 89 | _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n") 90 | Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager") 91 | } else { 92 | _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n") 93 | } 94 | } 95 | }) 96 | 97 | var _ = AfterSuite(func() { 98 | // Teardown Prometheus and CertManager after the suite if not skipped and if they were not already installed 99 | if !skipPrometheusInstall && !isPrometheusOperatorAlreadyInstalled { 100 | _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling Prometheus Operator...\n") 101 | utils.UninstallPrometheusOperator() 102 | } 103 | if !skipCertManagerInstall && !isCertManagerAlreadyInstalled { 104 | _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n") 105 | utils.UninstallCertManager() 106 | } 107 | }) 108 | -------------------------------------------------------------------------------- /test/grpc-stub-middleware/grpc_stub_middleware.go: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package grpc_stub_middleware 5 | 6 | import ( 7 | "context" 8 | "os" 9 | 10 | grpcmw "github.com/grpc-ecosystem/go-grpc-middleware" 11 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/logging" 12 | "github.com/open-edge-platform/cluster-api-provider-intel/pkg/tenant" 13 | "google.golang.org/grpc" 14 | ) 15 | 16 | var ( 17 | log = logging.GetLogger("grpc-stub-middleware") 18 | ) 19 | 20 | func GetStubGrpcOptions() []grpc.ServerOption { 21 | var serverOptions []grpc.ServerOption 22 | 23 | var unaryInterceptors []grpc.UnaryServerInterceptor 24 | var streamInterceptors []grpc.StreamServerInterceptor 25 | 26 | unaryInterceptors = append(unaryInterceptors, StubActiveProjectIdGrpcInterceptor()) 27 | 28 | serverOptions = append(serverOptions, 29 | grpc.UnaryInterceptor(grpcmw.ChainUnaryServer(unaryInterceptors...)), 30 | grpc.StreamInterceptor(grpcmw.ChainStreamServer(streamInterceptors...))) 31 | 32 | return serverOptions 33 | } 34 | 35 | // StubActiveProjectIdGrpcInterceptor returns an interceptor that inserts a dummy active project id. 36 | func StubActiveProjectIdGrpcInterceptor() grpc.UnaryServerInterceptor { 37 | return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { 38 | // Get the tenant ID from the environment. Note that tenantId and projectId are used interchangeably. 39 | projectId := os.Getenv("TENANT_ID") 40 | if projectId == "" { 41 | projectId = "53cd37b9-66b2-4cc8-b080-3722ed7af64a" // Fallback to a default tenant ID if not set 42 | } 43 | log.Trace().Msgf("project id intercepted in grpc request: '%s'", projectId) 44 | return handler(tenant.AddActiveProjectIdToContext(ctx, projectId), req) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /trivy.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: (C) 2025 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | scan: 5 | skip-files: 6 | - deployment/charts/intel-infra-provider/templates/role.yaml 7 | 8 | vulnerability: 9 | ignore-unfixed: true 10 | --------------------------------------------------------------------------------