From 2c194c51d40bf6fb85867d70eb58e3b80bcbbe47 Mon Sep 17 00:00:00 2001 From: ausias-armesto Date: Tue, 14 Nov 2023 10:48:34 +0100 Subject: [PATCH] Refactor operator to work with hoprd providence (#149) * Main refactoring * Fix scripts * Update script * Add contracts dir * remove spaces * add time * Update script * Fixing path * Remove unused import * Update dummy script * Download script * Filtering active jobs * remove declare * More bug fixing * Fix double sync * Adding format * Adding modification * Use correct deployer * Update more code * Update to 0.2.0 * Adding more error handling * adding debugging * Adding identity on resource creation * Update runner to self hosted * Update versions * Fixing creating node with different pattern * Basic manual testing works * Include modification of resources * Add more fixes * Remove files * moving events * Updating to alpha2 * cargo update * Fixing clusterHoprd * fixing context * Fixing helm-chart * remove dash * Increasing resources * Fix service monitor * Fixing pipelines * Update parameters README.md * Fixing pipelines * Update parameters README.md * Using github runners * adding docker to publish --------- Co-authored-by: HOPR CI robot --- .../{container-pr.yml => build-docker.yml} | 31 +- ...est-cluster.yml => build-helm-cluster.yml} | 109 +- ...t-operator.yml => build-helm-operator.yml} | 111 ++- ...ontainer-master.yml => publish-docker.yml} | 31 +- ...h-cluster.yml => publish-helm-cluster.yml} | 30 +- ...operator.yml => publish-helm-operator.yml} | 29 +- .github/workflows/security.yaml | 46 - .gitignore | 4 +- Cargo.lock | 937 +++++++++--------- Cargo.toml | 7 +- Dockerfile | 1 + Makefile | 89 +- README.md | 4 +- charts/cluster-hoprd/Chart.yaml | 2 +- charts/cluster-hoprd/README.md | 14 +- .../templates/cluster-hoprd.yaml | 24 +- charts/cluster-hoprd/values-stage.yaml | 100 ++ charts/cluster-hoprd/values.yaml | 30 +- charts/hoprd-operator/Chart.lock | 6 - charts/hoprd-operator/Chart.yaml | 9 +- charts/hoprd-operator/README.md | 109 +- charts/hoprd-operator/templates/NOTES.txt | 101 +- .../templates/configmap-config.yaml | 6 +- .../templates/configmap-scripts.yaml | 62 -- .../templates/crd-cluster-hoprd.yaml | 202 ++-- .../hoprd-operator/templates/crd-hoprd.yaml | 140 +-- .../templates/crd-identity-pool.yaml | 98 ++ .../templates/crd-identity.yaml | 105 ++ .../templates/deployment-adminui.yaml | 28 +- .../templates/deployment-operator.yaml | 15 +- charts/hoprd-operator/templates/rbac.yaml | 16 + charts/hoprd-operator/templates/secret.yaml | 19 - charts/hoprd-operator/values-prod.yaml | 38 + charts/hoprd-operator/values-stage.yaml | 37 + charts/hoprd-operator/values.yaml | 39 +- cluster-hoprd.yaml | 22 - hoprd-node-1.yaml | 85 -- scripts/create-identity-dummy.sh | 46 + scripts/create-identity.sh | 17 + scripts/create-resource.sh | 26 + src/bootstrap_operator.rs | 235 +++-- src/cluster.rs | 536 ++++++---- src/constants.rs | 46 +- src/context_data.rs | 95 +- src/controller_cluster.rs | 78 +- src/controller_hoprd.rs | 57 +- src/controller_identity.rs | 125 +++ src/controller_identity_pool.rs | 130 +++ src/hoprd.rs | 547 +++++----- src/hoprd_deployment.rs | 348 +++---- src/hoprd_deployment_spec.rs | 144 +-- src/hoprd_ingress.rs | 261 +++-- src/hoprd_jobs.rs | 382 ------- src/hoprd_secret.rs | 381 ------- src/hoprd_service.rs | 75 +- src/hoprd_service_monitor.rs | 158 --- src/identity_hoprd.rs | 346 +++++++ ...tence.rs => identity_hoprd_persistence.rs} | 32 +- src/identity_pool.rs | 687 +++++++++++++ src/identity_pool_service_account.rs | 170 ++++ src/identity_pool_service_monitor.rs | 211 ++++ src/main.rs | 58 +- src/model.rs | 107 +- src/operator_config.rs | 11 +- src/servicemonitor.rs | 168 +++- src/utils.rs | 185 ++-- test-data/cluster-hoprd.yaml | 101 ++ test-data/hoprd-node-core.yaml | 99 ++ test-data/hoprd-node-operator.yaml | 100 ++ .../sample_config-prod.yaml | 2 +- .../sample_config-rpch.yaml | 0 .../sample_config-stage.yaml | 2 +- 72 files changed, 5229 insertions(+), 3473 deletions(-) rename .github/workflows/{container-pr.yml => build-docker.yml} (75%) rename .github/workflows/{helm-build-and-test-cluster.yml => build-helm-cluster.yml} (60%) rename .github/workflows/{helm-build-and-test-operator.yml => build-helm-operator.yml} (60%) rename .github/workflows/{container-master.yml => publish-docker.yml} (55%) rename .github/workflows/{helm-publish-cluster.yml => publish-helm-cluster.yml} (58%) rename .github/workflows/{helm-publish-operator.yml => publish-helm-operator.yml} (62%) delete mode 100644 .github/workflows/security.yaml create mode 100644 charts/cluster-hoprd/values-stage.yaml delete mode 100644 charts/hoprd-operator/Chart.lock delete mode 100644 charts/hoprd-operator/templates/configmap-scripts.yaml create mode 100644 charts/hoprd-operator/templates/crd-identity-pool.yaml create mode 100644 charts/hoprd-operator/templates/crd-identity.yaml delete mode 100644 charts/hoprd-operator/templates/secret.yaml create mode 100644 charts/hoprd-operator/values-prod.yaml create mode 100644 charts/hoprd-operator/values-stage.yaml delete mode 100644 cluster-hoprd.yaml delete mode 100644 hoprd-node-1.yaml create mode 100644 scripts/create-identity-dummy.sh create mode 100644 scripts/create-identity.sh create mode 100644 scripts/create-resource.sh create mode 100644 src/controller_identity.rs create mode 100644 src/controller_identity_pool.rs delete mode 100644 src/hoprd_jobs.rs delete mode 100644 src/hoprd_secret.rs delete mode 100644 src/hoprd_service_monitor.rs create mode 100644 src/identity_hoprd.rs rename src/{hoprd_persistence.rs => identity_hoprd_persistence.rs} (63%) create mode 100644 src/identity_pool.rs create mode 100644 src/identity_pool_service_account.rs create mode 100644 src/identity_pool_service_monitor.rs create mode 100644 test-data/cluster-hoprd.yaml create mode 100644 test-data/hoprd-node-core.yaml create mode 100644 test-data/hoprd-node-operator.yaml rename sample_config-prod.yaml => test-data/sample_config-prod.yaml (95%) rename sample_config-rpch.yaml => test-data/sample_config-rpch.yaml (100%) rename sample_config-stage.yaml => test-data/sample_config-stage.yaml (95%) diff --git a/.github/workflows/container-pr.yml b/.github/workflows/build-docker.yml similarity index 75% rename from .github/workflows/container-pr.yml rename to .github/workflows/build-docker.yml index 5853971..3f2b57e 100644 --- a/.github/workflows/container-pr.yml +++ b/.github/workflows/build-docker.yml @@ -1,26 +1,27 @@ --- -name: 'Build Container' - +name: 'Build Docker Image' on: pull_request: + types: + - synchronize + - ready_for_review branches: - master paths-ignore: - "charts/**/*" concurrency: - group: ${{ github.head_ref }}-container + group: ${{ github.head_ref }}-docker cancel-in-progress: true jobs: bump_version: - runs-on: ubuntu-latest + name: Bump version + runs-on: ubuntu-2-core permissions: contents: write steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Bump Cargo version id: bumping_version @@ -46,6 +47,7 @@ jobs: # Update Cargo.lock - name: Build Cargo uses: actions-rs/cargo@v1 + if: ${{ steps.bumping_version.outputs.bumped_version }} with: command: build @@ -53,7 +55,7 @@ jobs: if: ${{ steps.bumping_version.outputs.bumped_version }} uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: Bump cargo version + commit_message: "Bump to cargo version ${{ steps.bumping_version.outputs.bumped_version }}" repository: . commit_user_name: HOPR CI robot commit_user_email: noreply@hoprnet.org @@ -63,11 +65,22 @@ jobs: create_branch: false build: + name: Build docker needs: bump_version - runs-on: ubuntu-latest + runs-on: ubuntu-2-core steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver: kubernetes + - name: Build container image uses: docker/build-push-action@v5 with: push: false - tags: gcr.io/hoprassociation/hoprd-operator:latest + tags: europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:latest + + diff --git a/.github/workflows/helm-build-and-test-cluster.yml b/.github/workflows/build-helm-cluster.yml similarity index 60% rename from .github/workflows/helm-build-and-test-cluster.yml rename to .github/workflows/build-helm-cluster.yml index cbb3694..aa9e883 100644 --- a/.github/workflows/helm-build-and-test-cluster.yml +++ b/.github/workflows/build-helm-cluster.yml @@ -4,6 +4,11 @@ name: 'Package Helm chart Cluster' on: pull_request: + types: + - synchronize + - ready_for_review + branches: + - master paths: - "charts/cluster-hoprd/**" concurrency: @@ -11,14 +16,14 @@ concurrency: cancel-in-progress: true jobs: bump_version: - runs-on: ubuntu-latest + name: Bump version + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Bump Chart version + id: bumping_version run: | helm_chart_version=$(grep '^version:' Chart.yaml | sed 's/.*: //') git_tag=$(git tag -l helm-cluster-hoprd-${helm_chart_version}) @@ -29,14 +34,15 @@ jobs: bump_version=${release_number}.$((patch_number + 1)) echo "The helm chart version ${git_tag} already exists, bumping to version helm-cluster-hoprd-${bump_version}"; sed -i "s/^version: ${helm_chart_version}/version: ${bump_version}/" Chart.yaml - echo "version_bumped=true" >> $GITHUB_ENV + echo "bumped_version=true" >> $GITHUB_OUTPUT fi working-directory: 'charts/cluster-hoprd/' + - name: Commit and push - if: ${{ env.version_bumped }} + if: ${{ steps.bumping_version.outputs.bumped_version }} uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: Bump Helm chart version + commit_message: "Bump Helm chart version to ${{ steps.bumping_version.outputs.bumped_version }}" repository: . commit_user_name: HOPR CI robot commit_user_email: noreply@hoprnet.org @@ -46,76 +52,105 @@ jobs: create_branch: false lint: - runs-on: ubuntu-latest + name: Lint + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - fetch-depth: 0 + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - name: Lint - run: | - helm dependency update - helm lint + run: helm lint working-directory: 'charts/cluster-hoprd/' package: - runs-on: ubuntu-latest + name: Package Helm Chart + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - fetch-depth: 0 + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - name: Get Helm chart version + id: get_version run: | HELM_CHART_VERSION=$(grep '^version:' Chart.yaml | sed 's/.*: //') - echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_ENV + echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT working-directory: 'charts/cluster-hoprd/' - - name: Set up Google Cloud Credentials - uses: google-github-actions/auth@v1 - with: - credentials_json: ${{ secrets.GOOGLE_HOPRASSOCIATION_CREDENTIALS_REGISTRY }} + - name: Helm Package + run: helm package . --version ${{ steps.get_version.outputs.HELM_CHART_VERSION }} + working-directory: 'charts/cluster-hoprd/' - - name: Set up Google Cloud SDK - uses: google-github-actions/setup-gcloud@v1 + trivy: + name: Helm Security Analysis + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 # 93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - project_id: ${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }} - install_components: beta + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - - name: Artifact Registry authentication - run: gcloud auth application-default print-access-token | helm registry login -u oauth2accesstoken --password-stdin https://europe-west3-docker.pkg.dev + - name: Set up python + uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # tag=v4.7.1 + with: + python-version: 3.7 - - name: Helm Package - run: | - helm package . --version ${{ env.HELM_CHART_VERSION }} - working-directory: 'charts/cluster-hoprd/' + - name: Run Trivy vulnerability scanner in IaC mode + uses: aquasecurity/trivy-action@master + with: + scan-type: 'config' + hide-progress: false + format: 'table' + scan-ref: 'charts/hoprd-operator/' + exit-code: '1' + ignore-unfixed: true generate-readme: - runs-on: ubuntu-latest + name: Generate Readme + runs-on: ubuntu-2-core permissions: contents: write steps: - - name: Install readme-generator-for-helm - run: npm install -g @bitnami/readme-generator-for-helm@2.5.0 - - name: Checkout uses: actions/checkout@v4 + + - name: Setup NodeJs + uses: actions/setup-node@v4 with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - fetch-depth: 0 + node-version: 18 + + - name: Install readme-generator-for-helm + run: npm install -g @bitnami/readme-generator-for-helm@2.5.0 - name: Execute readme-generator-for-helm + id: generator run: | readme-generator --values "charts/cluster-hoprd/values.yaml" --readme "charts/cluster-hoprd/README.md" --schema "/tmp/schema.json" if git status -s | grep charts; then - echo "readme_updated=true" >> $GITHUB_ENV + echo "readme_updated=true" >> $GITHUB_OUTPUT fi - name: Commit and push - if: ${{ env.readme_updated }} + if: ${{ steps.generator.outputs.readme_updated }} uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Update parameters README.md diff --git a/.github/workflows/helm-build-and-test-operator.yml b/.github/workflows/build-helm-operator.yml similarity index 60% rename from .github/workflows/helm-build-and-test-operator.yml rename to .github/workflows/build-helm-operator.yml index 7ac150d..296677c 100644 --- a/.github/workflows/helm-build-and-test-operator.yml +++ b/.github/workflows/build-helm-operator.yml @@ -4,6 +4,11 @@ name: 'Package Helm chart Operator' on: pull_request: + types: + - synchronize + - ready_for_review + branches: + - master paths: - "charts/hoprd-operator/**" concurrency: @@ -11,14 +16,14 @@ concurrency: cancel-in-progress: true jobs: bump_version: - runs-on: ubuntu-latest + name: Bump version + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Bump Chart version + id: bumping_version run: | helm_chart_version=$(grep '^version:' Chart.yaml | sed 's/.*: //') git_tag=$(git tag -l helm-hoprd-operator-${helm_chart_version}) @@ -29,14 +34,15 @@ jobs: bump_version=${release_number}.$((patch_number + 1)) echo "The helm chart version ${git_tag} already exists, bumping to version helm-hoprd-operator-${bump_version}"; sed -i "s/^version: ${helm_chart_version}/version: ${bump_version}/" Chart.yaml - echo "version_bumped=true" >> $GITHUB_ENV + echo "bumped_version=true" >> $GITHUB_OUTPUT fi working-directory: 'charts/hoprd-operator/' + - name: Commit and push - if: ${{ env.version_bumped }} + if: ${{ steps.bumping_version.outputs.bumped_version }} uses: stefanzweifel/git-auto-commit-action@v5 with: - commit_message: Bump Helm chart version + commit_message: "Bump Helm chart version to ${{ steps.bumping_version.outputs.bumped_version }}" repository: . commit_user_name: HOPR CI robot commit_user_email: noreply@hoprnet.org @@ -46,78 +52,105 @@ jobs: create_branch: false lint: - runs-on: ubuntu-latest + name: Lint + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - fetch-depth: 0 + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - name: Lint - run: | - helm dependency update - helm lint + run: helm lint working-directory: 'charts/hoprd-operator/' package: - runs-on: ubuntu-latest + name: Package Helm Chart + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - fetch-depth: 0 + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - name: Get Helm chart version + id: get_version run: | HELM_CHART_VERSION=$(grep '^version:' Chart.yaml | sed 's/.*: //') - echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_ENV + echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT + working-directory: 'charts/hoprd-operator/' + + - name: Helm Package + run: helm package . --version ${{ steps.get_version.outputs.HELM_CHART_VERSION }} working-directory: 'charts/hoprd-operator/' - - name: Set up Google Cloud Credentials - uses: google-github-actions/auth@v1 + trivy: + name: Helm Security Analysis + runs-on: ubuntu-latest + permissions: + security-events: write + actions: read + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 # 93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - credentials_json: ${{ secrets.GOOGLE_HOPRASSOCIATION_CREDENTIALS_REGISTRY }} + version: latest + token: ${{ secrets.GITHUB_TOKEN }} - - name: Set up Google Cloud SDK - uses: google-github-actions/setup-gcloud@v1 + - name: Set up python + uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # tag=v4.7.1 with: - project_id: ${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }} - install_components: beta + python-version: 3.7 - - name: Artifact Registry authentication - run: gcloud auth application-default print-access-token | helm registry login -u oauth2accesstoken --password-stdin https://europe-west3-docker.pkg.dev + - name: Run Trivy vulnerability scanner in IaC mode + uses: aquasecurity/trivy-action@master + with: + scan-type: 'config' + hide-progress: false + format: 'table' + scan-ref: 'charts/hoprd-operator/' + exit-code: '1' + ignore-unfixed: true - - name: Helm Package - run: | - helm repo add mittwald https://helm.mittwald.de - helm repo update - helm dependency build - helm package . --version ${{ env.HELM_CHART_VERSION }} - working-directory: 'charts/hoprd-operator/' generate-readme: - runs-on: ubuntu-latest + name: Generate Readme + runs-on: ubuntu-2-core permissions: contents: write steps: - - name: Install readme-generator-for-helm - run: npm install -g @bitnami/readme-generator-for-helm@2.5.0 - - name: Checkout uses: actions/checkout@v4 + + - name: Setup NodeJs + uses: actions/setup-node@v4 with: - ref: ${{github.event.pull_request.head.ref}} - repository: ${{github.event.pull_request.head.repo.full_name}} - fetch-depth: 0 + node-version: 18 + + - name: Install readme-generator-for-helm + run: npm install -g @bitnami/readme-generator-for-helm@2.5.0 - name: Execute readme-generator-for-helm + id: generator run: | readme-generator --values "charts/hoprd-operator/values.yaml" --readme "charts/hoprd-operator/README.md" --schema "/tmp/schema.json" if git status -s | grep charts; then - echo "readme_updated=true" >> $GITHUB_ENV + echo "readme_updated=true" >> $GITHUB_OUTPUT fi - name: Commit and push - if: ${{ env.readme_updated }} + if: ${{ steps.generator.outputs.readme_updated }} uses: stefanzweifel/git-auto-commit-action@v5 with: commit_message: Update parameters README.md diff --git a/.github/workflows/container-master.yml b/.github/workflows/publish-docker.yml similarity index 55% rename from .github/workflows/container-master.yml rename to .github/workflows/publish-docker.yml index 95cfe0a..62bf351 100644 --- a/.github/workflows/container-master.yml +++ b/.github/workflows/publish-docker.yml @@ -1,6 +1,6 @@ --- -name: 'Publish Container' +name: 'Publish Docker Image' on: push: @@ -13,17 +13,23 @@ concurrency: cancel-in-progress: false jobs: publish: - runs-on: ubuntu-latest + name: Publish + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 with: - fetch-depth: 0 + driver: kubernetes - name: Set up Google Cloud Credentials + id: auth uses: google-github-actions/auth@v1 with: + token_format: "access_token" credentials_json: ${{ secrets.GOOGLE_HOPRASSOCIATION_CREDENTIALS_REGISTRY }} - name: Set up Google Cloud SDK @@ -32,25 +38,30 @@ jobs: project_id: ${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }} install_components: beta - - name: Google Container authentication - run: gcloud auth configure-docker --quiet gcr.io + - name: Login Google Container Registry + uses: docker/login-action@v3 + with: + registry: europe-west3-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} - name: Get package version + id: version run: | docker_tag=$(grep -A 1 "hoprd_operator" Cargo.lock | grep version | sed 's/.* "//' | sed 's/"//' | tr -d '\n') - echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_ENV + echo "DOCKER_TAG=${docker_tag}" >> $GITHUB_OUTPUT - name: Build container image uses: docker/build-push-action@v5 with: push: true tags: | - gcr.io/hoprassociation/hoprd-operator:${{ env.DOCKER_TAG }} - gcr.io/hoprassociation/hoprd-operator:latest + europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:${{ steps.version.outputs.DOCKER_TAG }} + europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:latest - name: Tag Docker version run: | git config user.email "noreply@hoprnet.org" git config user.name "HOPR CI robot" - git tag ${{ env.DOCKER_TAG }} - git push origin ${{ env.DOCKER_TAG }} + git tag ${{ steps.version.outputs.DOCKER_TAG }} + git push origin ${{ steps.version.outputs.DOCKER_TAG }} diff --git a/.github/workflows/helm-publish-cluster.yml b/.github/workflows/publish-helm-cluster.yml similarity index 58% rename from .github/workflows/helm-publish-cluster.yml rename to .github/workflows/publish-helm-cluster.yml index 5ceb0f3..5a55d67 100644 --- a/.github/workflows/helm-publish-cluster.yml +++ b/.github/workflows/publish-helm-cluster.yml @@ -13,23 +13,30 @@ concurrency: cancel-in-progress: false jobs: publish: - runs-on: ubuntu-latest + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 + + - name: Set up Helm + uses: azure/setup-helm@v3 with: - fetch-depth: 0 + version: latest + token: ${{ secrets.GITHUB_TOKEN }}s - name: Get Helm chart version + id: get_version run: | HELM_CHART_VERSION=$(grep '^version:' Chart.yaml | sed 's/.*: //') - echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_ENV + echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT working-directory: 'charts/cluster-hoprd/' - name: Set up Google Cloud Credentials + id: auth uses: google-github-actions/auth@v1 with: + token_format: "access_token" credentials_json: ${{ secrets.GOOGLE_HOPRASSOCIATION_CREDENTIALS_REGISTRY }} - name: Set up Google Cloud SDK @@ -38,21 +45,26 @@ jobs: project_id: ${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }} install_components: beta - - name: Artifact Registry authentication - run: gcloud auth application-default print-access-token | helm registry login -u oauth2accesstoken --password-stdin https://europe-west3-docker.pkg.dev + - name: Login Google Container Registry + uses: docker/login-action@v3 + with: + registry: europe-west3-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} - name: Helm Package run: | - helm package . --version ${{ env.HELM_CHART_VERSION }} + helm package . --version ${{ steps.get_version.outputs.HELM_CHART_VERSION }} working-directory: 'charts/cluster-hoprd/' + - name: Helm Publish run: | - helm push cluster-hoprd-${{ env.HELM_CHART_VERSION }}.tgz oci://europe-west3-docker.pkg.dev/${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }}/helm-charts + helm push cluster-hoprd-${{ steps.get_version.outputs.HELM_CHART_VERSION }}.tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts working-directory: 'charts/cluster-hoprd/' - name: Tag Helm version run: | git config user.email "noreply@hoprnet.org" git config user.name "HOPR CI robot" - git tag helm-cluster-hoprd-${{ env.HELM_CHART_VERSION }} - git push origin helm-cluster-hoprd-${{ env.HELM_CHART_VERSION }} + git tag helm-cluster-hoprd-${{ steps.get_version.outputs.HELM_CHART_VERSION }} + git push origin helm-cluster-hoprd-${{ steps.get_version.outputs.HELM_CHART_VERSION }} diff --git a/.github/workflows/helm-publish-operator.yml b/.github/workflows/publish-helm-operator.yml similarity index 62% rename from .github/workflows/helm-publish-operator.yml rename to .github/workflows/publish-helm-operator.yml index 8b711e1..63e1e3d 100644 --- a/.github/workflows/helm-publish-operator.yml +++ b/.github/workflows/publish-helm-operator.yml @@ -13,23 +13,24 @@ concurrency: cancel-in-progress: false jobs: publish: - runs-on: ubuntu-latest + runs-on: ubuntu-2-core steps: - name: Checkout uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Get Helm chart version + id: get_version run: | HELM_CHART_VERSION=$(grep '^version:' Chart.yaml | sed 's/.*: //') - echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_ENV + echo "HELM_CHART_VERSION=${HELM_CHART_VERSION}" >> $GITHUB_OUTPUT working-directory: 'charts/hoprd-operator/' - name: Set up Google Cloud Credentials + id: auth uses: google-github-actions/auth@v1 with: + token_format: "access_token" credentials_json: ${{ secrets.GOOGLE_HOPRASSOCIATION_CREDENTIALS_REGISTRY }} - name: Set up Google Cloud SDK @@ -38,24 +39,26 @@ jobs: project_id: ${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }} install_components: beta - - name: Artifact Registry authentication - run: gcloud auth application-default print-access-token | helm registry login -u oauth2accesstoken --password-stdin https://europe-west3-docker.pkg.dev + - name: Login Google Container Registry + uses: docker/login-action@v3 + with: + registry: europe-west3-docker.pkg.dev + username: oauth2accesstoken + password: ${{ steps.auth.outputs.access_token }} - name: Helm Package run: | - helm repo add mittwald https://helm.mittwald.de - helm repo update - helm dependency build - helm package . --version ${{ env.HELM_CHART_VERSION }} + helm package . --version ${{ steps.get_version.outputs.HELM_CHART_VERSION }} working-directory: 'charts/hoprd-operator/' + - name: Helm Publish run: | - helm push hoprd-operator-${{ env.HELM_CHART_VERSION }}.tgz oci://europe-west3-docker.pkg.dev/${{ secrets.GOOGLE_HOPRASSOCIATION_PROJECT }}/helm-charts + helm push hoprd-operator-${{ steps.get_version.outputs.HELM_CHART_VERSION }}.tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts working-directory: 'charts/hoprd-operator/' - name: Tag Helm version run: | git config user.email "noreply@hoprnet.org" git config user.name "HOPR CI robot" - git tag helm-hoprd-operator-${{ env.HELM_CHART_VERSION }} - git push origin helm-hoprd-operator-${{ env.HELM_CHART_VERSION }} + git tag helm-hoprd-operator-${{ steps.get_version.outputs.HELM_CHART_VERSION }} + git push origin helm-hoprd-operator-${{ steps.get_version.outputs.HELM_CHART_VERSION }} diff --git a/.github/workflows/security.yaml b/.github/workflows/security.yaml deleted file mode 100644 index a491d7a..0000000 --- a/.github/workflows/security.yaml +++ /dev/null @@ -1,46 +0,0 @@ ---- - -# source: https://dev.to/aws-builders/improving-your-cicd-pipeline-helm-charts-security-scanning-with-trivy-and-github-actions-3315 - -name: Security scans - -on: push - -jobs: - chart: - runs-on: ubuntu-latest - permissions: - security-events: write - actions: read - contents: read - steps: - - name: Checkout - uses: actions/checkout@v4 # 93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 - with: - fetch-depth: 0 - - - name: Set up Helm - uses: azure/setup-helm@5119fcb9089d432beecbf79bb2c7915207344b78 # tag=v3.5 - with: - version: v3.6.3 - - - name: Set up python - uses: actions/setup-python@65d7f2d534ac1bc67fcd62888c5f4f3d2cb2b236 # tag=v4.7.1 - with: - python-version: 3.7 - - - name: Run Trivy vulnerability scanner in IaC mode - uses: aquasecurity/trivy-action@master - with: - scan-type: 'config' - hide-progress: false - format: 'table' - scan-ref: 'charts/hoprd-operator/' - exit-code: '1' - ignore-unfixed: true - - ## sarif supported only on GH Enterprise - # - name: Upload Trivy scan results to GitHub Security tab - # uses: github/codeql-action/upload-sarif@312e093a1892bd801f026f1090904ee8e460b9b6 # v2.1.34 - # with: - # sarif_file: 'trivy-results-hoprd-operator.sarif' diff --git a/.gitignore b/.gitignore index 01102ce..c31b568 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,5 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -charts/*/values-testing*.yaml -charts/hoprd-operator/charts/*.tgz +test-data/identity-pool-secret.yaml +*hoprd-*.tgz diff --git a/Cargo.lock b/Cargo.lock index b386d46..8a18395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,14 +19,24 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", ] [[package]] @@ -58,18 +68,18 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] @@ -91,9 +101,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", @@ -106,45 +116,42 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - -[[package]] -name = "base64" -version = "0.21.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "0f8e7c90afad890484a21653d08b6e209ae34770fb5ee298f9c699fcc1e5c856" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -164,17 +171,17 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.0", + "windows-targets", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "core-foundation" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "termcolor", - "unicode-width", + "core-foundation-sys", + "libc", ] [[package]] @@ -183,55 +190,11 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" -[[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.28", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] - [[package]] name = "darling" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ "darling_core", "darling_macro", @@ -239,34 +202,37 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -281,21 +247,21 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "fnv" @@ -303,35 +269,20 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -344,9 +295,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -354,15 +305,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -371,38 +322,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -418,9 +369,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -429,15 +380,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.27.3" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" dependencies = [ "ahash", "allocator-api2", @@ -445,12 +396,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "home" @@ -458,14 +406,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "hoprd_operator" -version = "0.1.46" +version = "0.2.0" dependencies = [ "async-recursion", + "base64", "chrono", "futures", "json-patch", @@ -486,9 +435,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" dependencies = [ "bytes", "fnv", @@ -508,9 +457,9 @@ dependencies = [ [[package]] name = "http-range-header" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" @@ -520,15 +469,15 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" dependencies = [ "bytes", "futures-channel", @@ -540,7 +489,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -548,21 +497,19 @@ dependencies = [ ] [[package]] -name = "hyper-openssl" -version = "0.9.2" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ + "futures-util", "http", "hyper", - "linked_hash_set", - "once_cell", - "openssl", - "openssl-sys", - "parking_lot", + "log", + "rustls", + "rustls-native-certs", "tokio", - "tokio-openssl", - "tower-layer", + "tokio-rustls", ] [[package]] @@ -579,26 +526,25 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.56" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -609,9 +555,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", @@ -628,15 +574,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -666,11 +612,11 @@ dependencies = [ [[package]] name = "k8s-openapi" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95578de7d6eac4fba42114bc751e38c59a739968769df1be56feba6f17fd148e" +checksum = "edc3606fd16aca7989db2f84bb25684d0270c6d6fa1dbcd0025af7b4130523a6" dependencies = [ - "base64 0.21.0", + "base64", "bytes", "chrono", "serde", @@ -680,9 +626,9 @@ dependencies = [ [[package]] name = "kube" -version = "0.85.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a189cb8721a47de68d883040713bbb9c956763d784fcf066828018d32c180b96" +checksum = "e34392aea935145070dcd5b39a6dea689ac6534d7d117461316c3d157b1d0fc3" dependencies = [ "k8s-openapi", "kube-client", @@ -693,11 +639,11 @@ dependencies = [ [[package]] name = "kube-client" -version = "0.85.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98989b6e1f27695afe22aa29c94136fa06be5e8d28b91222e6dfbe5a460c803f" +checksum = "7266548b9269d9fa19022620d706697e64f312fb2ba31b93e6986453fcc82c92" dependencies = [ - "base64 0.20.0", + "base64", "bytes", "chrono", "either", @@ -706,14 +652,15 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-openssl", + "hyper-rustls", "hyper-timeout", "jsonpath_lib", "k8s-openapi", "kube-core", - "openssl", "pem", "pin-project", + "rustls", + "rustls-pemfile", "secrecy", "serde", "serde_json", @@ -728,9 +675,9 @@ dependencies = [ [[package]] name = "kube-core" -version = "0.85.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24d23bf764ec9a5652f943442ff062b91fd52318ea6d2fc11115f19d8c84d13" +checksum = "b8321c315b96b59f59ef6b33f604b84b905ab8f9ff114a4f909d934c520227b1" dependencies = [ "chrono", "form_urlencoded", @@ -746,22 +693,22 @@ dependencies = [ [[package]] name = "kube-derive" -version = "0.85.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bbec4da219dcb02bb32afd762a7ac4dffd47ed92b7e35ac9a7b961d21327117" +checksum = "d54591e1f37fc329d412c0fdaced010cc1305b546a39f283fc51700f8fb49421" dependencies = [ "darling", "proc-macro2", "quote", "serde_json", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] name = "kube-runtime" -version = "0.85.0" +version = "0.87.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "381224caa8a6fc16f8251cf1fd6d8678cdf5366f33000a923e4c54192e4b25b5" +checksum = "e511e2c1a368d9d4bf6e70db58197e535d818df355b5a2007a8aeb17a370a8ba" dependencies = [ "ahash", "async-trait", @@ -791,39 +738,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - -[[package]] -name = "linked_hash_set" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47186c6da4d81ca383c7c47c1bfc80f4b95f4720514d860a5407aaf4233f9588" -dependencies = [ - "linked-hash-map", -] +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -831,12 +754,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "matchers" @@ -844,14 +764,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -870,14 +790,13 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys", ] [[package]] @@ -892,18 +811,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", @@ -911,59 +830,39 @@ dependencies = [ [[package]] name = "object" -version = "0.31.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "openssl" -version = "0.10.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" +name = "openssl-probe" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.28", -] +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.1.5+3.1.3" +version = "300.1.6+3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559068e4c12950d7dcaa1857a61725c0d38d4fc03ff8e070ab31a75d6e316491" +checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ "cc", "libc", @@ -974,9 +873,9 @@ dependencies = [ [[package]] name = "ordered-float" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ "num-traits", ] @@ -999,57 +898,58 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets", ] [[package]] name = "pem" -version = "1.1.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "3163d2912b7c3b52d651a055f2c7eec9ba5cd22d26ef75b8dd3a59980b185923" dependencies = [ - "base64 0.13.1", + "base64", + "serde", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.39", ] [[package]] name = "pin-project-lite" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cc1b0bf1727a77a54b6654e7b5f1af8604923edc8b81885f8ec92f9e3f0a05" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" @@ -1059,9 +959,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" @@ -1071,18 +977,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.29" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1119,20 +1025,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.8.2" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ - "regex-syntax 0.7.2", + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -1144,6 +1053,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1152,9 +1072,23 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys", +] [[package]] name = "rustc-demangle" @@ -1162,17 +1096,69 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustls" +version = "0.21.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "schannel" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys", +] [[package]] name = "schemars" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" dependencies = [ "dyn-clone", "schemars_derive", @@ -1182,9 +1168,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.12" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" dependencies = [ "proc-macro2", "quote", @@ -1194,15 +1180,19 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "scratch" -version = "1.0.5" +name = "sct" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "secrecy" @@ -1214,11 +1204,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -1235,13 +1248,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] @@ -1257,9 +1270,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "indexmap", "itoa", @@ -1269,9 +1282,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.25" +version = "0.9.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574" +checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c" dependencies = [ "indexmap", "itoa", @@ -1282,9 +1295,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -1300,24 +1313,24 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1325,14 +1338,20 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.10.0" @@ -1352,42 +1371,33 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.47" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] @@ -1402,11 +1412,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", + "powerfmt", "serde", "time-core", ] @@ -1419,9 +1430,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "tokio" -version = "1.33.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "libc", @@ -1429,9 +1440,9 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -1446,32 +1457,30 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] -name = "tokio-openssl" -version = "0.6.3" +name = "tokio-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "futures-util", - "openssl", - "openssl-sys", + "rustls", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1501,12 +1510,12 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "base64 0.20.0", - "bitflags", + "base64", + "bitflags 2.4.1", "bytes", "futures-core", "futures-util", @@ -1552,7 +1561,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.39", ] [[package]] @@ -1567,12 +1576,12 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] @@ -1611,21 +1620,21 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "unsafe-libyaml" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa" [[package]] -name = "unsafe-libyaml" -version = "0.2.8" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1865806a559042e51ab5414598446a5871b561d21b6764f2eabb0dd481d880a6" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "valuable" @@ -1647,11 +1656,10 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] @@ -1663,9 +1671,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1673,24 +1681,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1698,22 +1706,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "winapi" @@ -1731,15 +1739,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1747,21 +1746,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", + "windows-targets", ] [[package]] @@ -1770,122 +1760,85 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.48.0", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +name = "zerocopy" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557" +dependencies = [ + "zerocopy-derive", +] [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "zerocopy-derive" +version = "0.7.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 08c57ee..e2936c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hoprd_operator" -version = "0.1.46" +version = "0.2.0" authors = ["HOPR Association "] edition = "2021" @@ -9,11 +9,11 @@ tokio = { version = "1.33", features = [ "macros", "rt-multi-thread", ] } -kube = { version = "0.85", default-features = true, features = [ +kube = { version = "0.87", default-features = true, features = [ "derive", "runtime", ] } -k8s-openapi = { version = "0.19", default-features = false, features = [ +k8s-openapi = { version = "0.20", default-features = false, features = [ "v1_24", ] } futures = "0.3" @@ -32,6 +32,7 @@ serde_yaml = "0.9.25" chrono = "0.4.31" tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", default-features = true, features = ["env-filter"]} +base64 = "0.21.5" [features] # Force openssl-sys to staticly link in the openssl library. Necessary when diff --git a/Dockerfile b/Dockerfile index dee99af..3057383 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,7 @@ LABEL name="hoprd operator" \ summary="Operator managing hoprd instances" \ description="Automation to introduce a hoprd network into a Kubernetes cluster using a dedicated operator" COPY --from=builder /hoprd_operator/target/hoprd_operator /bin/hoprd_operator +COPY ./scripts /app/ ENV OPERATOR_ENVIRONMENT=production diff --git a/Makefile b/Makefile index d0ef875..440c9c2 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,58 @@ -gcp-login: + +.PHONY: help +help: ## Show help of available commands + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +gcp-login: ## Login into GCP gcloud auth configure-docker europe-west3-docker.pkg.dev gcloud auth application-default print-access-token | helm registry login -u oauth2accesstoken --password-stdin https://europe-west3-docker.pkg.dev -build: +build: ## Rust build cargo build -run: +run: ## Rust run nohup cargo run & -helm-test: - helm install --dry-run --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-testing.yaml hoprd-operator ./charts/hoprd-operator/ - -helm-install: - helm install --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-testing.yaml hoprd-operator ./charts/hoprd-operator/ - -helm-uninstall: - helm uninstall --namespace hoprd-operator hoprd-operator - -helm-upgrade: - helm upgrade --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-testing.yaml hoprd-operator ./charts/hoprd-operator/ - sleep 3 - kubectl delete deployment -n hoprd-operator hoprd-operator-controller - -helm-package-operator: - helm repo add mittwald https://helm.mittwald.de - helm repo update - helm dependency build charts/hoprd-operator - helm package charts/hoprd-operator --version 0.0.1 - -helm-package-cluster: - helm package charts/cluster-hoprd --version 0.0.1 +helm-test: ## Print helm resources + helm install --dry-run --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-stage.yaml hoprd-operator ./charts/hoprd-operator/ -helm-publish-operator: - helm push hoprd-operator-0.0.1.tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts +helm-lint: ## Lint Helm + helm lint ./charts/hoprd-operator + helm lint ./charts/cluster-hoprd -helm-publish-cluster: - helm push cluster-hoprd-0.0.1.tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts +helm-install: ## Install helm chart using values-stage.yaml file + helm install --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-stage.yaml hoprd-operator ./charts/hoprd-operator/ -create-node: - kubectl apply -f hoprd-node-1.yaml - -delete-node: - kubectl delete -f hoprd-node-1.yaml +helm-uninstall: ## Uninstall helm chart + helm uninstall --namespace hoprd-operator hoprd-operator -docker-build: - docker build -t gcr.io/hoprassociation/hoprd-operator:latest --platform linux/amd64 --progress plain . +helm-upgrade: ## Update helm-chart templates into cluster and remove deployment to be run within VsCode in debug mode + helm upgrade --namespace hoprd-operator --create-namespace -f ./charts/hoprd-operator/values-stage.yaml hoprd-operator ./charts/hoprd-operator/ + # sleep 3 + # kubectl delete deployment -n hoprd-operator hoprd-operator-controller -docker-push: - docker push gcr.io/hoprassociation/hoprd-operator:latest +helm-package: ## Creates helm package + helm package charts/hoprd-operator --version $$(yq '.version' charts/hoprd-operator/Chart.yaml) + helm package charts/cluster-hoprd --version $$(yq '.version' charts/cluster-hoprd/Chart.yaml) -create-cluster: - kubectl apply -f cluster-hoprd.yaml +helm-publish: ## Deploys helm package to GCP artifact registry + helm push hoprd-operator-$$(yq '.version' charts/hoprd-operator/Chart.yaml).tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts + helm push cluster-hoprd-$$(yq '.version' charts/cluster-hoprd/Chart.yaml).tgz oci://europe-west3-docker.pkg.dev/hoprassociation/helm-charts -delete-cluster: - kubectl delete -f cluster-hoprd.yaml +docker-build: ## Builds docker image + docker build -t europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:latest --no-cache --platform linux/amd64 --progress plain . -UNLOCK_PATCH_DATA="{\"metadata\":{\"labels\":{\"hoprds.hoprnet.org/locked\": \"false\"}}}" -LOCK_PATCH_DATA="{\"metadata\":{\"labels\":{\"hoprds.hoprnet.org/locked\": \"true\"}}}" +docker-push: ## Deploys docker image into GCP Artifact registry + docker push europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:latest -lock-secrets: - for secret in `kubectl get secrets -n hoprd-operator -l hoprds.hoprnet.org/locked=false -o jsonpath="{.items[*].metadata.name}"`; do kubectl patch secret -n hoprd-operator $$secret --type merge --patch $(LOCK_PATCH_DATA); done +create-identity: ## Create identity resources + kubectl apply -f ./test-data/identity-pool.yaml + kubectl apply -f ./test-data/identity-hoprd.yaml + kubectl patch -n hoprd-operator IdentityPool pool-hoprd-operator --type='json' -p='[{"op": "replace", "path": "/spec/minReadyIdentities", "value":1}]' + kubectl patch -n rotsee IdentityPool core-rotsee --type='json' -p='[{"op": "replace", "path": "/spec/minReadyIdentities", "value":1}]' -unlock-secrets: - for secret in `kubectl get secrets -n hoprd-operator -l hoprds.hoprnet.org/locked=true -o jsonpath="{.items[*].metadata.name}"`; do kubectl patch secret -n hoprd-operator $$secret --type merge --patch $(UNLOCK_PATCH_DATA); done +delete-identity: ## Deletes identity resources + kubectl patch -n hoprd-operator IdentityPool pool-hoprd-operator --type='json' -p='[{"op": "replace", "path": "/spec/minReadyIdentities", "value":0}]' + kubectl patch -n rotsee IdentityPool core-rotsee --type='json' -p='[{"op": "replace", "path": "/spec/minReadyIdentities", "value":0}]' + kubectl delete -f ./test-data/identity-hoprd.yaml + kubectl apply -f ./test-data/identity-pool.yaml diff --git a/README.md b/README.md index 3af8e6d..8b8d22b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This operator provides two CRD: - **ClusterHoprd**: This resource manage a cluster of related hoprd nodes. See the [specifications](./charts/hoprd-operator/templates/crd-cluster-hoprd.yaml) for details about what can be configured on a cluster of nodes. -Note: Keep in mind that the `secret.secretName` and `network` attributes of a node cannot be modified. +Note: Keep in mind that the `network` attributes of a node cannot be modified. ## Development @@ -38,6 +38,6 @@ kopium servicemonitors.monitoring.coreos.com -A > src/service_monitor.rs Build the hoprd-operator container using in the repo root: ```shell -docker build -t gcr.io/hoprassociation/hoprd-operator:latest . +docker build -t europe-west3-docker.pkg.dev/hoprassociation/docker-images/hoprd-operator:latest . ``` diff --git a/charts/cluster-hoprd/Chart.yaml b/charts/cluster-hoprd/Chart.yaml index bd4e756..ca2067e 100644 --- a/charts/cluster-hoprd/Chart.yaml +++ b/charts/cluster-hoprd/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: cluster-hoprd -version: 0.1.1 +version: 0.2.0 description: A Helm chart to deploy ClusterHoprd type: application icon: "https://hoprnet.org/assets/icons/logo.svg" diff --git a/charts/cluster-hoprd/README.md b/charts/cluster-hoprd/README.md index d145c19..b935052 100644 --- a/charts/cluster-hoprd/README.md +++ b/charts/cluster-hoprd/README.md @@ -15,9 +15,11 @@ This chart packages the creation of a ClusterHoprd ### Cluster Hoprd parameters -| Name | Description | Value | -| -------------------- | ---------------------------------------------------- | ------ | -| `network` | Network of the ClusterHoprd | `""` | -| `ingress.enabled` | Whether to create or not the Ingress resource | `true` | -| `monitoring.enabled` | Whether to create or not the ServiceMonitor resource | `true` | -| `nodes` | Array of node configuration | `[]` | +| Name | Description | Value | +| ---------------------- | ----------------------------- | ------- | +| `identityPoolName` | Name of the identity pool | `""` | +| `replicas` | Number of instances | `1` | +| `version` | Hoprd node version to run | `2.0.2` | +| `enabled` | Running status of the nodes | `true` | +| `deployment.resources` | Deployment resources spec | `""` | +| `config` | Custom configuration of nodes | `""` | diff --git a/charts/cluster-hoprd/templates/cluster-hoprd.yaml b/charts/cluster-hoprd/templates/cluster-hoprd.yaml index b7a6575..cbf75d1 100644 --- a/charts/cluster-hoprd/templates/cluster-hoprd.yaml +++ b/charts/cluster-hoprd/templates/cluster-hoprd.yaml @@ -1,20 +1,16 @@ --- -apiVersion: hoprnet.org/v1alpha +apiVersion: hoprnet.org/v1alpha2 kind: ClusterHoprd metadata: name: {{ include "cluster-hoprd.name" . }} namespace: {{ .Release.Namespace | quote }} spec: - network: {{ .Values.network }} - ingress: - enabled: {{ .Values.ingress.enabled }} - monitoring: - enabled: {{ .Values.monitoring.enabled }} - nodes: - {{- range .Values.nodes }} - - name: {{ .name }} - version: {{ .version }} - replicas: {{ .replicas }} - enabled: {{ .enabled }} - config: {{ .config | toYaml | nindent 8 }} - {{- end }} + identityPoolName: {{ .Values.identityPoolName | quote }} + replicas: {{ .Values.replicas }} + version: {{ .Values.version | quote }} + enabled: {{ .Values.enabled }} + {{- if .Values.deployment.resources }} + deployment: + resources: {{ .Values.deployment.resources | toYaml | nindent 4 }} + {{- end }} + config: {{ .Values.config | toYaml | nindent 4 }} \ No newline at end of file diff --git a/charts/cluster-hoprd/values-stage.yaml b/charts/cluster-hoprd/values-stage.yaml new file mode 100644 index 0000000..45a2bf7 --- /dev/null +++ b/charts/cluster-hoprd/values-stage.yaml @@ -0,0 +1,100 @@ +identityPoolName: identity-pool-hoprd-operator +replicas: 1 +version: latest +enabled: true +# deployment: +# resources: | +# limits: +# cpu: 2000m +# memory: 3Gi +# requests: +# cpu: 750m +# memory: 512Mi +config: | + hopr: + host: + address: !IPv4 0.0.0.0 + port: 9091 + db: + data: "/app/hoprd-db" + initialize: true + force_initialize: false + strategy: + on_fail_continue: true + allow_recursive: false + finalize_channel_closure: true + strategies: + - !Promiscuous + max_channels: 10 + network_quality_threshold: 0.5 + new_channel_stake: "1000000 HOPR" + minimum_node_balance: "10000000 HOPR" + min_network_size_samples: 20 + enforce_max_channels: true + - !AutoFunding + funding_amount: "1000000 HOPR" + min_stake_threshold: "100000 HOPR" + - !Aggregating + aggregation_threshold: 1000000 + unrealized_balance_ratio: 0.9 + aggregation_timeout: 60 + aggregate_on_channel_close: true + - !AutoRedeeming + redeem_only_aggregated: True + heartbeat: + variance: 2000 + interval: 20000 + threshold: 60000 + network_options: + min_delay: 1 + max_delay: 300 + quality_avg_window_size: 25 + quality_bad_threshold: 0.2 + quality_offline_threshold: 0.5 + quality_step: 0.1 + ignore_timeframe: 600 + backoff_exponent: 1.5 + backoff_min: 2 + backoff_max: 300 + healthcheck: + enable: true + host: 0.0.0.0 + port: 8080 + transport: + announce_local_addresses: false + prefer_local_addresses: false + protocol: + ack: + timeout: 15 + heartbeat: + timeout: 15 + msg: + timeout: 15 + ticket_aggregation: + timeout: 15 + chain: + network: rotsee + announce: true + provider: + check_unrealized_balance: true + safe_module: + safe_transaction_service_provider: https://safe-transaction.prod.hoprtech.net/ + safe_address: "0x0000000000000000000000000000000000000000" + module_address: "0x0000000000000000000000000000000000000000" + identity: + file: "/app/hoprd-db/.hoprd.id" + password: "" + private_key: + inbox: + capacity: 512 + max_age: 900 + excluded_tags: + - 0 + api: + enable: true + auth: !Token "" + host: + address: !IPv4 0.0.0.0 + port: 3001 + test: + use_weak_crypto: false diff --git a/charts/cluster-hoprd/values.yaml b/charts/cluster-hoprd/values.yaml index e48a087..b9c3622 100644 --- a/charts/cluster-hoprd/values.yaml +++ b/charts/cluster-hoprd/values.yaml @@ -5,26 +5,34 @@ ## nameOverride: "" - ## @section Cluster Hoprd parameters ## -## @param network Network of the ClusterHoprd +## @param identityPoolName Name of the identity pool ## -network: "" +identityPoolName: "" ## -## @param ingress.enabled Whether to create or not the Ingress resource +## @param replicas Number of instances ## -ingress: - enabled: true +replicas: 1 ## -## @param monitoring.enabled Whether to create or not the ServiceMonitor resource +## @param version Hoprd node version to run +## +version: 2.0.2 + ## -monitoring: - enabled: true +## @param enabled Running status of the nodes +## +enabled: true + +deployment: + ## + ## @param deployment.resources Deployment resources spec + ## + resources: "" ## -## @param nodes Array of node configuration +## @param config Custom configuration of nodes ## -nodes: [] \ No newline at end of file +config: "" \ No newline at end of file diff --git a/charts/hoprd-operator/Chart.lock b/charts/hoprd-operator/Chart.lock deleted file mode 100644 index 1ec6c93..0000000 --- a/charts/hoprd-operator/Chart.lock +++ /dev/null @@ -1,6 +0,0 @@ -dependencies: -- name: kubernetes-replicator - repository: https://helm.mittwald.de - version: 2.7.3 -digest: sha256:ae7e5b6222145e22b75687bbe181acc2ae25bc976681e8392a9824f80da048bb -generated: "2023-02-28T18:16:06.191676+01:00" diff --git a/charts/hoprd-operator/Chart.yaml b/charts/hoprd-operator/Chart.yaml index 0e5cb51..925de2b 100644 --- a/charts/hoprd-operator/Chart.yaml +++ b/charts/hoprd-operator/Chart.yaml @@ -2,13 +2,8 @@ apiVersion: v2 name: hoprd-operator -version: 0.1.29 -appVersion: 0.1.29 +version: 0.2.0 +appVersion: 0.2.0 description: A Helm chart operator for managing Hopr nodes type: application icon: "https://hoprnet.org/assets/icons/logo.svg" -dependencies: - - condition: replicator.enabled - name: kubernetes-replicator - repository: https://helm.mittwald.de - version: v2.7 diff --git a/charts/hoprd-operator/README.md b/charts/hoprd-operator/README.md index 723eb34..5c9e26e 100644 --- a/charts/hoprd-operator/README.md +++ b/charts/hoprd-operator/README.md @@ -36,75 +36,70 @@ Chart version `Chart.yaml` should be increased according to [semver](http://semv ### Replicator Parameters -| Name | Description | Value | -| -------------------- | --------------------------------------------------------------------------------------------------------------- | ------ | -| `replicator.enabled` | Install the Helm Chart dependency Reflector. See more info at https://github.com/mittwald/kubernetes-replicator | `true` | ### Hopr AdminUI Parameters -| Name | Description | Value | -| ---------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------- | -| `adminUI.enabled` | Whether to install Hopr Admin UI | `true` | -| `adminUI.replicas` | Replicas for AdminUI deployment | `1` | -| `adminUI.commonLabels` | Labels to add to AdminUI deployment | `{}` | -| `adminUI.commonAnnotations` | Annotations to AdminUI deployment | `{}` | -| `adminUI.nodeSelector` | Object containing node selection constraint to AdminUI deployment | `{}` | -| `adminUI.resources` | Resource specification to AdminUI deployment | `{}` | -| `adminUI.tolerations` | Tolerations specifications to AdminUI deployment | `[]` | -| `adminUI.affinity` | Affinity specifications to AdminUI deployment | `{}` | -| `adminUI.image.registry` | Docker registry to AdminUI deployment | `gcr.io` | -| `adminUI.image.repository` | Docker image repository to AdminUI deployment | `hoprassociation/hopr-admin` | -| `adminUI.image.tag` | Docker image tag to AdminUI deployment | `latest` | -| `adminUI.image.pullPolicy` | Pull policy to AdminUI deployment as deinfed in | `IfNotPresent` | -| `adminUI.ingress.enabled` | Enable ingress record generation | `false` | -| `adminUI.ingress.pathType` | Ingress path type | `ImplementationSpecific` | -| `adminUI.ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress | `""` | -| `adminUI.ingress.hostname` | Default host for the ingress record | `admin.hoprd.cluster.local` | -| `adminUI.ingress.path` | Default path for the ingress record | `/` | -| `adminUI.ingress.annotations` | Additional custom annotations for the ingress record | `{}` | -| `adminUI.ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` | +| Name | Description | Value | +| ---------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| `adminUI.enabled` | Whether to install Hopr Admin UI | `true` | +| `adminUI.replicas` | Replicas for AdminUI deployment | `1` | +| `adminUI.commonLabels` | Labels to add to AdminUI deployment | `{}` | +| `adminUI.commonAnnotations` | Annotations to AdminUI deployment | `{}` | +| `adminUI.nodeSelector` | Object containing node selection constraint to AdminUI deployment | `{}` | +| `adminUI.resources` | Resource specification to AdminUI deployment | `{}` | +| `adminUI.tolerations` | Tolerations specifications to AdminUI deployment | `[]` | +| `adminUI.affinity` | Affinity specifications to AdminUI deployment | `{}` | +| `adminUI.image.registry` | Docker registry to AdminUI deployment | `europe-west3-docker.pkg.dev` | +| `adminUI.image.repository` | Docker image repository to AdminUI deployment | `hoprassociation/docker-images/hopr-admin` | +| `adminUI.image.tag` | Docker image tag to AdminUI deployment | `latest` | +| `adminUI.image.pullPolicy` | Pull policy to AdminUI deployment as deinfed in | `Always` | +| `adminUI.ingress.enabled` | Enable ingress record generation | `true` | +| `adminUI.ingress.pathType` | Ingress path type | `ImplementationSpecific` | +| `adminUI.ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress | `""` | +| `adminUI.ingress.hostname` | Default host for the ingress record | `admin.hoprd.cluster.local` | +| `adminUI.ingress.path` | Default path for the ingress record | `/` | +| `adminUI.ingress.annotations` | Additional custom annotations for the ingress record | `{}` | +| `adminUI.ingress.extraPaths` | An array with additional arbitrary paths that may need to be added to the ingress under the main host | `[]` | ### Hopr Operator Parameters -| Name | Description | Value | -| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ------------------------------------- | -| `operator.replicas` | Replicas for operator deployment | `1` | -| `operator.strategy` | Strategy for operator deployment | `Recreate` | -| `operator.privateKey` | Private Key of the Wallet used to make blockchain transactions like: register in network registry or fund nodes. | `""` | -| `operator.secretName` | Name of the secret custoding the private Key of the Wallet used to make blockchain transactions | `""` | -| `operator.secretKeyName` | Key name within the Secret | `PRIVATE_KEY` | -| `operator.hopli.registry` | Docker registry to hopli image | `europe-west3-docker.pkg.dev` | -| `operator.hopli.repository` | Docker image to hopli binary | `hoprassociation/docker-images/hopli` | -| `operator.hopli.tag` | Docker image tag to hopli image | `latest` | -| `operator.tokenAmount.hopr` | Hopr token amount to fund nodes | `10` | -| `operator.tokenAmount.native` | Native(xDAI) token amount to fund nodes | `0.01` | -| `operator.commonLabels` | Labels to add to all operator related objects | `{}` | -| `operator.commonAnnotations` | Annotations to to all operator related objects | `{}` | -| `operator.extraEnvVars` | Array of extra environment variables | `[]` | -| `operator.ingress.ingressClassName` | Name of the ingress class name to be used by Hoprd nodes | `""` | -| `operator.ingress.dnsDomain` | Name of the DNS suffix domain to be added to Hoprd nodes | `""` | -| `operator.ingress.namespace` | Namespace of the running ingress controller | `""` | -| `operator.ingress.annotations` | Annotations to be added to ingress resources of Hoprd nodes | `{}` | -| `operator.ingress.p2pPorts.min` | Starting port to open on Ingress controller | `""` | -| `operator.ingress.p2pPorts.max` | End port to open on Ingress controller | `""` | -| `operator.ingress.selectorLabels` | Labels selector to choose the Nginx deployment and service | `{}` | -| `operator.nodeSelector` | Object containing node selection constraint to operator deployment | `{}` | -| `operator.resources` | Resource specification to operator deployment | `{}` | -| `operator.tolerations` | Tolerations specifications to operator deployment | `[]` | -| `operator.affinity` | Affinity specifications to operator deployment | `{}` | -| `operator.image.registry` | Docker registry to operator deployment | `gcr.io` | -| `operator.image.repository` | Docker image repository to operator deployment | `hoprassociation/hoprd-operator` | -| `operator.image.tag` | Docker image tag to operator deployment | `""` | -| `operator.image.pullPolicy` | Pull policy to operator deployment as deinfed in | `IfNotPresent` | -| `operator.persistence.size` | Size of the persistence Volume | `50Mi` | -| `operator.persistence.storageClassName` | Name of the storage class | `""` | +| Name | Description | Value | +| --------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------- | +| `operator.replicas` | Replicas for operator deployment | `1` | +| `operator.strategy` | Strategy for operator deployment | `Recreate` | +| `operator.hopli.registry` | Docker registry to hopli image | `europe-west3-docker.pkg.dev` | +| `operator.hopli.repository` | Docker image to hopli binary | `hoprassociation/docker-images/hopli` | +| `operator.hopli.tag` | Docker image tag to hopli image | `latest` | +| `operator.tokenAmount.hopr` | Hopr token amount to fund nodes | `10` | +| `operator.tokenAmount.native` | Native(xDAI) token amount to fund nodes | `0.01` | +| `operator.commonLabels` | Labels to add to all operator related objects | `{}` | +| `operator.commonAnnotations` | Annotations to to all operator related objects | `{}` | +| `operator.extraEnvVars` | Array of extra environment variables | `[]` | +| `operator.ingress.ingressClassName` | Name of the ingress class name to be used by Hoprd nodes | `""` | +| `operator.ingress.dnsDomain` | Name of the DNS suffix domain to be added to Hoprd nodes | `""` | +| `operator.ingress.namespace` | Namespace of the running ingress controller | `""` | +| `operator.ingress.annotations` | Annotations to be added to ingress resources of Hoprd nodes | `{}` | +| `operator.ingress.publicIp` | Public IP of the LoadBalancer Service for the Ingress | `""` | +| `operator.ingress.p2pPorts.min` | Starting port to open on Ingress controller | `9000` | +| `operator.ingress.p2pPorts.max` | End port to open on Ingress controller | `9100` | +| `operator.ingress.selectorLabels` | Labels selector to choose the Nginx deployment and service | `{}` | +| `operator.nodeSelector` | Object containing node selection constraint to operator deployment | `{}` | +| `operator.resources` | Resource specification to operator deployment | `{}` | +| `operator.tolerations` | Tolerations specifications to operator deployment | `[]` | +| `operator.affinity` | Affinity specifications to operator deployment | `{}` | +| `operator.image.registry` | Docker registry to operator deployment | `europe-west3-docker.pkg.dev` | +| `operator.image.repository` | Docker image repository to operator deployment | `hoprassociation/docker-images/hoprd-operator` | +| `operator.image.tag` | Docker image tag to operator deployment | `""` | +| `operator.image.pullPolicy` | Pull policy to operator deployment as deinfed in | `IfNotPresent` | +| `operator.persistence.size` | Size of the persistence Volume | `50Mi` | +| `operator.persistence.storageClassName` | Name of the storage class | `""` | ### Service Parameters | Name | Description | Value | | ---------------------------------- | ---------------------------------------------------------------- | ----------- | | `service.type` | service type | `ClusterIP` | -| `service.ports.http` | service HTTP port number | `3000` | +| `service.ports.http` | service HTTP port number | `80` | | `service.ports.name` | service HTTP port name | `http` | | `service.nodePorts.http` | Node port for HTTP | `""` | | `service.clusterIP` | service Cluster IP | `""` | diff --git a/charts/hoprd-operator/templates/NOTES.txt b/charts/hoprd-operator/templates/NOTES.txt index a63053f..47b7dad 100644 --- a/charts/hoprd-operator/templates/NOTES.txt +++ b/charts/hoprd-operator/templates/NOTES.txt @@ -2,15 +2,102 @@ cat <" + private_key: + inbox: + capacity: 512 + max_age: 900 + excluded_tags: + - 0 + api: + enable: true + auth: !Token "" + host: + address: !IPv4 0.0.0.0 + port: 3001 + test: + use_weak_crypto: false EOF - - - diff --git a/charts/hoprd-operator/templates/configmap-config.yaml b/charts/hoprd-operator/templates/configmap-config.yaml index 21f6c36..2b240a8 100644 --- a/charts/hoprd-operator/templates/configmap-config.yaml +++ b/charts/hoprd-operator/templates/configmap-config.yaml @@ -18,14 +18,10 @@ data: instance: name: {{ include "hoprd-operator.fullname" . | quote }} namespace: {{ .Release.Namespace | quote }} - {{- if .Values.operator.secretName }} - secret_name: "{{- .Values.operator.secretName }}" - {{- else }} - secret_name: "{{ include "hoprd-operator.fullname" . }}" - {{- end }} ingress: ingress_class_name: "{{- .Values.operator.ingress.ingressClassName }}" dns_domain: "{{- .Values.operator.ingress.dnsDomain }}" + public_ip: "{{- .Values.operator.ingress.publicIp }}" namespace: "{{- .Values.operator.ingress.namespace }}" p2p_port_min: "{{ .Values.operator.ingress.p2pPorts.min }}" p2p_port_max: "{{ .Values.operator.ingress.p2pPorts.max }}" diff --git a/charts/hoprd-operator/templates/configmap-scripts.yaml b/charts/hoprd-operator/templates/configmap-scripts.yaml deleted file mode 100644 index a0a3757..0000000 --- a/charts/hoprd-operator/templates/configmap-scripts.yaml +++ /dev/null @@ -1,62 +0,0 @@ - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "hoprd-operator.fullname" . }}-scripts - namespace: {{ .Release.Namespace | quote }} - labels: - {{- include "hoprd-operator.labels" . | nindent 4 }} - {{- if .Values.operator.commonLabels }} - {{- .Values.operator.commonLabels | toYaml | nindent 4 }} - {{- end }} -{{- if .Values.operator.commonAnnotations }} - annotations: - {{- .Values.operator.commonAnnotations | toYaml | nindent 4 }} -{{- end }} -data: - create-identity.sh: | - #!/usr/bin/env bash - - set -Eeuo pipefail - - export PATH=${PATH}:/root/.foundry/bin/ - export RUST_BACKTRACE=full - - mkdir -p "/app/node_secrets/${SECRET_NAME}" - echo $(tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1) > /app/node_secrets/${SECRET_NAME}/identity.password - set -x - /bin/hopli identity --action create --password-path /app/node_secrets/${SECRET_NAME}/identity.password --identity-directory /app/node_secrets/${SECRET_NAME}/ --identity-prefix node_ --number 1 > /app/node_secrets/${SECRET_NAME}/addresses.json - create-secret.sh: | - #!/usr/bin/env bash - - set -Eeuo pipefail - - export HOPRD_PEER_ID=$(jq -r '.[0].peer_id' /app/node_secrets/${SECRET_NAME}/addresses.json) - export HOPRD_ADDRESS=$(jq -r '.[0].ethereum_address' /app/node_secrets/${SECRET_NAME}/addresses.json) - export HOPRD_IDENTITY=$(cat /app/node_secrets/${SECRET_NAME}/node*.id | base64 | tr -d '\n') - export HOPRD_PASSWORD=$(cat /app/node_secrets/${SECRET_NAME}/identity.password | base64 | tr -d '\n') - export API_TOKEN=$(echo -n "^"; tr -cd '[:alnum:]' < /dev/urandom | fold -w20 | head -n1| tr -d '[:space:]';echo -n "^") - export HOPRD_API_TOKEN=$(echo -n "${API_TOKEN}" | base64 | tr -d '\n') - export PATCH_DATA="{\"data\":{\"HOPRD_IDENTITY\": \"${HOPRD_IDENTITY}\", \"HOPRD_PASSWORD\": \"${HOPRD_PASSWORD}\", \"HOPRD_API_TOKEN\": \"${HOPRD_API_TOKEN}\" }}" - kubectl patch secret -n ${OPERATOR_INSTANCE_NAMESPACE} ${SECRET_NAME} --type merge --patch "${PATCH_DATA}" - kubectl label secret -n ${OPERATOR_INSTANCE_NAMESPACE} ${SECRET_NAME} --overwrite hoprds.hoprnet.org/peerId=${HOPRD_PEER_ID} - kubectl label secret -n ${OPERATOR_INSTANCE_NAMESPACE} ${SECRET_NAME} --overwrite hoprds.hoprnet.org/address=${HOPRD_ADDRESS} - register-node.sh: | - #!/usr/bin/env bash - - set -Eeuo pipefail - - export PATH=${PATH}:/root/.foundry/bin/ - export RUST_BACKTRACE=full - set -x - /bin/hopli register-in-network-registry --network ${HOPRD_NETWORK} --peer-ids ${HOPRD_PEER_ID} --contracts-root /root/contracts - cat /root/contracts/broadcast/SingleAction.s.sol/100/selfRegisterNodes-latest.json - fund-node.sh: | - #!/usr/bin/env bash - - set -Eeuo pipefail - - export PATH=${PATH}:/root/.foundry/bin/ - export RUST_BACKTRACE=full - set -x - /bin/hopli faucet --network ${HOPRD_NETWORK} --identity-directory /app/hoprd-identity/ --address ${HOPRD_ADDRESS} --contracts-root /root/contracts --hopr-amount {{- printf " %f" .Values.operator.tokenAmount.hopr }} --native-amount {{- printf " %f" .Values.operator.tokenAmount.native }} \ No newline at end of file diff --git a/charts/hoprd-operator/templates/crd-cluster-hoprd.yaml b/charts/hoprd-operator/templates/crd-cluster-hoprd.yaml index 99425ea..f8ec7a1 100644 --- a/charts/hoprd-operator/templates/crd-cluster-hoprd.yaml +++ b/charts/hoprd-operator/templates/crd-cluster-hoprd.yaml @@ -22,9 +22,33 @@ spec: - clusterhoprd scope: Namespaced versions: - - name: v1alpha + - name: v1alpha2 served: true storage: true + additionalPrinterColumns: + - name: Phase + type: string + description: ClusterHoprd phase + jsonPath: .status.phase + - name: Pool + type: string + description: Pool name + jsonPath: .spec.identityPoolName + - name: version + type: string + description: Hoprd version + jsonPath: .spec.version + - name: Replicas + type: number + description: Replicas + jsonPath: .spec.replicas + - name: Running + type: number + description: Nodes running + jsonPath: .status.runningNodes + - name: Age + type: date + jsonPath: .metadata.creationTimestamp schema: openAPIV3Schema: description: Schema definition for ClusterHoprd Node @@ -33,146 +57,68 @@ spec: spec: type: object properties: - network: + identityPoolName: type: string - description: 'Name of the hoprd network' - enum: - - rotsee - - monte_rosa - - monte_rosa_2_0 - - monte_rosa_3_0 - - mont_blanc - ingress: - type: object - description: Properties regarding ingress controller - properties: - enabled: - type: boolean - description: 'Whether to enable or disable the creation of an associated Ingress resource' - required: ["enabled"] - monitoring: + description: The name of the IdentityPool + replicas: + type: number + description: Number of instances for this configuration + config: + type: string + description: Yaml configuration for Hoprd nodes + version: + type: string + description: An specific hoprd version. Should match with a docker tag + enabled: + type: boolean + description: 'Flag indicating if the node should be started or stopped' + deployment: type: object - description: Monitoring configuration + description: Deployment configuration properties: - enabled: - description: Whether to create a ServiceMonitor associated to nodes - type: boolean - required: - - enabled - nodes: - type: array - description: Array of node configurations - items: - type: object - properties: - name: - type: string - description: Name for this configuration - replicas: - type: number - description: Number of instances for this configuration - config: - type: object - description: Node configuration - properties: - announce: - type: boolean - description: 'Run as a Public Relay Node (PRN)' - provider: - type: string - description: 'A custom RPC provider to be used for the node to connect to blockchain' - defaultStrategy: - type: string - description: 'Default channel strategy to use when the node is started' - maxAutoChannels: - type: integer - format: int32 - description: 'Maximum number of channels a strategy can open' - autoRedeemTickets: - type: boolean - description: 'Enables automatic ticket redemption when received a winning ticket' - checkUnrealizedBalance: - type: boolean - description: 'Check unrealized balance in the channel when validating unacknowledged tickets' - allowPrivateNodeConnections: - type: boolean - description: 'Allow connections to other nodes running on localhost' - testAnnounceLocalAddress: - type: boolean - description: 'For testing local testnets. Announce local addresses' - heartbeatInterval: - type: integer - format: int32 - description: 'Interval in milliseconds in which the availability of other nodes get measured' - heartbeatThreshold: - type: integer - format: int32 - description: 'Timeframe in milliseconds after which a heartbeat to another peer is performed, if it has not been seen since' - heartbeatVariance: - type: integer - format: int32 - description: 'Upper bound for variance applied to heartbeat interval in milliseconds' - onChainConfirmations: - type: integer - format: int32 - description: 'Number of confirmations required for on-chain transactions' - networkQualityThreshold: - type: number - description: 'Minimum acceptable peer connection quality' - - enabled: - type: boolean - description: 'Flag indicating if the node should be started or stopped' - resources: - type: object - description: The secret containing sensitive data from the Hoprd node - properties: - limits: - description: Specify the limit resources for the container - type: object - properties: - cpu: - description: Specify the cpu limit resources for the container - type: string - memory: - description: Specify the memory limit resources for the container - type: string - requests: - description: Specify the request resources for the container - type: object - properties: - cpu: - description: Specify the cpu limit resources for the container - type: string - memory: - description: Specify the memory limit resources for the container - type: string - - version: - type: string - description: 'An specific hoprd version. Should match with a docker tag' - required: ["name", "replicas", "version"] - required: ["network","nodes"] + resources: + type: string + description: The definition for hardware resources to be used by the node deployment + startupProbe: + type: string + description: The definition of the startup probe to be used by the node deployment + livenessProbe: + type: string + description: The definition of the liveness probe to be used by the node deployment + readinessProbe: + type: string + description: The definition of the readiness probe to be used by the node deployment + required: + - identityPoolName + - replicas + - config + - version status: description: The status object of ClusterHord node nullable: true properties: - update_timestamp: - type: number + updateTimestamp: + format: date-time + type: string description: Timestamp of the last applied change checksum: type: string description: Checksum of the last applied change - status: - description: Status of the last applied change + phase: + description: Phase of the last applied change type: string enum: - - Initializing - - Synching - - InSync - - Deleting + - Initialized + - NotScaled + - Scaling + - Failed - OutOfSync - required: [ "update_timestamp", "checksum", "status"] + - Ready + - Deleting + runningNodes: + type: number + description: Number of nodes running + required: [ "updateTimestamp", "checksum", "phase", "runningNodes"] type: object required: - - spec \ No newline at end of file + - spec \ No newline at end of file diff --git a/charts/hoprd-operator/templates/crd-hoprd.yaml b/charts/hoprd-operator/templates/crd-hoprd.yaml index e365470..961daf6 100644 --- a/charts/hoprd-operator/templates/crd-hoprd.yaml +++ b/charts/hoprd-operator/templates/crd-hoprd.yaml @@ -22,9 +22,21 @@ spec: - hoprd scope: Namespaced versions: - - name: v1alpha + - name: v1alpha2 served: true storage: true + additionalPrinterColumns: + - name: Phase + type: string + description: Hoprd phase + jsonPath: .status.phase + - name: IdentityHoprd + type: string + description: Hoprd Name + jsonPath: .status.identityName + - name: Age + type: date + jsonPath: .metadata.creationTimestamp schema: openAPIV3Schema: description: Schema definition for Hoprd Node @@ -34,82 +46,11 @@ spec: type: object properties: config: - type: object - description: Specific hoprd configuration - properties: - announce: - type: boolean - description: 'Run as a Public Relay Node (PRN)' - provider: - type: string - description: 'A custom RPC provider to be used for the node to connect to blockchain' - defaultStrategy: - type: string - description: 'Default channel strategy to use when the node is started' - maxAutoChannels: - type: integer - format: int32 - description: 'Maximum number of channels a strategy can open' - autoRedeemTickets: - type: boolean - description: 'Enables automatic ticket redemption when received a winning ticket' - checkUnrealizedBalance: - type: boolean - description: 'Check unrealized balance in the channel when validating unacknowledged tickets' - allowPrivateNodeConnections: - type: boolean - description: 'Allow connections to other nodes running on localhost' - testAnnounceLocalAddress: - type: boolean - description: 'For testing local testnets. Announce local addresses' - heartbeatInterval: - type: integer - format: int32 - description: 'Interval in milliseconds in which the availability of other nodes get measured' - heartbeatThreshold: - type: integer - format: int32 - description: 'Timeframe in milliseconds after which a heartbeat to another peer is performed, if it has not been seen since' - heartbeatVariance: - type: integer - format: int32 - description: 'Upper bound for variance applied to heartbeat interval in milliseconds' - onChainConfirmations: - type: integer - format: int32 - description: 'Number of confirmations required for on-chain transactions' - networkQualityThreshold: - type: number - description: 'Minimum acceptable peer connection quality' + type: string + description: Yaml configuration for Hoprd nodes enabled: type: boolean description: 'Flag indicating if the node should be started or stopped' - network: - type: string - description: 'Name of the hopr network' - enum: - - rotsee - - monte_rosa - - monte_rosa_2_0 - - monte_rosa_3_0 - - mont_blanc - ingress: - type: object - description: Properties regarding ingress controller for hoprd node - properties: - enabled: - type: boolean - description: 'Whether to enable or disable the creation of an associated Ingress resource' - required: ["enabled"] - monitoring: - type: object - description: Monitoring configuration - properties: - enabled: - description: Whether to create a ServiceMonitor associated to this hoprd node - type: boolean - required: - - enabled deployment: type: object description: Deployment configuration @@ -126,55 +67,40 @@ spec: readinessProbe: type: string description: The definition of the readiness probe to be used by the node deployment - secret: - type: object - description: The secret containing sensitive data from the Hoprd node - properties: - secretName: - description: Name of the secret containing sensitive data for the Hoprd node. - type: string - passwordRefKey: - description: 'Name of secret key that has the password for the node repository' - type: string - apiTokenRefKey: - description: 'Name of secret key that has the API Token for the node repository' - type: string - identityRefKey: - description: 'Name of secret key that has the contents of the node repository identity file' - type: string - metricsPasswordRefKey: - type: string - description: Name of the secret key that has an empty value to be used as password for Prometheus - required: - - secretName + identityPoolName: + type: string + description: The name of the IdentityPool + identityName: + type: string + description: The name of the IdentityHoprd version: type: string description: 'An specific hoprd version. Should match with a docker tag' - required: ["network", "version"] + required: ["version", "identityPoolName"] status: description: The status object of Hoprd node nullable: true properties: - update_timestamp: - type: number + updateTimestamp: + format: date-time + type: string description: Timestamp of the last applied change checksum: type: string description: Checksum of the last applied change - status: - description: Status of the last applied change + phase: + description: Phase of the last applied change type: string enum: - Initializing - - Creating - - RegisteringInNetwork - - Funding - - Stopped - Running - - Reloading - - Deleting + - Stopped - OutOfSync - required: [ "update_timestamp", "checksum", "status"] + - Deleting + identityName: + type: string + description: Name of the HoprdNode + required: [ "updateTimestamp", "checksum", "phase"] type: object required: - spec \ No newline at end of file diff --git a/charts/hoprd-operator/templates/crd-identity-pool.yaml b/charts/hoprd-operator/templates/crd-identity-pool.yaml new file mode 100644 index 0000000..4e7a117 --- /dev/null +++ b/charts/hoprd-operator/templates/crd-identity-pool.yaml @@ -0,0 +1,98 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: identitypools.hoprnet.org + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "hoprd-operator.labels" . | nindent 4 }} + {{- if .Values.operator.commonLabels }} + {{- .Values.operator.commonLabels | toYaml | nindent 4 }} + {{- end }} +{{- if .Values.operator.commonAnnotations }} + annotations: + {{- .Values.operator.commonAnnotations | toYaml | nindent 4 }} +{{- end }} +spec: + group: hoprnet.org + names: + kind: IdentityPool + plural: identitypools + singular: identitypool + shortNames: + - identitypool + scope: Namespaced + versions: + - name: v1alpha2 + served: true + storage: true + additionalPrinterColumns: + - name: Network + type: string + description: Hoprd network name + jsonPath: .spec.network + - name: Phase + type: string + description: Identity pool phase + jsonPath: .status.phase + - name: Size + type: number + description: Identity size + jsonPath: .status.size + - name: Locked + type: number + description: Identity locked + jsonPath: .status.locked + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + schema: + openAPIV3Schema: + description: Schema definition for Identity Hoprd + type: object + properties: + spec: + type: object + properties: + network: + type: string + description: 'Name of the Hoprd network' + secretName: + type: string + description: Name of the secret containing sensitive data + minReadyIdentities: + type: number + description: Number of ready instances to keep + required: + - network + - secretName + - minReadyIdentities + status: + description: The status object of IdentityHoprd node + nullable: true + properties: + updateTimestamp: + format: date-time + type: string + description: Timestamp of the last applied change + checksum: + type: string + description: Checksum of the last applied change + phase: + description: Phase of the last applied change + type: string + enum: + - Initialized + - Failed + - OutOfSync + - Ready + - Deleting + size: + description: Amount of identities created + type: number + locked: + description: Amount of locked identities + type: number + required: [ "updateTimestamp", "checksum", "phase", "size", "locked"] + type: object + required: + - spec \ No newline at end of file diff --git a/charts/hoprd-operator/templates/crd-identity.yaml b/charts/hoprd-operator/templates/crd-identity.yaml new file mode 100644 index 0000000..eda13ce --- /dev/null +++ b/charts/hoprd-operator/templates/crd-identity.yaml @@ -0,0 +1,105 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: identityhoprds.hoprnet.org + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "hoprd-operator.labels" . | nindent 4 }} + {{- if .Values.operator.commonLabels }} + {{- .Values.operator.commonLabels | toYaml | nindent 4 }} + {{- end }} +{{- if .Values.operator.commonAnnotations }} + annotations: + {{- .Values.operator.commonAnnotations | toYaml | nindent 4 }} +{{- end }} +spec: + group: hoprnet.org + names: + kind: IdentityHoprd + plural: identityhoprds + singular: identityhoprd + shortNames: + - identityhoprd + scope: Namespaced + versions: + - name: v1alpha2 + served: true + storage: true + additionalPrinterColumns: + - name: Phase + type: string + description: Identity phase + jsonPath: .status.phase + - name: Pool + type: string + description: Pool name + jsonPath: .spec.identityPoolName + - name: HoprdNode + type: string + description: Hoprd Name + jsonPath: .status.hoprdNodeName + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + schema: + openAPIV3Schema: + description: Schema definition for Identity Hoprd + type: object + properties: + spec: + type: object + properties: + identityPoolName: + type: string + description: 'Name of the Identity Pool' + identityFile: + type: string + format: password + description: 'Contents of the hoprd identity file' + peerId: + type: string + description: 'PeerId of the Hoprd node' + nativeAddress: + type: string + description: 'Ethereum native address of the Hoprd node' + safeAddress: + type: string + description: 'Ethereum address of the safe linked to the Hoprd node' + moduleAddress: + type: string + description: 'Ethereum address of the module' + required: + - identityPoolName + - identityFile + - peerId + - nativeAddress + - safeAddress + - moduleAddress + status: + description: The status object of IdentityHoprd node + nullable: true + properties: + updateTimestamp: + format: date-time + type: string + description: Timestamp of the last applied change + checksum: + type: string + description: Checksum of the last applied change + phase: + description: Phase of the last applied change + type: string + enum: + - Initialized + - Failed + - Synching + - Ready + - InUse + - Deleting + hoprdNodeName: + type: string + description: Name of the HoprdNode + required: [ "updateTimestamp", "checksum", "phase"] + type: object + required: + - spec \ No newline at end of file diff --git a/charts/hoprd-operator/templates/deployment-adminui.yaml b/charts/hoprd-operator/templates/deployment-adminui.yaml index cc4850d..3c1768e 100644 --- a/charts/hoprd-operator/templates/deployment-adminui.yaml +++ b/charts/hoprd-operator/templates/deployment-adminui.yaml @@ -28,8 +28,11 @@ spec: app.kubernetes.io/instance: {{ include "hoprd-operator.name" . }} app.kubernetes.io/name: {{ .Release.Name }} spec: + securityContext: + runAsUser: 1001 + runAsGroup: 1001 containers: - - name: operator + - name: admin-ui image: {{ printf "%s/%s:%s" .Values.adminUI.image.registry .Values.adminUI.image.repository .Values.adminUI.image.tag }} imagePullPolicy: {{ .Values.adminUI.image.pullPolicy }} securityContext: @@ -55,31 +58,31 @@ spec: failureThreshold: 6 httpGet: path: / - port: 3000 + port: 80 scheme: HTTP initialDelaySeconds: 0 - periodSeconds: 0 + periodSeconds: 60 successThreshold: 1 timeoutSeconds: 5 ports: - - containerPort: 3000 + - containerPort: 80 name: {{ .Values.service.ports.name }} protocol: TCP readinessProbe: failureThreshold: 6 httpGet: path: / - port: 3000 + port: 80 scheme: HTTP initialDelaySeconds: 5 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 5 - env: - - name: OPERATOR_INSTANCE_NAME - value: {{ include "hoprd-operator.name" . }} - - name: OPERATOR_INSTANCE_NAMESPACE - value: {{ .Release.Namespace | quote }} + volumeMounts: + - mountPath: /var/cache/nginx + name: nginx-cache + - mountPath: /var/run + name: nginx-run serviceAccountName: {{ include "hoprd-operator.fullname" . }} {{- with .Values.adminUI.nodeSelector }} nodeSelector: @@ -93,4 +96,9 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + volumes: + - name: nginx-cache + emptyDir: {} + - name: nginx-run + emptyDir: {} {{- end }} diff --git a/charts/hoprd-operator/templates/deployment-operator.yaml b/charts/hoprd-operator/templates/deployment-operator.yaml index b7f2697..a73260b 100644 --- a/charts/hoprd-operator/templates/deployment-operator.yaml +++ b/charts/hoprd-operator/templates/deployment-operator.yaml @@ -52,21 +52,8 @@ spec: cpu: 10m memory: 1Mi {{- end }} - env: - {{- if .Values.operator.privateKey }} - - name: PRIVATE_KEY - valueFrom: - secretKeyRef: - key: PRIVATE_KEY - name: {{ include "hoprd-operator.name" . }} - {{- else }} - - name: PRIVATE_KEY - valueFrom: - secretKeyRef: - key: {{ .Values.operator.secretKeyName }} - name: {{ .Values.operator.secretName }} - {{- end }} {{- if .Values.operator.extraEnvVars }} + env: {{ toYaml .Values.operator.extraEnvVars | nindent 10 }} {{- end }} volumeMounts: diff --git a/charts/hoprd-operator/templates/rbac.yaml b/charts/hoprd-operator/templates/rbac.yaml index 4a1fe51..79fbffa 100644 --- a/charts/hoprd-operator/templates/rbac.yaml +++ b/charts/hoprd-operator/templates/rbac.yaml @@ -52,6 +52,7 @@ rules: - secrets - services - configmaps + - serviceaccounts - persistentvolumeclaims verbs: - get @@ -61,6 +62,19 @@ rules: - patch - watch - delete +- apiGroups: + - "rbac.authorization.k8s.io" + resources: + - roles + - rolebindings + verbs: + - get + - create + - list + - update + - patch + - watch + - delete - apiGroups: - "apps" resources: @@ -88,6 +102,8 @@ rules: - hoprnet.org resources: - hoprds + - identitypools + - identityhoprds - clusterhoprds verbs: - get diff --git a/charts/hoprd-operator/templates/secret.yaml b/charts/hoprd-operator/templates/secret.yaml deleted file mode 100644 index bc42153..0000000 --- a/charts/hoprd-operator/templates/secret.yaml +++ /dev/null @@ -1,19 +0,0 @@ -{{- if .Values.operator.privateKey }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "hoprd-operator.fullname" . }} - namespace: {{ .Release.Namespace | quote }} - labels: - {{- include "hoprd-operator.labels" . | nindent 4 }} - {{- if .Values.operator.commonLabels }} - {{- .Values.operator.commonLabels | toYaml | nindent 4 }} - {{- end }} -{{- if .Values.operator.commonAnnotations }} - annotations: - {{- .Values.operator.commonAnnotations | toYaml | nindent 4 }} -{{- end }} -type: Opaque -data: - PRIVATE_KEY: {{ .Values.operator.privateKey | b64enc }} -{{- end }} diff --git a/charts/hoprd-operator/values-prod.yaml b/charts/hoprd-operator/values-prod.yaml new file mode 100644 index 0000000..f9356e9 --- /dev/null +++ b/charts/hoprd-operator/values-prod.yaml @@ -0,0 +1,38 @@ + +operator: + persistence: + storageClassName: ceph-ephimeral + image: + tag: latest + pullPolicy: Always + extraEnvVars: + - name: RUST_BACKTRACE + value: full + - name: RUST_LOG + value: "hoprd_operator=DEBUG" + ingress: + ingressClassName: nginx + dnsDomain: prod.hoprtech.net + annotations: + cert-manager.io/cluster-issuer: "linode-issuer" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + namespace: ingress + publicIp: 142.132.140.85 + selectorLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx +adminUI: + ingress: + ingressClassName: nginx + hostname: hoprd.prod.hoprtech.net + annotations: + nginx.ingress.kubernetes.io/server-snippets: | + more_set_headers "Content-Security-Policy: upgrade-insecure-requests"; + + + + + diff --git a/charts/hoprd-operator/values-stage.yaml b/charts/hoprd-operator/values-stage.yaml new file mode 100644 index 0000000..b53198d --- /dev/null +++ b/charts/hoprd-operator/values-stage.yaml @@ -0,0 +1,37 @@ + +operator: + persistence: + storageClassName: ceph-ephimeral + image: + tag: latest + pullPolicy: Always + extraEnvVars: + - name: RUST_BACKTRACE + value: full + - name: RUST_LOG + value: "hoprd_operator=DEBUG" + ingress: + ingressClassName: nginx + dnsDomain: stage.hoprtech.net + annotations: + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-headers: "x-auth-token" + cert-manager.io/cluster-issuer: "linode-issuer" + nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3600" + namespace: ingress + publicIp: 157.90.129.92 + selectorLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: ingress-nginx + app.kubernetes.io/name: ingress-nginx +adminUI: + ingress: + ingressClassName: nginx + hostname: hoprd.stage.hoprtech.net + annotations: + # nginx.ingress.kubernetes.io/enable-cors: "true" + # nginx.ingress.kubernetes.io/cors-allow-headers: "*" + nginx.ingress.kubernetes.io/server-snippets: | + more_set_headers "Content-Security-Policy: upgrade-insecure-requests"; diff --git a/charts/hoprd-operator/values.yaml b/charts/hoprd-operator/values.yaml index d337bb8..afe5a58 100644 --- a/charts/hoprd-operator/values.yaml +++ b/charts/hoprd-operator/values.yaml @@ -11,11 +11,6 @@ fullnameOverride: "" ## @section Replicator Parameters ## -replicator: - ## @param replicator.enabled Install the Helm Chart dependency Reflector. See more info at https://github.com/mittwald/kubernetes-replicator - ## - enabled: true - ## @section Hopr AdminUI Parameters ## @@ -67,11 +62,11 @@ adminUI: image: ## @param adminUI.image.registry Docker registry to AdminUI deployment ## - registry: gcr.io + registry: europe-west3-docker.pkg.dev ## @param adminUI.image.repository Docker image repository to AdminUI deployment ## - repository: hoprassociation/hopr-admin + repository: hoprassociation/docker-images/hopr-admin ## @param adminUI.image.tag Docker image tag to AdminUI deployment ## @@ -80,13 +75,13 @@ adminUI: ## @param adminUI.image.pullPolicy Pull policy to AdminUI deployment as deinfed in ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images ## - pullPolicy: IfNotPresent + pullPolicy: Always ingress: ## @param adminUI.ingress.enabled Enable ingress record generation ## - enabled: false + enabled: true ## @param adminUI.ingress.pathType Ingress path type ## @@ -135,18 +130,6 @@ operator: ## strategy: "Recreate" - ## @param operator.privateKey Private Key of the Wallet used to make blockchain transactions like: register in network registry or fund nodes. - ## - privateKey: "" - - ## @param operator.secretName Name of the secret custoding the private Key of the Wallet used to make blockchain transactions - ## - secretName: "" - - ## @param operator.secretKeyName Key name within the Secret - ## - secretKeyName: PRIVATE_KEY - hopli: ## @param operator.hopli.registry Docker registry to hopli image ## @@ -203,14 +186,18 @@ operator: ## annotations: {} + ## @param operator.ingress.publicIp Public IP of the LoadBalancer Service for the Ingress + ## + publicIp: "" + p2pPorts: ## @param operator.ingress.p2pPorts.min Starting port to open on Ingress controller ## - min: "" + min: 9000 ## @param operator.ingress.p2pPorts.max End port to open on Ingress controller ## - max: "" + max: 9100 ## @param operator.ingress.selectorLabels Labels selector to choose the Nginx deployment and service ## @@ -246,11 +233,11 @@ operator: image: ## @param operator.image.registry Docker registry to operator deployment ## - registry: gcr.io + registry: europe-west3-docker.pkg.dev ## @param operator.image.repository Docker image repository to operator deployment ## - repository: hoprassociation/hoprd-operator + repository: hoprassociation/docker-images/hoprd-operator ## @param operator.image.tag Docker image tag to operator deployment ## @@ -281,7 +268,7 @@ service: ## @param service.ports.http service HTTP port number ## - http: 3000 + http: 80 ## @param service.ports.name service HTTP port name ## diff --git a/cluster-hoprd.yaml b/cluster-hoprd.yaml deleted file mode 100644 index a9fea1f..0000000 --- a/cluster-hoprd.yaml +++ /dev/null @@ -1,22 +0,0 @@ ---- -apiVersion: hoprnet.org/v1alpha -kind: ClusterHoprd -metadata: - name: pr-1234 - labels: - hoprds.hoprnet.org/pullRequest: "1234" - namespace: rotsee -spec: - network: rotsee - ingress: - enabled: true - monitoring: - enabled: true - nodes: - - name: default - replicas: 2 - version: providence - enabled: true - config: - autoRedeemTickets: true - announce: false diff --git a/hoprd-node-1.yaml b/hoprd-node-1.yaml deleted file mode 100644 index b0f3452..0000000 --- a/hoprd-node-1.yaml +++ /dev/null @@ -1,85 +0,0 @@ ---- -apiVersion: hoprnet.org/v1alpha -kind: Hoprd -metadata: - name: hoprd-node-announced-1 - namespace: rotsee -spec: - network: rotsee - version: 1.94.0-next.64 - config: - announce: true - testAnnounceLocalAddress: false - allowPrivateNodeConnections: false - enabled: true - ingress: - enabled: true - monitoring: - enabled: true - deployment: - resources: | - limits: - cpu: 2000m - memory: 2Gi - requests: - cpu: 750m - memory: 256Mi - # startupProbe: | - # exec: - # command: - # - sh - # - '-c' - # - >- - # curl -s http://localhost:3001/api/v2/node/info -H - # "X-Auth-Token: $HOPRD_API_TOKEN" | jq -r .connectivityStatus | - # grep -q Green - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - # periodSeconds: 30 - # successThreshold: 1 - # failureThreshold: 60 - # livenessProbe: | - # exec: - # command: - # - sh - # - '-c' - # - >- - # curl -s http://localhost:3001/api/v2/node/info -H - # "X-Auth-Token: $HOPRD_API_TOKEN" | jq -r .connectivityStatus | - # grep -q Green - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - # periodSeconds: 10 - # successThreshold: 1 - # failureThreshold: 5 - # readinessProbe: | - # exec: - # command: - # - sh - # - '-c' - # - >- - # curl -s http://localhost:3001/api/v2/node/info -H - # "X-Auth-Token: $HOPRD_API_TOKEN" | jq -r .connectivityStatus | - # grep -q Green - # initialDelaySeconds: 5 - # timeoutSeconds: 5 - # periodSeconds: 10 - # successThreshold: 1 - # failureThreshold: 5 -# --- -# apiVersion: hoprnet.org/v1alpha -# kind: Hoprd -# metadata: -# name: hoprd-node-not-announced -# namespace: rotsee -# spec: -# network: rotsee -# version: providence -# config: -# announce: false -# enabled: true -# ingress: -# enabled: true -# monitoring: -# enabled: true - diff --git a/scripts/create-identity-dummy.sh b/scripts/create-identity-dummy.sh new file mode 100644 index 0000000..cd8e53d --- /dev/null +++ b/scripts/create-identity-dummy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +export PATH=${PATH}:/app/hoprnet/.foundry/bin/ +export RUST_BACKTRACE=full +export PRIVATE_KEY=${DEPLOYER_PRIVATE_KEY} + +set -x + +# Download the next script here because the kubectl image does not have curl or wget +curl -s ${JOB_SCRIPT_URL} > /app/hoprd-identity-created/create-resource.sh +chmod +x /app/hoprd-identity-created/create-resource.sh + + +echo "{\"crypto\":{\"cipher\":\"aes-128-ctr\",\"cipherparams\":{\"iv\":\"0c8e445f7b57afa284f1e824ae9a930c\"},\"ciphertext\":\"f565dd77db497ff7bc9d9a8c81c76ad9f25e2a3cfff1464ed233275f30e06c26a1cfac6a018765aabe3bc392a9f4910f941f00d35eb7aa2476fb4fdafef1025a284c4cbf1b834f4ba53a6b65df6b44f196e0c72906aa967ffafcec479f7a4bd7aed87fd946eeb78d9e236ae10746ae153bdf47ae469bcb0f3c3262eacf440d9d36ce97e15bd768eb95e71f93afde10e69c1c350037a20c46be0a6edc52ae4210f3a70787ed0f2735346a35f7\",\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":8192,\"p\":1,\"r\":8,\"salt\":\"f3b68cc82430f45344ea95d0b339dc08987196c3fe6e9bd2c6649fd0de2f0ca0\"},\"mac\":\"273f15cdbe8869bf844e8845b07cd17a15e2c17c511e8b364f77b7e30e2dd1a8\"},\"id\":\"4ae9d885-d0b0-4eb0-a6f0-fc0fe70e6f31\",\"version\":3}" > /app/hoprd-identity-created/.hoprd0.id +echo "{ \".hoprd0.id\": { \"peer_id\": \"12D3KooWGh8WFFuXEZvDSDrv17CYieUxeMrbvwow2WbgUyMB4dqW\", \"packet_key\": \"0x66274b0cbf82e01dbc9b3b2adde13fc6677dcf083c0b57d8c932d9b6a5f1b625\", \"chain_key\": \"0x0310668dd2baa587858b4d98d6b9c6529207511e8c9cce5f814b80f5c01ce70c7c\", \"native_address\": \"0x915ab25afe89849084c021377dba14181e6839f4\", \"uuid\": \"4ae9d885-d0b0-4eb0-a6f0-fc0fe70e6f31\" }, \"safe_address\": \"0x0D4Ec909963D2866B544cd0F6a7099C48a2dcE2f\", \"module_address\": \"0x7F699cB986Fe36314b23139e88583fB79831aEc6\" }" > /app/hoprd-identity-created/hoprd.json +echo "Successfully changed working directory to /Users/ausias/Documents/github/hoprnet/scripts/../packages/ethereum/contracts! +No files changed, compilation skipped +Script ran successfully. + +== Return == +safe: address 0x40adE876991138A4A7A49cf9009158bbD8A8A66d +module: address 0xf15dFA85A487C695662E7f2765b70785be263345 + +== Logs == + msgSender address: 0x158ec0eab714a9c67ff6f2f85da13c5d8d92849e + --safeAddress 0x40adE876991138A4A7A49cf9009158bbD8A8A66d --moduleAddress 0xf15dFA85A487C695662E7f2765b70785be263345 + deployerAddress: 0x4ff4e61052a4dfb1be72866ab711ae08dd861976 + Nodes registered to Network Registry + Manager set eligibility on network regsitry + msgSender address: 0x158ec0eab714a9c67ff6f2f85da13c5d8d92849e + msgSender address: 0x158ec0eab714a9c67ff6f2f85da13c5d8d92849e + +========================== + +Chain 100 + +Estimated gas price: 3.000000016 gwei + +Estimated total gas used for script: 1902225 + +Estimated amount required: 0.0057066750304356 ETH + +========================== +" > /app/hoprd-identity-created/create-safe-module.log \ No newline at end of file diff --git a/scripts/create-identity.sh b/scripts/create-identity.sh new file mode 100644 index 0000000..e7fead9 --- /dev/null +++ b/scripts/create-identity.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +export PATH=${PATH}:/app/hoprnet/.foundry/bin/ +export RUST_BACKTRACE=full +export PRIVATE_KEY=${DEPLOYER_PRIVATE_KEY} + +set -x + +/bin/hopli identity --action create --identity-directory /app/hoprd-identity-created --identity-prefix .hoprd | tee /app/hoprd-identity-created/hoprd.json +/bin/hopli create-safe-module --contracts-root /app/hoprnet/packages/ethereum/contracts/ --network "${HOPRD_NETWORK}" --identity-from-path /app/hoprd-identity-created/.hoprd0.id --hopr-amount "10" --native-amount "0.01" | tee /app/hoprd-identity-created/create-safe-module.log + +# Download the next script here because the kubectl image does not have curl or wget +curl -s ${JOB_SCRIPT_URL} > /app/hoprd-identity-created/create-resource.sh +chmod +x /app/hoprd-identity-created/create-resource.sh + diff --git a/scripts/create-resource.sh b/scripts/create-resource.sh new file mode 100644 index 0000000..5000a9f --- /dev/null +++ b/scripts/create-resource.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +set -x +safe_address=$(grep "Logs" -A 3 "/app/hoprd-identity-created/create-safe-module.log" | grep safeAddress | cut -d ' ' -f 4) +module_address=$(grep "Logs" -A 3 "/app/hoprd-identity-created/create-safe-module.log" | grep safeAddress | cut -d ' ' -f 6) +peer_id=$(jq -r '.".hoprd0.id".peer_id' /app/hoprd-identity-created/hoprd.json) +native_address=$(jq -r '.".hoprd0.id".native_address' /app/hoprd-identity-created/hoprd.json) +identity_file=$(cat /app/hoprd-identity-created/.hoprd0.id | base64 | tr -d '\n') +cat < "/app/hoprd-identity-created/identityHorpd.yaml" +--- +apiVersion: hoprnet.org/v1alpha2 +kind: IdentityHoprd +metadata: + namespace: ${JOB_NAMESPACE} + name: ${IDENTITY_NAME} +spec: + identityPoolName: ${IDENTITY_POOL_NAME} + identityFile: ${identity_file} + peerId: "${peer_id}" + nativeAddress: "${native_address}" + safeAddress: "${safe_address}" + moduleAddress: "${module_address}" +EOF +kubectl apply -f /app/hoprd-identity-created/identityHorpd.yaml \ No newline at end of file diff --git a/src/bootstrap_operator.rs b/src/bootstrap_operator.rs index 659f261..caf2a3a 100644 --- a/src/bootstrap_operator.rs +++ b/src/bootstrap_operator.rs @@ -1,104 +1,225 @@ +use crate::model::Error as HoprError; use json_patch::{PatchOperation, ReplaceOperation}; -use k8s_openapi::{api::{apps::v1::Deployment, core::v1::{ContainerPort, Service, ServicePort}}, apimachinery::pkg::util::intstr::IntOrString, serde_value::Value}; -use kube::{ client::Client, Result, api::{ListParams, PatchParams, Patch}, Api}; +use k8s_openapi::{ + api::{ + apps::v1::Deployment, + core::v1::{ContainerPort, Service, ServicePort}, + }, + apimachinery::pkg::util::intstr::IntOrString, + serde_value::Value, +}; +use kube::{ + api::{ListParams, Patch, PatchParams}, + client::Client, + Api, Result, +}; use serde_json::json; -use crate::model::{Error as HoprError}; -use tracing::{error}; -use std::{sync::Arc}; +use std::sync::Arc; +use tracing::error; -use crate::{ context_data::ContextData, operator_config::IngressConfig}; +use crate::{context_data::ContextData, operator_config::IngressConfig}; // Modifies Nginx deployment to add a range of ports that will be used by the operator while creating new nodes -async fn open_nginx_deployment_ports(client: Client, ingress_config: &IngressConfig) -> Result<(), HoprError> { +async fn open_nginx_deployment_ports( + client: Client, + ingress_config: &IngressConfig, +) -> Result<(), HoprError> { let namespace = ingress_config.namespace.as_ref().unwrap(); - let min_port = ingress_config.p2p_port_min.as_ref().unwrap().parse::().unwrap(); - let max_port = ingress_config.p2p_port_max.as_ref().unwrap().parse::().unwrap(); - let label_selector = ingress_config.selector_labels.as_ref().unwrap().iter().map(|entry| format!("{}={}", entry.0, entry.1)).collect::>().join(","); + let min_port = ingress_config + .p2p_port_min + .as_ref() + .unwrap() + .parse::() + .unwrap(); + let max_port = ingress_config + .p2p_port_max + .as_ref() + .unwrap() + .parse::() + .unwrap(); + let label_selector = ingress_config + .selector_labels + .as_ref() + .unwrap() + .iter() + .map(|entry| format!("{}={}", entry.0, entry.1)) + .collect::>() + .join(","); let api_deployment: Api = Api::namespaced(client.clone(), namespace); - let deployments = api_deployment.list(&ListParams::default().labels(&label_selector)).await?; - let mut deployment = deployments.items.first().map(|deployment| deployment.to_owned()).unwrap(); - let pod_spec = deployment.to_owned().spec.unwrap().template.spec.unwrap().to_owned(); - let deployment_ports: Vec = pod_spec.containers.first().unwrap().ports.as_ref().unwrap().to_owned(); - let mut new_deployment_ports: Vec = deployment_ports.iter() + let deployments = api_deployment + .list(&ListParams::default().labels(&label_selector)) + .await?; + let mut deployment = deployments + .items + .first() + .map(|deployment| deployment.to_owned()) + .unwrap(); + let pod_spec = deployment + .to_owned() + .spec + .unwrap() + .template + .spec + .unwrap() + .to_owned(); + let deployment_ports: Vec = pod_spec + .containers + .first() + .unwrap() + .ports + .as_ref() + .unwrap() + .to_owned(); + let mut new_deployment_ports: Vec = deployment_ports + .iter() .map(|e| e.to_owned()) - .filter(|port_spec| port_spec.container_port < min_port || port_spec.container_port > max_port) + .filter(|port_spec| { + port_spec.container_port < min_port || port_spec.container_port > max_port + }) .collect(); for port_number in min_port..max_port { - new_deployment_ports.push(ContainerPort{ - container_port: port_number, - name: Some(format!("{}-tcp",port_number.to_string())), + new_deployment_ports.push(ContainerPort { + container_port: port_number, + name: Some(format!("{}-tcp", port_number.to_string())), protocol: Some("TCP".to_string()), - ..ContainerPort::default()}); - new_deployment_ports.push(ContainerPort{ - container_port: port_number, - name: Some(format!("{}-udp",port_number.to_string())), + ..ContainerPort::default() + }); + new_deployment_ports.push(ContainerPort { + container_port: port_number, + name: Some(format!("{}-udp", port_number.to_string())), protocol: Some("UDP".to_string()), - ..ContainerPort::default()}); + ..ContainerPort::default() + }); } - deployment.spec.as_mut().unwrap().template.spec.as_mut().unwrap().containers[0].ports = Some(new_deployment_ports.to_owned()); + deployment + .spec + .as_mut() + .unwrap() + .template + .spec + .as_mut() + .unwrap() + .containers[0] + .ports = Some(new_deployment_ports.to_owned()); - let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation{ + let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation { path: "/spec/template/spec/containers".to_owned(), - value: json!([deployment.spec.as_mut().unwrap().template.spec.as_mut().unwrap().containers[0]]) + value: json!([deployment + .spec + .as_mut() + .unwrap() + .template + .spec + .as_mut() + .unwrap() + .containers[0]]), })]); let patch: Patch<&Value> = Patch::Json::<&Value>(json_patch); - match api_deployment.patch(&deployment.metadata.name.as_ref().unwrap().to_owned(), &PatchParams::default(), &patch).await { - Ok(_) => Ok(()), - Err(error) => { - Ok(error!("Could not open Nginx default ports on deployment: {:?}", error)) - } + match api_deployment + .patch( + &deployment.metadata.name.as_ref().unwrap().to_owned(), + &PatchParams::default(), + &patch, + ) + .await + { + Ok(_) => Ok(()), + Err(error) => Ok(error!( + "Could not open Nginx default ports on deployment: {:?}", + error + )), } } // Modifies Nginx service to add a range of ports that will be used by the operator while creating new nodes -async fn open_nginx_service_ports(client: Client, ingress_config: &IngressConfig) -> Result<(), HoprError> { +async fn open_nginx_service_ports( + client: Client, + ingress_config: &IngressConfig, +) -> Result<(), HoprError> { let namespace = ingress_config.namespace.as_ref().unwrap(); - let min_port = ingress_config.p2p_port_min.as_ref().unwrap().parse::().unwrap(); - let max_port = ingress_config.p2p_port_max.as_ref().unwrap().parse::().unwrap(); - let label_selector = ingress_config.selector_labels.as_ref().unwrap().iter().map(|entry| format!("{}={}", entry.0, entry.1)).collect::>().join(","); + let min_port = ingress_config + .p2p_port_min + .as_ref() + .unwrap() + .parse::() + .unwrap(); + let max_port = ingress_config + .p2p_port_max + .as_ref() + .unwrap() + .parse::() + .unwrap(); + let label_selector = ingress_config + .selector_labels + .as_ref() + .unwrap() + .iter() + .map(|entry| format!("{}={}", entry.0, entry.1)) + .collect::>() + .join(","); let api_service: Api = Api::namespaced(client.clone(), namespace); - let services = api_service.list(&ListParams::default().labels(&label_selector)).await?; - let service = services.items.first().map(|deployment| deployment.to_owned()).unwrap(); + let services = api_service + .list(&ListParams::default().labels(&label_selector)) + .await?; + let service = services + .items + .first() + .map(|deployment| deployment.to_owned()) + .unwrap(); let service_ports = service.to_owned().spec.unwrap().ports.unwrap().to_owned(); - let mut new_service_ports: Vec = service_ports.iter() + let mut new_service_ports: Vec = service_ports + .iter() .map(|e| e.to_owned()) .filter(|service_port| service_port.port < min_port || service_port.port > max_port) .collect(); for port_number in min_port..max_port { - new_service_ports.push(ServicePort{ - port: port_number, - name: Some(format!("{}-tcp",port_number.to_string())), + new_service_ports.push(ServicePort { + port: port_number, + name: Some(format!("{}-tcp", port_number.to_string())), protocol: Some("TCP".to_string()), target_port: Some(IntOrString::Int(port_number)), - ..ServicePort::default()}); - new_service_ports.push(ServicePort{ - port: port_number, - name: Some(format!("{}-udp",port_number.to_string())), + ..ServicePort::default() + }); + new_service_ports.push(ServicePort { + port: port_number, + name: Some(format!("{}-udp", port_number.to_string())), protocol: Some("UDP".to_string()), target_port: Some(IntOrString::Int(port_number)), - ..ServicePort::default()}); + ..ServicePort::default() + }); } - let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation{ + let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation { path: "/spec/ports".to_owned(), - value: json!(new_service_ports.to_owned()) + value: json!(new_service_ports.to_owned()), })]); let patch: Patch<&Value> = Patch::Json::<&Value>(json_patch); - match api_service.patch(&service.metadata.name.as_ref().unwrap().to_owned(), &PatchParams::default(), &patch).await { - Ok(_) => Ok(()), - Err(error) => { - Ok(error!("Could not open Nginx default ports on service: {:?}", error)) - } + match api_service + .patch( + &service.metadata.name.as_ref().unwrap().to_owned(), + &PatchParams::default(), + &patch, + ) + .await + { + Ok(_) => Ok(()), + Err(error) => Ok(error!( + "Could not open Nginx default ports on service: {:?}", + error + )), } } - /// Boot operator pub async fn start(client: Client, context_data: Arc) -> () { // Open Nginx Ports if context_data.config.ingress.ingress_class_name == "nginx" { - open_nginx_deployment_ports(client.clone(), &context_data.config.ingress).await.unwrap(); - open_nginx_service_ports(client, &context_data.config.ingress).await.unwrap(); + open_nginx_deployment_ports(client.clone(), &context_data.config.ingress) + .await + .unwrap(); + open_nginx_service_ports(client, &context_data.config.ingress) + .await + .unwrap(); } } diff --git a/src/cluster.rs b/src/cluster.rs index e7573b0..badc104 100644 --- a/src/cluster.rs +++ b/src/cluster.rs @@ -1,33 +1,35 @@ -use std::collections::BTreeMap; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; -use std::sync::Arc; -use std::time::Duration; -use tracing::{debug, info, warn, error}; -use crate::hoprd::{HoprdSpec, HoprdConfig}; +use crate::hoprd::HoprdSpec; use crate::hoprd_deployment_spec::HoprdDeploymentSpec; +use crate::model::Error; use crate::utils; use crate::{constants, context_data::ContextData, hoprd::Hoprd}; -use crate::model::{EnablingFlag, Error, ClusterHoprdStatusEnum}; use chrono::Utc; -use futures::{StreamExt, TryStreamExt}; -use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; -use kube::Resource; -use kube::api::{PostParams, ListParams, DeleteParams, WatchParams}; -use kube::core::{ObjectMeta, WatchEvent}; +use kube::api::{DeleteParams, ListParams, PostParams}; +use kube::core::ObjectMeta; +use kube::runtime::conditions; use kube::runtime::events::Recorder; -use schemars::JsonSchema; -use serde::{Serialize, Deserialize}; -use serde_json::{json}; +use kube::Resource; +use kube::runtime::wait::await_condition; use kube::{ api::{Api, Patch, PatchParams, ResourceExt}, client::Client, runtime::{ - controller::{Action}, events::{Event, EventType} + controller::Action, + events::{Event, EventType}, }, - CustomResource, Result + CustomResource, Result, }; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::hash_map::DefaultHasher; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; +use std::time::Duration; +use tracing::{debug, error, info}; /// Struct corresponding to the Specification (`spec`) part of the `Hoprd` resource, directly /// reflects context of the `hoprds.hoprnet.org.yaml` file to be found in this repository. @@ -35,7 +37,7 @@ use kube::{ #[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Hash)] #[kube( group = "hoprnet.org", - version = "v1alpha", + version = "v1alpha2", kind = "ClusterHoprd", plural = "clusterhoprds", derive = "PartialEq", @@ -44,89 +46,167 @@ use kube::{ #[kube(status = "ClusterHoprdStatus", shortname = "clusterhoprd")] #[serde(rename_all = "camelCase")] pub struct ClusterHoprdSpec { - pub network: String, - pub nodes: Vec, - pub ingress: Option, - pub monitoring: Option - -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone, JsonSchema, Hash)] -pub struct Node { - pub name: String, + pub identity_pool_name: String, pub replicas: i32, - pub config: Option, + pub config: String, + pub version: String, pub enabled: Option, - pub deployment: Option, - pub version: String + pub deployment: Option } - /// The status object of `Hoprd` #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] pub struct ClusterHoprdStatus { - pub update_timestamp: i64, - pub status: ClusterHoprdStatusEnum, + pub update_timestamp: String, + pub phase: ClusterHoprdPhaseEnum, pub checksum: String, + pub running_nodes: i32 } -impl ClusterHoprd { +impl Default for ClusterHoprdStatus { + fn default() -> Self { + Self { + update_timestamp: Utc::now().to_rfc3339(), + phase: ClusterHoprdPhaseEnum::Initialized, + checksum: "init".to_owned(), + running_nodes: 0 + } + } +} +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] +pub enum ClusterHoprdPhaseEnum { + // The HoprdCluster is initialized + Initialized, + /// The HoprdCluster is not synchronized with its nodes + NotScaled, + /// The HoprdCluster is performing a scaling action + Scaling, + /// The HoprdCluster is not synchronized with its nodes + OutOfSync, + // The HoprdCluster is synchronized with its nodes + Ready, + /// The HoprdCluster is being deleted + Deleting, + // Event that represents when the ClusterHoprd is syncronizing by creating new node + CreatingNode, + // Event that represents when the ClusterHoprd has created a new node + NodeCreated, + // Event that represents when the ClusterHoprd is syncronizing by creating new node + DeletingNode, + // Event that represents when the ClusterHoprd has created a new node + NodeDeleted, +} + +impl Display for ClusterHoprdPhaseEnum { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + ClusterHoprdPhaseEnum::Initialized => write!(f, "Initialized"), + ClusterHoprdPhaseEnum::NotScaled => write!(f, "NotScaled"), + ClusterHoprdPhaseEnum::Scaling => write!(f, "Scaling"), + ClusterHoprdPhaseEnum::OutOfSync => write!(f, "OutOfSync"), + ClusterHoprdPhaseEnum::Ready => write!(f, "Ready"), + ClusterHoprdPhaseEnum::Deleting => write!(f, "Deleting"), + ClusterHoprdPhaseEnum::CreatingNode => write!(f, "CreatingNode"), + ClusterHoprdPhaseEnum::NodeCreated => write!(f, "NodeCreated"), + ClusterHoprdPhaseEnum::DeletingNode => write!(f, "DeletingNode"), + ClusterHoprdPhaseEnum::NodeDeleted => write!(f, "NodeDeleted"), + } + } +} + +impl ClusterHoprd { /// Creates the hoprd nodes related with ClusterHoprd pub async fn create(&self, context: Arc) -> Result { let client: Client = context.client.clone(); let hoprd_namespace: String = self.namespace().unwrap(); - let cluster_hoprd_name: String= self.name_any(); - self.create_event(context.clone(), ClusterHoprdStatusEnum::Initializing, None).await?; - self.update_status(context.clone(), ClusterHoprdStatusEnum::Initializing).await?; - info!("Starting to create ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace}"); + let cluster_hoprd_name: String = self.name_any(); + info!("Starting to create ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace}"); self.add_finalizer(client.clone(), &cluster_hoprd_name, &hoprd_namespace).await.unwrap(); - let mut out_of_sync = false; - for node_set in self.spec.nodes.to_owned() { - self.create_node_set(context.clone(), node_set, &mut out_of_sync).await.unwrap(); - } - if ! out_of_sync { - self.create_event(context.clone(), ClusterHoprdStatusEnum::InSync, None).await?; - self.update_status(context.clone(), ClusterHoprdStatusEnum::InSync).await?; + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Initialized, None).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::Initialized).await?; + if self.spec.replicas > 0 { + self.create_event(context.clone(), ClusterHoprdPhaseEnum::NotScaled, Some(self.spec.replicas.to_string())).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::NotScaled).await?; + } else { + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Ready, None).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::Ready).await?; } Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) } // Modifies the hoprd nodes related with ClusterHoprd - pub async fn modify(&self, context: Arc) -> Result { - let client: Client = context.client.clone(); + pub async fn modify(&self, context_data: Arc) -> Result { + let client: Client = context_data.client.clone(); let hoprd_namespace: String = self.namespace().unwrap(); - let cluster_hoprd_name: String= self.name_any(); + let cluster_hoprd_name: String = self.name_any(); info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} has been successfully modified"); - self.create_event(context.clone(), ClusterHoprdStatusEnum::Synching, None).await?; - self.update_status(context.clone(), ClusterHoprdStatusEnum::Synching).await?; let annotations = utils::get_resource_kinds(client.clone(), utils::ResourceType::ClusterHoprd, utils::ResourceKind::Annotations, &cluster_hoprd_name, &hoprd_namespace).await; if annotations.contains_key(constants::ANNOTATION_LAST_CONFIGURATION) { let previous_config_text: String = annotations.get_key_value(constants::ANNOTATION_LAST_CONFIGURATION).unwrap().1.parse().unwrap(); match serde_json::from_str::(&previous_config_text) { - Ok(modified_cluster_hoprd) => { - self.check_inmutable_fields(&modified_cluster_hoprd.spec).unwrap(); - }, + Ok(previous_cluster_hoprd) => { + self.check_inmutable_fields(&previous_cluster_hoprd.spec).unwrap(); + // Handling modification + self.appply_modification(context_data.clone()).await?; + + // Handle rescaling + let unsynched_nodes: i32 = self.spec.replicas - self.status.as_ref().unwrap().running_nodes; + if unsynched_nodes != 0 { + if unsynched_nodes > 0 { + info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} requires to create {} new nodes", unsynched_nodes); + } else { + info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} requires to delete {} nodes", unsynched_nodes.abs()); + } + self.create_event(context_data.clone(),ClusterHoprdPhaseEnum::NotScaled,Some(unsynched_nodes.to_string())).await?; + self.update_phase(context_data.clone(), ClusterHoprdPhaseEnum::NotScaled).await?; + } else { + self.create_event(context_data.clone(),ClusterHoprdPhaseEnum::Ready,None).await?; + self.update_phase(context_data.clone(), ClusterHoprdPhaseEnum::Ready).await?; + } + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } Err(_err) => { - error!("Could not parse the last applied configuration of Hoprd node {cluster_hoprd_name}."); + self.create_event(context_data.clone(),ClusterHoprdPhaseEnum::OutOfSync,None).await?; + self.update_phase(context_data.clone(), ClusterHoprdPhaseEnum::OutOfSync).await?; + Err(Error::HoprdConfigError(format!("Could not parse the last applied configuration of ClusterHoprd {cluster_hoprd_name}"))) } } } else { - warn!("The ClusterHoprd {cluster_hoprd_name} resource did not have previous configuration") + self.create_event(context_data.clone(),ClusterHoprdPhaseEnum::OutOfSync,None).await?; + self.update_phase(context_data.clone(), ClusterHoprdPhaseEnum::OutOfSync).await?; + Err(Error::HoprdConfigError(format!("Could not modify ClusterHoprd {cluster_hoprd_name} because cannot recover last configuration"))) } - - Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) } // Sync Cluster with its hoprd nodes - pub async fn sync(&self, context: Arc) -> Result { + pub async fn rescale(&self, context: Arc) -> Result { let hoprd_namespace: String = self.namespace().unwrap(); - let cluster_hoprd_name: String= self.name_any(); - info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} is out of sync"); - self.create_event(context.clone(), ClusterHoprdStatusEnum::Synching, None).await?; - self.update_status(context.clone(), ClusterHoprdStatusEnum::Synching).await?; - warn!("ClusterHoprd Sync not implemented yet !!!!!"); - Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + let cluster_hoprd_name: String = self.name_any(); + if self.status.as_ref().unwrap().phase.eq(&ClusterHoprdPhaseEnum::NotScaled) { + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Scaling, None).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::Scaling).await?; + let current_unsynched_nodes = self.spec.replicas - self.status.as_ref().unwrap().running_nodes; + info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} is not scaled"); + if current_unsynched_nodes > 0 { + let node_name = self.create_node(context.clone()).await?; + self.create_event(context.clone(), ClusterHoprdPhaseEnum::NodeCreated, Some(node_name)).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::NodeCreated).await?; + } else if current_unsynched_nodes < 0 { + let node_name = self.delete_node(context.clone()).await?; + self.create_event(context.clone(), ClusterHoprdPhaseEnum::NodeDeleted, Some(node_name)).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::NodeDeleted).await?; + } else { + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Ready, None).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::Ready).await?; + }; + return Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } else { + info!("ClusterHoprd {cluster_hoprd_name} in namespace {hoprd_namespace} is already being scaling"); + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Scaling, None).await?; + return Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } } // Deletes the hoprd nodes related with ClusterHoprd @@ -134,8 +214,8 @@ impl ClusterHoprd { let cluster_hoprd_name = self.name_any(); let hoprd_namespace = self.namespace().unwrap(); let client: Client = context.client.clone(); - self.update_status(context.clone(), ClusterHoprdStatusEnum::Deleting).await?; - self.create_event(context.clone(), ClusterHoprdStatusEnum::Deleting, None).await?; + self.update_phase(context.clone(), ClusterHoprdPhaseEnum::Deleting).await?; + self.create_event(context.clone(), ClusterHoprdPhaseEnum::Deleting, None).await?; info!("Starting to delete ClusterHoprd {cluster_hoprd_name} from namespace {hoprd_namespace}"); self.delete_nodes(client.clone()).await.unwrap_or(()); self.delete_finalizer(client.clone(), &cluster_hoprd_name, &hoprd_namespace).await?; @@ -143,7 +223,7 @@ impl ClusterHoprd { Ok(Action::await_change()) // Makes no sense to delete after a successful delete, as the resource is gone } - /// Adds a finalizer in ClusterHoprd to prevent deletion of the resource by Kubernetes API and allow the controller to safely manage its deletion + /// Adds a finalizer in ClusterHoprd to prevent deletion of the resource by Kubernetes API and allow the controller to safely manage its deletion async fn add_finalizer(&self, client: Client, hoprd_name: &str, hoprd_namespace: &str) -> Result<(), Error> { let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); let pp = PatchParams::default(); @@ -155,8 +235,8 @@ impl ClusterHoprd { match api.patch(&hoprd_name, &pp, &Patch::Merge(patch)).await { Ok(_) => Ok(()), Err(error) => { - error!("Could not add finalizer on {hoprd_name}: {:?}", error); - return Err(Error::HoprdStatusError(format!("Could not add finalizer on {hoprd_name}.").to_owned())); + error!("Could not add finalizer on {hoprd_name}: {:?}",error); + Err(Error::HoprdStatusError(format!("Could not add finalizer on {hoprd_name}.").to_owned())) } } } @@ -173,63 +253,95 @@ impl ClusterHoprd { if let Some(_) = api.get_opt(&cluster_name).await? { match api.patch(&cluster_name, &pp, &Patch::Merge(patch)).await { Ok(_) => Ok(()), - Err(error) => { - Ok(error!("Could not delete finalizer on {cluster_name}: {:?}", error)) - } + Err(error) => Ok(error!("Could not delete finalizer on {cluster_name}: {:?}", error)) } } else { - Ok(debug!("ClusterHoprd {cluster_name} has already been deleted")) + Ok(debug!("ClusterHoprd {cluster_name} already deleted")) } } /// Check the fileds that cannot be modifed - fn check_inmutable_fields(&self, spec: &ClusterHoprdSpec) -> Result<(),Error> { - if ! self.spec.network.eq(&spec.network) { - return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, network field cannot be changed on {}.", self.name_any()))); + fn check_inmutable_fields(&self, spec: &ClusterHoprdSpec) -> Result<(), Error> { + if !self.spec.identity_pool_name.eq(&spec.identity_pool_name) { + return Err(Error::HoprdConfigError(format!("Cluster configuration is invalid, identity_pool_name field cannot be changed on {}.", self.name_any()))); } Ok(()) } /// Creates an event for ClusterHoprd given the new ClusterHoprdStatusEnum - pub async fn create_event(&self, context: Arc, status: ClusterHoprdStatusEnum, node_name: Option) -> Result<(), Error> { - let client: Client = context.client.clone(); + pub async fn create_event(&self, context: Arc, status: ClusterHoprdPhaseEnum, attribute: Option) -> Result<(), Error> { + let client: Client = context.client.clone(); let ev: Event = match status { - ClusterHoprdStatusEnum::Initializing => Event { - type_: EventType::Normal, - reason: "Initializing".to_string(), - note: Some("Initializing ClusterHoprd node".to_owned()), - action: "Starting the process of creating a new cluster of hoprd".to_string(), - secondary: None, - }, - ClusterHoprdStatusEnum::Synching => Event { - type_: EventType::Normal, - reason: "Synching".to_string(), - note: Some(format!("ClusterHoprd synchronized with node {}", node_name.unwrap_or("".to_owned()))), - action: "Node secrets are being created".to_string(), - secondary: None, - }, - ClusterHoprdStatusEnum::InSync => Event { - type_: EventType::Normal, - reason: "RegisteringInNetwork".to_string(), - note: Some("ClusterHoprd is sinchronized".to_owned()), - action: "Node is registering into the Network registry".to_string(), - secondary: None, - }, - - ClusterHoprdStatusEnum::Deleting => Event { - type_: EventType::Normal, - reason: "Deleting".to_string(), - note: Some("ClusterHoprd is being deleted".to_owned()), - action: "Node deletion started".to_string(), - secondary: None, - }, - ClusterHoprdStatusEnum::OutOfSync => Event { - type_: EventType::Warning, - reason: "Out of sync".to_string(), - note: Some(format!("ClusterHoprd is not sync with node {}", node_name.unwrap_or("unknown".to_owned()))), - action: "Node sync failed".to_string(), - secondary: None, - } + ClusterHoprdPhaseEnum::Initialized => Event { + type_: EventType::Normal, + reason: "Initialized".to_string(), + note: Some("ClusterHoprd node initialized".to_owned()), + action: "Starting the process of creating a new cluster of hoprd".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::NotScaled => Event { + type_: EventType::Warning, + reason: "NotScaled".to_string(), + note: Some(format!("ClusterHoprd is not sync. There are {} nodes pending to be synchronized", attribute.as_ref().unwrap_or(&"unknown".to_string()))), + action: "ClusterHoprd is not scaled".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::Scaling => Event { + type_: EventType::Warning, + reason: "Scaling".to_string(), + note: Some(format!("ClusterHoprd is in a process of scaling")), + action: "ClusterHoprd is not scaled".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::OutOfSync => Event { + type_: EventType::Warning, + reason: "OutOfSync".to_string(), + note: Some(format!("ClusterHoprd is not sync.")), + action: "ClusterHoprd is not sync".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::Ready => Event { + type_: EventType::Normal, + reason: "Ready".to_string(), + note: Some("ClusterHoprd is in ready phase".to_owned()), + action: "ClusterHoprd is in ready phase".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::Deleting => Event { + type_: EventType::Normal, + reason: "Deleting".to_string(), + note: Some("ClusterHoprd is being deleted".to_owned()), + action: "ClusterHoprd is being deleted".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::CreatingNode => Event { + type_: EventType::Normal, + reason: "CreatingNode".to_string(), + note: Some("A new node is being created for the cluster".to_owned()), + action: "A new node is being created for the cluster".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::NodeCreated => Event { + type_: EventType::Normal, + reason: "NodeCreated".to_string(), + note: Some("A new node is created for the cluster".to_owned()), + action: "A new node is created for the cluster".to_string(), + secondary: None, + }, + ClusterHoprdPhaseEnum::DeletingNode => Event { + type_: EventType::Normal, + reason: "DeletingNode".to_string(), + note: Some(format!("Node {} is being deleted from the cluster", attribute.as_ref().unwrap_or(&"unknown".to_string()))), + action: format!("Node {} is being deleted from the cluster", attribute.as_ref().unwrap_or(&"unknown".to_string())), + secondary: None, + }, + ClusterHoprdPhaseEnum::NodeDeleted => Event { + type_: EventType::Normal, + reason: "NodeDeleted".to_string(), + note: Some(format!("Node {} is deleted from the cluster", attribute.as_ref().unwrap_or(&"unknown".to_string()))), + action: format!("Node {} is deleted from the cluster", attribute.as_ref().unwrap_or(&"unknown".to_string())), + secondary: None, + }, }; let recorder: Recorder = context.state.read().await.generate_cluster_hoprd_event(client.clone(), self); @@ -237,103 +349,137 @@ impl ClusterHoprd { } /// Updates the status of ClusterHoprd - pub async fn update_status(&self, context: Arc, status: ClusterHoprdStatusEnum) -> Result<(), Error> { - let client: Client = context.client.clone(); - let cluster_hoprd_name = self.metadata.name.as_ref().unwrap().to_owned(); - let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); + pub async fn update_phase(&self, context: Arc, phase: ClusterHoprdPhaseEnum) -> Result<(), Error> { + let client: Client = context.client.clone(); + let cluster_hoprd_name = self.metadata.name.as_ref().unwrap().to_owned(); + let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); + let mut cluster_hoprd_status = self + .status + .as_ref() + .unwrap_or(&&ClusterHoprdStatus::default()) + .to_owned(); - let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); - if status.eq(&ClusterHoprdStatusEnum::Deleting) { - Ok(()) - } else { - let mut hasher: DefaultHasher = DefaultHasher::new(); - self.spec.clone().hash(&mut hasher); - let hash: u64 = hasher.finish(); - let status = ClusterHoprdStatus { - update_timestamp: Utc::now().timestamp(), - status: status, - checksum: format!("checksum-{}",hash.to_string()) - }; - let pp = PatchParams::default(); - let patch = json!({ - "status": status - }); - match api.patch(&cluster_hoprd_name, &pp, &Patch::Merge(patch)).await { - Ok(_cluster_hopr) => Ok(()), - Err(error) => { - Ok(error!("Could not update status on cluster {cluster_hoprd_name}: {:?}", error)) + let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); + if phase.eq(&ClusterHoprdPhaseEnum::Deleting) { + Ok(()) + } else { + cluster_hoprd_status.update_timestamp = Utc::now().to_rfc3339(); + cluster_hoprd_status.checksum = self.get_checksum(); + cluster_hoprd_status.phase = phase; + if phase.eq(&ClusterHoprdPhaseEnum::NodeCreated) { + cluster_hoprd_status.running_nodes += 1; + } else if phase.eq(&ClusterHoprdPhaseEnum::NodeDeleted) { + cluster_hoprd_status.running_nodes -= 1; + }; + + if phase.eq(&ClusterHoprdPhaseEnum::NodeCreated) || phase.eq(&ClusterHoprdPhaseEnum::NodeDeleted) { + if cluster_hoprd_status.running_nodes == self.spec.replicas { + cluster_hoprd_status.phase = ClusterHoprdPhaseEnum::Ready; + } else { + cluster_hoprd_status.phase = ClusterHoprdPhaseEnum::NotScaled; + } + }; + + let patch = Patch::Merge(json!({"status": cluster_hoprd_status })); + match api.patch(&cluster_hoprd_name, &PatchParams::default(), &patch).await + { + Ok(_cluster_hopr) => Ok(()), + Err(error) => Ok(error!("Could not update phase on cluster {cluster_hoprd_name}: {:?}", error)) } } } - -} - /// Creates a set of hoprd resources with similar configuration - async fn create_node_set(&self, context: Arc, node_set: Node, out_of_sync: &mut bool) -> Result<(), Error> { - let client: Client = context.client.clone(); - info!("Starting to create nodeset {}", node_set.name.to_owned()); - for node_instance in 0..node_set.replicas.to_owned() { - let name = format!("{}-{}-{}", self.name_any(), node_set.name.to_owned(), node_instance.to_owned()).to_owned(); - let hoprd_spec: HoprdSpec = HoprdSpec { - config: node_set.config.to_owned(), - enabled: node_set.enabled, - network: self.spec.network.to_owned(), - ingress: self.spec.ingress.to_owned(), - monitoring: self.spec.monitoring.to_owned(), - version: node_set.version.to_owned(), - deployment: node_set.deployment.to_owned(), - secret: None - }; - if self.create_hoprd_resource(client.clone(), name.to_owned(), hoprd_spec).await.is_ok() { - self.create_event(context.clone(), ClusterHoprdStatusEnum::Synching, Some(name)).await?; - } else { - *out_of_sync = true; - self.create_event(context.clone(), ClusterHoprdStatusEnum::OutOfSync, Some(name)).await?; - } + async fn create_node(&self, context: Arc) -> Result { + let cluster_name = self.metadata.name.as_ref().unwrap().to_owned(); + let node_instance = self.status.clone().unwrap().running_nodes + 1; + let node_name = format!("{}-{}", cluster_name.to_owned(), node_instance).to_owned(); + info!("Creating node {} for cluster {}", node_name, cluster_name.to_owned()); + let hoprd_spec: HoprdSpec = HoprdSpec { + config: self.spec.config.to_owned(), + enabled: self.spec.enabled, + version: self.spec.version.to_owned(), + deployment: self.spec.deployment.to_owned(), + identity_pool_name: self.spec.identity_pool_name.to_owned(), + identity_name: None + }; + match self.create_hoprd_resource(context.clone(), node_name.clone(), hoprd_spec).await { + Ok(hoprd) => Ok(hoprd.name_any()), + Err(error) => { + error!("{:?}", error); + Err(Error::ClusterHoprdSynchError(format!("Hoprd node {} not created", node_name))) } - Ok(()) + } + } + + /// Creates a set of hoprd resources with similar configuration + async fn delete_node(&self, context: Arc) -> Result { + let cluster_name = self.metadata.name.as_ref().unwrap().to_owned(); + let node_instance = self.status.clone().unwrap().running_nodes; + let node_name = format!("{}-{}", cluster_name.to_owned(), node_instance).to_owned(); + info!("Deleting node {} for cluster {}", node_name, cluster_name.to_owned()); + let api: Api = Api::namespaced(context.client.clone(), &self.namespace().unwrap()); + if let Some(hoprd_node) = api.get_opt(&node_name).await? { + let uid = hoprd_node.metadata.uid.unwrap(); + api.delete(&node_name, &DeleteParams::default()).await?; + await_condition(api, &node_name.to_owned(), conditions::is_deleted(&uid)).await.unwrap(); + }; + Ok(node_name) } /// Creates a hoprd resource - async fn create_hoprd_resource(&self, client: Client, name: String, hoprd_spec: HoprdSpec) -> Result { - let mut labels: BTreeMap = utils::common_lables(&name.to_owned()); + async fn create_hoprd_resource(&self, context: Arc, name: String, hoprd_spec: HoprdSpec) -> Result { + let mut labels: BTreeMap = utils::common_lables(context.config.instance.name.to_owned(), Some(name.to_owned()), Some("node".to_owned())); labels.insert(constants::LABEL_NODE_CLUSTER.to_owned(), self.name_any()); - let api: Api = Api::namespaced(client.clone(), &self.namespace().unwrap()); + let api: Api = Api::namespaced(context.client.clone(), &self.namespace().unwrap()); let owner_references: Option> = Some(vec![self.controller_owner_ref(&()).unwrap()]); let hoprd: Hoprd = Hoprd { - metadata: ObjectMeta { + metadata: ObjectMeta { labels: Some(labels.clone()), - name: Some(name.to_owned()), + name: Some(name.to_owned()), namespace: self.namespace().to_owned(), owner_references, ..ObjectMeta::default() - }, + }, spec: hoprd_spec.to_owned(), - status: None + status: None, }; // Create the Hoprd resource defined above let hoprd_created = api.create(&PostParams::default(), &hoprd).await?; - // Wait for the Hoprd deployment to be created - let lp = WatchParams::default() - .fields(&format!("metadata.name={name}")) - .timeout(constants::OPERATOR_NODE_SYNC_TIMEOUT); - let deployment_api: Api = Api::namespaced(client.clone(), &self.namespace().unwrap()); - let mut stream = deployment_api.watch(&lp, "0").await?.boxed(); - while let Some(deployment) = stream.try_next().await? { - match deployment { - WatchEvent::Added(deployment) => { - debug!("Deployment uid {:?} is created for node {}", deployment.uid().unwrap(), name); - info!("Hoprd node {name} has been added to the cluster"); - return Ok(hoprd_created); - }, - WatchEvent::Modified(_) => return Err(Error::ClusterHoprdSynchError("Modified operation not expected".to_owned())), - WatchEvent::Deleted(_) => return Err(Error::ClusterHoprdSynchError("Deleted operation not expected".to_owned())), - WatchEvent::Bookmark(_) => return Err(Error::ClusterHoprdSynchError("Bookmark operation not expected".to_owned())), - WatchEvent::Error(_) => return Err(Error::ClusterHoprdSynchError("Error operation not expected".to_owned())) - } + hoprd_created.wait_deployment(context.client.clone()).await?; + Ok(hoprd_created) + + } + + /// Creates a hoprd resource + async fn appply_modification(&self, context: Arc) -> Result<(), Error> { + let api: Api = Api::namespaced(context.client.clone(), &self.namespace().unwrap()); + for hoprd_node in self.get_my_nodes(api.clone()).await.unwrap() { + self.modify_hoprd_resource(context.clone(), api.clone(), hoprd_node.name_any()).await?; } - return Err(Error::ClusterHoprdSynchError("Timeout waiting for Hoprd node to be created".to_owned())) + Ok(()) + } + + pub fn get_checksum(&self) -> String { + let mut hasher: DefaultHasher = DefaultHasher::new(); + self.spec.clone().hash(&mut hasher); + return hasher.finish().to_string(); + } + + /// Modifies a specific hoprd resource + async fn modify_hoprd_resource(&self, context: Arc, api: Api, name: String) -> Result { + let hoprd_spec: HoprdSpec = HoprdSpec { + config: self.spec.config.to_owned(), + enabled: self.spec.enabled, + version: self.spec.version.to_owned(), + deployment: self.spec.deployment.to_owned(), + identity_pool_name: self.spec.identity_pool_name.to_owned(), + identity_name: None + }; + let patch = &Patch::Merge(json!({ "spec": hoprd_spec })); + let hoprd_modified = api.patch(&name, &PatchParams::default(), patch).await.unwrap(); + hoprd_modified.wait_deployment(context.client.clone()).await?; + Ok(hoprd_modified) } /// Get the hoprd nodes owned by the ClusterHoprd @@ -350,15 +496,13 @@ impl ClusterHoprd { let nodes = self.get_my_nodes(api.clone()).await.unwrap(); for node in nodes { let node_name = &node.name_any(); - match api.delete(node_name, &DeleteParams::default()).await { + match api.delete(node_name, &DeleteParams::default()).await { Ok(node_deleted) => { node_deleted.map_right(|s| info!("Deleted Node: {:?}", s)); - }, + } Err(_error) => info!("The Hoprd node {:?} deletion failed", node.name_any()) - }; + }; } Ok(()) } } - - diff --git a/src/constants.rs b/src/constants.rs index 9acf42f..104d3c3 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -3,58 +3,47 @@ pub const RECONCILE_FREQUENCY: u64 = 10; pub const OPERATOR_ENVIRONMENT: &str = "OPERATOR_ENVIRONMENT"; pub const OPERATOR_FINALIZER: &str = "hoprds.hoprnet.org/finalizer"; pub const OPERATOR_JOB_TIMEOUT: u64 = 300; +pub const OPERATOR_JOB_SCRIPT_URL: &str = + "https://raw.githubusercontent.com/hoprnet/hoprd-operator/ausias/refactor-operator/scripts"; // This value `OPERATOR_NODE_SYNC_TIMEOUT` should be lower than 295 pub const OPERATOR_NODE_SYNC_TIMEOUT: u32 = 290; -pub const OPERATOR_INSTANCE_NAMESPACE: &str = "OPERATOR_INSTANCE_NAMESPACE"; -pub const SECRET_NAME: &str = "SECRET_NAME"; pub const OPERATOR_P2P_MIN_PORT: &str = "9000"; pub const OPERATOR_P2P_MAX_PORT: &str = "9100"; +pub const IDENTITY_POOL_WALLET_PRIVATE_KEY_REF_KEY: &str = "DEPLOYER_PRIVATE_KEY"; +pub const IDENTITY_POOL_IDENTITY_PASSWORD_REF_KEY: &str = "IDENTITY_PASSWORD"; +pub const IDENTITY_POOL_API_TOKEN_REF_KEY: &str = "HOPRD_API_TOKEN"; +pub const IDENTITY_POOL_METRICS_PASSWORD_REF_KEY: &str = "METRICS_PASSWORD"; // Annotations -pub const ANNOTATION_HOPRD_NETWORK_REGISTRY: &str = "hoprds.hoprnet.org/network_registry"; -pub const ANNOTATION_HOPRD_FUNDED: &str = "hoprds.hoprnet.org/funded"; -pub const ANNOTATION_REPLICATOR_NAMESPACES: &str = "replicator.v1.mittwald.de/replicate-to"; pub const ANNOTATION_LAST_CONFIGURATION: &str = "kubectl.kubernetes.io/last-applied-configuration"; // Labels -pub const LABEL_KUBERNETES_COMPONENT: &str = "app.kubernetes.io/component"; pub const LABEL_KUBERNETES_NAME: &str = "app.kubernetes.io/name"; pub const LABEL_KUBERNETES_INSTANCE: &str = "app.kubernetes.io/instance"; +pub const LABEL_KUBERNETES_COMPONENT: &str = "app.kubernetes.io/component"; + +pub const LABEL_KUBERNETES_IDENTITY_POOL: &str = "hoprds.hoprnet.org/identitypool"; pub const LABEL_NODE_PEER_ID: &str = "hoprds.hoprnet.org/peerId"; -pub const LABEL_NODE_ADDRESS: &str = "hoprds.hoprnet.org/address"; +pub const LABEL_NODE_NATIVE_ADDRESS: &str = "hoprds.hoprnet.org/nativeAddress"; +pub const LABEL_NODE_SAFE_ADDRESS: &str = "hoprds.hoprnet.org/safeAddress"; +pub const LABEL_NODE_MODULE_ADDRESS: &str = "hoprds.hoprnet.org/moduleAddress"; pub const LABEL_NODE_NETWORK: &str = "hoprds.hoprnet.org/network"; -pub const LABEL_NODE_LOCKED: &str = "hoprds.hoprnet.org/locked"; pub const LABEL_NODE_CLUSTER: &str = "hoprds.hoprnet.org/cluster"; -// FINALIZERS -pub const FINALIZER_SECRET: &str = "hoprds.hoprnet.org/secret"; - // Kubernetes Specs pub const HOPR_DOCKER_REGISTRY: &str = "europe-west3-docker.pkg.dev"; pub const HOPR_DOCKER_IMAGE_NAME: &str = "hoprassociation/docker-images/hoprd"; -pub const HOPR_PRIVATE_KEY: &str = "PRIVATE_KEY"; -pub const HOPRD_PEER_ID: &str = "HOPRD_PEER_ID"; -pub const HOPRD_ADDRESS: &str = "HOPRD_ADDRESS"; -pub const HOPRD_METRICS_PASSWORD: &str = "HOPRD_METRICS_PASSWORD"; -pub const HOPLI_ETHERSCAN_API_KEY: &str = "ETHERSCAN_API_KEY"; // HOPRD Arguments +pub const HOPRD_IDENTITY_FILE: &str = "HOPRD_IDENTITY_FILE"; pub const HOPRD_PASSWORD: &str = "HOPRD_PASSWORD"; pub const HOPRD_API_TOKEN: &str = "HOPRD_API_TOKEN"; pub const HOPRD_NETWORK: &str = "HOPRD_NETWORK"; +pub const HOPRD_CONFIGURATION_FILE_PATH: &str = "HOPRD_CONFIGURATION_FILE_PATH"; +pub const HOPRD_CONFIGURATION: &str = "HOPRD_CONFIGURATION"; pub const HOPRD_ANNOUNCE: &str = "HOPRD_ANNOUNCE"; -pub const HOPRD_PROVIDER: &str = "HOPRD_PROVIDER"; -pub const HOPRD_DEFAULT_STRATEGY: &str = "HOPRD_DEFAULT_STRATEGY"; -pub const HOPRD_MAX_AUTOCHANNELS: &str = "HOPRD_MAX_AUTOCHANNELS"; -pub const HOPRD_AUTO_REDEEM_TICKETS: &str = "HOPRD_AUTO_REDEEM_TICKETS"; -pub const HOPRD_CHECK_UNREALIZED_BALANCE: &str = "HOPRD_CHECK_UNREALIZED_BALANCE"; -pub const HOPRD_ALLOW_PRIVATE_NODE_CONNECTIONS: &str = "HOPRD_ALLOW_PRIVATE_NODE_CONNECTIONS"; -pub const HOPRD_TEST_ANNOUNCE_LOCAL_ADDRESSES: &str = "HOPRD_TEST_ANNOUNCE_LOCAL_ADDRESSES"; -pub const HOPRD_HEARTBEAT_INTERVAL: &str = "HOPRD_HEARTBEAT_INTERVAL"; -pub const HOPRD_HEARTBEAT_THRESHOLD: &str = "HOPRD_HEARTBEAT_THRESHOLD"; -pub const HOPRD_HEARTBEAT_VARIANCE: &str = "HOPRD_HEARTBEAT_VARIANCE"; -pub const HOPRD_ON_CHAIN_CONFIRMATIONS: &str = "HOPRD_ON_CHAIN_CONFIRMATIONS"; -pub const HOPRD_NETWORK_QUALITY_THRESHOLD: &str = "HOPRD_NETWORK_QUALITY_THRESHOLD"; +pub const HOPRD_SAFE_ADDRESS: &str = "HOPRD_SAFE_ADDRESS"; +pub const HOPRD_MODULE_ADDRESS: &str = "HOPRD_MODULE_ADDRESS"; pub const HOPRD_IDENTITY: &str = "HOPRD_IDENTITY"; pub const HOPRD_DATA: &str = "HOPRD_DATA"; pub const HOPRD_HOST: &str = "HOPRD_HOST"; @@ -63,4 +52,3 @@ pub const HOPRD_API_HOST: &str = "HOPRD_API_HOST"; pub const HOPRD_INIT: &str = "HOPRD_INIT"; pub const HOPRD_HEALTH_CHECK: &str = "HOPRD_HEALTH_CHECK"; pub const HOPRD_HEALTH_CHECK_HOST: &str = "HOPRD_HEALTH_CHECK_HOST"; -pub const ENABLED: bool= true; diff --git a/src/context_data.rs b/src/context_data.rs index 5d07fe1..6356c32 100644 --- a/src/context_data.rs +++ b/src/context_data.rs @@ -1,12 +1,15 @@ -use std::{sync::{Arc}, env}; -use tokio::{sync::RwLock}; - -use kube::{Client, runtime::events::{Reporter, Recorder}, Resource}; -use serde::Serialize; - -use crate::{operator_config::OperatorConfig, constants, hoprd::Hoprd, cluster::ClusterHoprd}; +use std::{env, sync::Arc, collections::BTreeMap}; +use tokio::sync::RwLock; +use kube::{ + runtime::events::{Recorder, Reporter}, + Client, Resource, Api, api::ListParams, ResourceExt, +}; +use crate::{ + cluster::ClusterHoprd, constants, hoprd::Hoprd, identity_hoprd::IdentityHoprd, + identity_pool::{IdentityPool}, operator_config::OperatorConfig, +}; #[derive(Clone)] pub struct ContextData { @@ -15,53 +18,99 @@ pub struct ContextData { /// In memory state pub state: Arc>, - pub config: OperatorConfig + pub config: OperatorConfig, } /// State wrapper around the controller outputs for the web server impl ContextData { - // Create a Controller Context that can update State pub async fn new(client: Client) -> Self { - let operator_environment= env::var(constants::OPERATOR_ENVIRONMENT).unwrap(); + let operator_environment = env::var(constants::OPERATOR_ENVIRONMENT).unwrap(); let config_path = if operator_environment.eq("production") { let path = "/app/config/config.yaml".to_owned(); path } else { let mut path = env::current_dir().as_ref().unwrap().to_str().unwrap().to_owned(); - path.push_str(&format!("/sample_config-{operator_environment}.yaml")); + path.push_str(&format!("/test-data/sample_config-{operator_environment}.yaml")); path }; let config_file = std::fs::File::open(&config_path).expect("Could not open config file."); let config: OperatorConfig = serde_yaml::from_reader(config_file).expect("Could not read contents of config file."); + let api = Api::::all(client.clone()); + let pools = api.list(&ListParams::default()).await.unwrap().items.clone(); + ContextData { client, - state: Arc::new(RwLock::new(State::default())), - config: config + state: Arc::new(RwLock::new(State::new(pools))), + config, } } -} + pub async fn sync_identities(context_data: Arc) { + let api_identities = Api::::all(context_data.client.clone()); + let all_identities = api_identities.list(&ListParams::default()).await.unwrap().items.clone(); + let api_hoprd = Api::::all(context_data.client.clone()); + let all_hoprds: Vec = api_hoprd.list(&ListParams::default()).await.unwrap().items.clone() + .iter().map(|hoprd| format!("{}-{}", hoprd.metadata.namespace.as_ref().unwrap(), hoprd.metadata.name.as_ref().unwrap())).collect(); + for identity_hoprd in all_identities { + if let Some(status) = identity_hoprd.to_owned().status { + if let Some(hoprd_name) = status.hoprd_node_name { + let identity_full_name = format!("{}-{}", identity_hoprd.to_owned().metadata.namespace.unwrap(), hoprd_name); + if ! all_hoprds.contains(&identity_full_name) { + // Remove hoprd relationship + identity_hoprd.unlock(context_data.clone()).await.expect("Could not synchronize identity"); + } + } + } + } + } -#[derive(Clone, Serialize)] +} + +#[derive(Debug,Clone)] pub struct State { - #[serde(skip)] pub reporter: Reporter, + pub identity_pool: BTreeMap> } -impl Default for State { - fn default() -> Self { - Self { +impl State { + pub fn new(identity_pools: Vec) -> State { + State { reporter: Reporter::from("hopr-operator-controller"), + identity_pool: identity_pools.into_iter().map(|identity_pool| (format!("{}-{}",identity_pool.namespace().unwrap(), identity_pool.name_any()), Arc::new(identity_pool))).collect() } } -} -impl State { + + pub fn add_identity_pool(&mut self, identity_pool: IdentityPool) { + self.identity_pool.insert(format!("{}-{}",identity_pool.namespace().unwrap(), identity_pool.name_any()), Arc::new(identity_pool)); + } + + pub fn remove_identity_pool(&mut self, namespace: &String, identity_pool_name: &String) { + self.identity_pool.remove(&format!("{}-{}",namespace, identity_pool_name)); + } + + pub fn get_identity_pool(&self, namespace: &String, identity_pool_name: &String) -> Option> { + self.identity_pool.get(&format!("{}-{}",namespace, identity_pool_name)).map(|x| x.clone()) + } + + pub fn update_identity_pool(&mut self, identity_pool: IdentityPool) { + self.remove_identity_pool(&identity_pool.metadata.namespace.as_ref().unwrap(), &identity_pool.metadata.name.as_ref().unwrap()); + self.add_identity_pool(identity_pool); + } + + pub fn generate_identity_hoprd_event(&self, client: Client, identity_hoprd: &IdentityHoprd) -> Recorder { + Recorder::new(client, self.reporter.clone(), identity_hoprd.object_ref(&())) + } + + pub fn generate_identity_pool_event(&self, client: Client, identity_pool: &IdentityPool) -> Recorder { + Recorder::new(client, self.reporter.clone(), identity_pool.object_ref(&())) + } + pub fn generate_hoprd_event(&self, client: Client, hoprd: &Hoprd) -> Recorder { Recorder::new(client, self.reporter.clone(), hoprd.object_ref(&())) } - pub fn generate_cluster_hoprd_event(&self, client: Client, cluster_hoprd: &ClusterHoprd) -> Recorder { + pub fn generate_cluster_hoprd_event(&self,client: Client,cluster_hoprd: &ClusterHoprd) -> Recorder { Recorder::new(client, self.reporter.clone(), cluster_hoprd.object_ref(&())) } -} \ No newline at end of file +} diff --git a/src/controller_cluster.rs b/src/controller_cluster.rs index aad0a42..b1ec52e 100644 --- a/src/controller_cluster.rs +++ b/src/controller_cluster.rs @@ -1,39 +1,45 @@ - use futures::StreamExt; use kube::{ - api::{Api}, + api::Api, client::Client, runtime::{ - controller::{Action, Controller}, watcher::Config + controller::{Action, Controller}, + watcher::Config, }, - Resource, Result + Resource, Result, }; -use tracing::{error}; -use std::{sync::Arc, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; -use tokio::{ time::Duration}; +use std::sync::Arc; +use tokio::time::Duration; +use tracing::error; -use crate::{ constants::{self}, cluster::{ClusterHoprd, ClusterHoprdSpec}, context_data::ContextData, hoprd::Hoprd, model::{ClusterHoprdStatusEnum, Error}}; +use crate::{ + cluster::{ClusterHoprd, ClusterHoprdPhaseEnum}, + constants::{self}, + context_data::ContextData, + hoprd::Hoprd, + model::Error, +}; -/// Action to be taken upon an `Hoprd` resource during reconciliation +/// Action to be taken upon an `ClusterHoprd` resource during reconciliation enum ClusterHoprdAction { - /// Create the subresources, this includes spawning `n` pods with Hoprd service + /// Create the subresources, this includes spawning multiple `Hoprd` resources Create, - /// Modify Hoprd resource + /// Modify ClusterHoprd resource Modify, - /// Sync Hoprd resources - Sync, + /// Sync ClusterHoprd resources + Rescale, /// Delete all subresources created in the `Create` phase Delete, - /// This `Hoprd` resource is in desired state and requires no actions to be taken + /// This `ClusterHoprd` resource is in desired state and requires no actions to be taken NoOp, } /// Resources arrives into reconciliation queue in a certain state. This function looks at -/// the state of given `Hoprd` resource and decides which actions needs to be performed. +/// the state of given `ClusterHoprd` resource and decides which actions needs to be performed. /// The finite set of possible actions is represented by the `ClusterHoprdAction` enum. /// /// # Arguments -/// - `hoprd`: A reference to `Hoprd` being reconciled to decide next action upon. +/// - `cluster_hoprd`: A reference to `ClusterHoprd` being reconciled to decide next action upon. fn determine_action(cluster_hoprd: &ClusterHoprd) -> ClusterHoprdAction { return if cluster_hoprd.meta().deletion_timestamp.is_some() { ClusterHoprdAction::Delete @@ -44,16 +50,12 @@ fn determine_action(cluster_hoprd: &ClusterHoprd) -> ClusterHoprdAction { .map_or(true, |finalizers| finalizers.is_empty()) { ClusterHoprdAction::Create - } else if cluster_hoprd.status.as_ref().unwrap().status == ClusterHoprdStatusEnum::OutOfSync { - ClusterHoprdAction::Sync - } else if cluster_hoprd.status.as_ref().unwrap().status == ClusterHoprdStatusEnum::Deleting { + } else if cluster_hoprd.status.as_ref().unwrap().phase == ClusterHoprdPhaseEnum::NotScaled || cluster_hoprd.status.as_ref().unwrap().phase == ClusterHoprdPhaseEnum::Scaling { + ClusterHoprdAction::Rescale + } else if cluster_hoprd.status.as_ref().unwrap().phase == ClusterHoprdPhaseEnum::Deleting { ClusterHoprdAction::NoOp } else { - let mut hasher: DefaultHasher = DefaultHasher::new(); - let cluster_hoprd_spec: ClusterHoprdSpec = cluster_hoprd.spec.clone(); - cluster_hoprd_spec.clone().hash(&mut hasher); - let hash: String = hasher.finish().to_string(); - let current_checksum = format!("checksum-{}",hash.to_string()); + let current_checksum = cluster_hoprd.get_checksum(); let previous_checksum: String = cluster_hoprd.status.as_ref().map_or("0".to_owned(), |status| status.checksum.to_owned()); // When the resource is created, does not have previous checksum and needs to be skip the modification because it's being handled already by the creation operation if previous_checksum.eq(&"0".to_owned()) || current_checksum.eq(&previous_checksum) { @@ -64,51 +66,57 @@ fn determine_action(cluster_hoprd: &ClusterHoprd) -> ClusterHoprdAction { }; } -async fn reconciler(cluster_hoprd: Arc, context: Arc) -> Result { +async fn reconciler( + cluster_hoprd: Arc, + context: Arc, +) -> Result { // Performs action as decided by the `determine_action` function. return match determine_action(&cluster_hoprd) { ClusterHoprdAction::Create => cluster_hoprd.create(context.clone()).await, ClusterHoprdAction::Modify => cluster_hoprd.modify(context.clone()).await, ClusterHoprdAction::Delete => cluster_hoprd.delete(context.clone()).await, - ClusterHoprdAction::Sync => cluster_hoprd.sync(context.clone()).await, + ClusterHoprdAction::Rescale => cluster_hoprd.rescale(context.clone()).await, // The resource is already in desired state, do nothing and re-check after 10 seconds - ClusterHoprdAction::NoOp => Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))), + ClusterHoprdAction::NoOp => Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))), }; } - /// Actions to be taken when a reconciliation fails - for whatever reason. /// Prints out the error to `stderr` and requeues the resource for another reconciliation after /// five seconds. /// /// # Arguments -/// - `hoprd`: The erroneous resource. +/// - `cluster_hoprd`: The erroneous resource. /// - `error`: A reference to the `kube::Error` that occurred during reconciliation. /// - `_context`: Unused argument. Context Data "injected" automatically by kube-rs. -pub fn on_error(hoprd: Arc, error: &Error, _context: Arc) -> Action { - error!("[ClusterHoprd] Reconciliation error:\n{:?}.\n{:?}", error, hoprd); +pub fn on_error( + cluster_hoprd: Arc, + error: &Error, + _context: Arc, +) -> Action { + error!("[ClusterHoprd] Reconciliation error:\n{:?}.\n{:?}",error, cluster_hoprd); Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY)) } - /// Initialize the controller pub async fn run(client: Client, context_data: Arc) { let owned_api: Api = Api::::all(client.clone()); let hoprd = Api::::all(client.clone()); - Controller::new(owned_api, Config::default()) .owns(hoprd, Config::default()) .shutdown_on_signal() .run(reconciler, on_error, context_data) .for_each(|reconciliation_result| async move { match reconciliation_result { - Ok(_hoprd_resource) => {} + Ok(_cluster_hoprd_resource) => {} Err(reconciliation_err) => { let err_string = reconciliation_err.to_string(); if !err_string.contains("that was not found in local store") { // https://github.com/kube-rs/kube/issues/712 - error!("[ClusterHoprd] Reconciliation error: {:?}", reconciliation_err) + error!("[ClusterHoprd] Reconciliation error: {:?}",reconciliation_err) } } } diff --git a/src/controller_hoprd.rs b/src/controller_hoprd.rs index 59fcad6..fe47c17 100644 --- a/src/controller_hoprd.rs +++ b/src/controller_hoprd.rs @@ -1,16 +1,30 @@ use futures::StreamExt; -use k8s_openapi::api::{apps::v1::Deployment, networking::v1::Ingress, core::v1::{Service, Secret}, batch::v1::Job}; +use k8s_openapi::api::{ + apps::v1::Deployment, + batch::v1::Job, + core::v1::{Secret, Service}, + networking::v1::Ingress, +}; use kube::{ - api::{Api}, + api::Api, client::Client, - runtime::{controller::{Action, Controller}, watcher::Config}, - Resource, Result + runtime::{ + controller::{Action, Controller}, + watcher::Config, + }, + Resource, Result, }; -use tracing::{error}; -use std::{sync::Arc, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; -use tokio::{ time::Duration}; +use std::sync::Arc; +use tokio::time::Duration; +use tracing::error; -use crate::{ constants::{self}, hoprd::{Hoprd, HoprdSpec}, servicemonitor::ServiceMonitor, context_data::ContextData, model::Error}; +use crate::{ + constants::{self}, + context_data::ContextData, + hoprd::{Hoprd, HoprdPhaseEnum}, + model::Error, + servicemonitor::ServiceMonitor, +}; /// Action to be taken upon an `Hoprd` resource during reconciliation enum HoprdAction { @@ -40,19 +54,22 @@ fn determine_action(hoprd: &Hoprd) -> HoprdAction { .map_or(true, |finalizers| finalizers.is_empty()) { HoprdAction::Create + } else if hoprd.status.as_ref().unwrap().phase == HoprdPhaseEnum::OutOfSync { + HoprdAction::Modify + } else if hoprd.status.as_ref().unwrap().phase == HoprdPhaseEnum::Deleting { + HoprdAction::NoOp } else { - let mut hasher: DefaultHasher = DefaultHasher::new(); - let hoprd_spec: HoprdSpec = hoprd.spec.clone(); - hoprd_spec.clone().hash(&mut hasher); - let hash: String = hasher.finish().to_string(); - let current_checksum = format!("checksum-{}",hash.to_string()); - let previous_checksum: String = hoprd.status.as_ref().map_or("0".to_owned(), |status| status.checksum.to_owned()); + let current_checksum = hoprd.get_checksum(); + let previous_checksum: String = hoprd + .status + .as_ref() + .map_or("0".to_owned(), |status| status.checksum.to_owned()); // When the resource is created, does not have previous checksum and needs to be skip the modification because it's being handled already by the creation operation if previous_checksum.eq(&"0".to_owned()) || current_checksum.eq(&previous_checksum) { HoprdAction::NoOp } else { HoprdAction::Modify - } + } }; } @@ -61,11 +78,12 @@ async fn reconciler(hoprd: Arc, context: Arc) -> Result hoprd.create(context.clone()).await, HoprdAction::Modify => hoprd.modify(context.clone()).await, HoprdAction::Delete => hoprd.delete(context.clone()).await, - HoprdAction::NoOp => Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))), + HoprdAction::NoOp => Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))), }; } - /// Actions to be taken when a reconciliation fails - for whatever reason. /// Prints out the error to `stderr` and requeues the resource for another reconciliation after /// five seconds. @@ -75,11 +93,10 @@ async fn reconciler(hoprd: Arc, context: Arc) -> Result, error: &Error, _context: Arc) -> Action { - error!("[ClusterHoprd] Reconciliation error:\n{:?}.\n{:?}", error, hoprd); + error!("[Hoprd] Reconciliation error:\n{:?}.\n{:?}",error, hoprd); Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY)) } - /// Initialize the controller pub async fn run(client: Client, context_data: Arc) { let owned_api: Api = Api::::all(client.clone()); @@ -106,7 +123,7 @@ pub async fn run(client: Client, context_data: Arc) { let err_string = reconciliation_err.to_string(); if !err_string.contains("that was not found in local store") { // https://github.com/kube-rs/kube/issues/712 - error!("[Hoprd] Reconciliation error: {:?}", reconciliation_err) + error!("[Hoprd] Reconciliation error: {:?}", reconciliation_err) } } } diff --git a/src/controller_identity.rs b/src/controller_identity.rs new file mode 100644 index 0000000..69bc906 --- /dev/null +++ b/src/controller_identity.rs @@ -0,0 +1,125 @@ +use futures::StreamExt; +use k8s_openapi::api::core::v1::PersistentVolumeClaim; +use kube::{ + api::Api, + client::Client, + runtime::{ + controller::{Action, Controller}, + watcher::Config, + }, + Resource, Result, +}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + sync::Arc, +}; +use tokio::time::Duration; +use tracing::error; + +use crate::{ + constants::{self}, + context_data::ContextData, + identity_hoprd::{IdentityHoprd, IdentityHoprdSpec}, + model::Error, +}; + +/// Action to be taken upon an `IdentityHoprd` resource during reconciliation +enum IdentityHoprdAction { + /// Validate the data on-chain + Create, + /// Modify the IdentityHoprd resource and validate data on-chain + Modify, + /// Delete the IdentityHoprd resource + Delete, + /// This `IdentityHoprd` resource is in desired state and requires no actions to be taken + NoOp, +} + +/// Resources arrives into reconciliation queue in a certain state. This function looks at +/// the state of given `IdentityHoprd` resource and decides which actions needs to be performed. +/// The finite set of possible actions is represented by the `IdentityHoprdAction` enum. +/// +/// # Arguments +/// - `identity_hoprd`: A reference to `IdentityHoprd` being reconciled to decide next action upon. +fn determine_action(identity_hoprd: &IdentityHoprd) -> IdentityHoprdAction { + return if identity_hoprd.meta().deletion_timestamp.is_some() { + IdentityHoprdAction::Delete + } else if identity_hoprd + .meta() + .finalizers + .as_ref() + .map_or(true, |finalizers| finalizers.is_empty()) + { + IdentityHoprdAction::Create + } else { + let mut hasher: DefaultHasher = DefaultHasher::new(); + let identity_spec: IdentityHoprdSpec = identity_hoprd.spec.clone(); + identity_spec.clone().hash(&mut hasher); + let hash: String = hasher.finish().to_string(); + let current_checksum = hash.to_string(); + let previous_checksum: String = identity_hoprd.status.as_ref().map_or("0".to_owned(), |status| status.checksum.to_owned()); + // When the resource is created, does not have previous checksum and needs to be skip the modification because it's being handled already by the creation operation + if previous_checksum.eq(&"0".to_owned()) || current_checksum.eq(&previous_checksum) { + IdentityHoprdAction::NoOp + } else { + IdentityHoprdAction::Modify + } + }; +} + +async fn reconciler(identity_hoprd: Arc,context: Arc) -> Result { + let mut identity_hoprd_cloned = identity_hoprd.clone(); + let identity_hoprd_mutable: &mut IdentityHoprd = Arc::::make_mut(&mut identity_hoprd_cloned); + // Performs action as decided by the `determine_action` function. + return match determine_action(&identity_hoprd_mutable) { + IdentityHoprdAction::Create => identity_hoprd_mutable.create(context.clone()).await, + IdentityHoprdAction::Modify => identity_hoprd_mutable.modify().await, + IdentityHoprdAction::Delete => identity_hoprd_mutable.delete(context.clone()).await, + // The resource is already in desired state, do nothing and re-check after 10 seconds + IdentityHoprdAction::NoOp => Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))), + }; +} + +/// Actions to be taken when a reconciliation fails - for whatever reason. +/// Prints out the error to `stderr` and requeues the resource for another reconciliation after +/// five seconds. +/// +/// # Arguments +/// - `identity_hoprd`: The erroneous resource. +/// - `error`: A reference to the `kube::Error` that occurred during reconciliation. +/// - `_context`: Unused argument. Context Data "injected" automatically by kube-rs. +pub fn on_error( + identity_hoprd: Arc, + error: &Error, + _context: Arc, +) -> Action { + error!("[IdentityHoprd] Reconciliation error:\n{:?}.\n{:?}",error, identity_hoprd); + Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY)) +} + +/// Initialize the controller +pub async fn run(client: Client, context_data: Arc) { + let owned_api: Api = Api::::all(client.clone()); + let pvc = Api::::all(client.clone()); + + Controller::new(owned_api, Config::default()) + .owns(pvc, Config::default()) + .shutdown_on_signal() + .run(reconciler, on_error, context_data) + .for_each(|reconciliation_result| async move { + match reconciliation_result { + Ok(_identity_hoprd_resource) => {} + Err(reconciliation_err) => { + let err_string = reconciliation_err.to_string(); + if !err_string.contains("that was not found in local store") { + // https://github.com/kube-rs/kube/issues/712 + error!("[IdentityHoprd] Reconciliation error: {:?}",reconciliation_err) + } + } + } + }) + .await; +} diff --git a/src/controller_identity_pool.rs b/src/controller_identity_pool.rs new file mode 100644 index 0000000..065f3d2 --- /dev/null +++ b/src/controller_identity_pool.rs @@ -0,0 +1,130 @@ +use futures::StreamExt; +use kube::{ + api::Api, + client::Client, + runtime::{ + controller::{Action, Controller}, + watcher::Config, + }, + Resource, Result, +}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + sync::Arc, +}; +use tokio::time::Duration; +use tracing::error; + +use crate::{ + constants::{self}, + context_data::ContextData, + identity_pool::{IdentityPool, IdentityPoolSpec}, + model::Error, + servicemonitor::ServiceMonitor, identity_hoprd::IdentityHoprd, +}; + +/// Action to be taken upon an `IdentityPool` resource during reconciliation +enum IdentityPoolAction { + /// Validate the data on-chain + Create, + /// Synchronize the identity pool + Sync, + /// Modify the IdentityPool resource and validate data on-chain + Modify, + /// Delete the IdentityPool resource + Delete, + /// This `IdentityPool` resource is in desired state and requires no actions to be taken + NoOp, +} + +/// Resources arrives into reconciliation queue in a certain state. This function looks at +/// the state of given `IdentityPool` resource and decides which actions needs to be performed. +/// The finite set of possible actions is represented by the `IdentityPoolAction` enum. +/// +/// # Arguments +/// - `identity_hoprd`: A reference to `IdentityPool` being reconciled to decide next action upon. +fn determine_action(identity_pool: &IdentityPool) -> IdentityPoolAction { + return if identity_pool.meta().deletion_timestamp.is_some() { + IdentityPoolAction::Delete + } else if identity_pool.meta().finalizers.as_ref().map_or(true, |finalizers| finalizers.is_empty()) + { + IdentityPoolAction::Create + } else if identity_pool.status.as_ref().unwrap().phase.eq(&crate::identity_pool::IdentityPoolPhaseEnum::OutOfSync) + { + IdentityPoolAction::Sync + } else { + let mut hasher: DefaultHasher = DefaultHasher::new(); + let identity_pool_spec: IdentityPoolSpec = identity_pool.spec.clone(); + identity_pool_spec.clone().hash(&mut hasher); + let hash: String = hasher.finish().to_string(); + let current_checksum = hash.to_string(); + let previous_checksum: String = identity_pool.status.as_ref() + .map_or("0".to_owned(), |status| status.checksum.to_owned()); + // When the resource is created, does not have previous checksum and needs to be skip the modification because it's being handled already by the creation operation + if previous_checksum.eq(&"0".to_owned()) || current_checksum.eq(&previous_checksum) { + IdentityPoolAction::NoOp + } else { + IdentityPoolAction::Modify + } + }; +} + +async fn reconciler(identity_pool: Arc, context: Arc) -> Result { + let mut identity_pool_cloned = identity_pool.clone(); + let identity_pool_mutable: &mut IdentityPool = Arc::::make_mut(&mut identity_pool_cloned); + // Performs action as decided by the `determine_action` function. + return match determine_action(identity_pool_mutable) { + IdentityPoolAction::Create => identity_pool_mutable.create(context.clone()).await, + IdentityPoolAction::Modify => identity_pool_mutable.modify(context.clone()).await, + IdentityPoolAction::Sync => identity_pool_mutable.sync(context.clone()).await, + IdentityPoolAction::Delete => identity_pool_mutable.delete(context.clone()).await, + // The resource is already in desired state, do nothing and re-check after 10 seconds + IdentityPoolAction::NoOp => Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))), + }; +} + +/// Actions to be taken when a reconciliation fails - for whatever reason. +/// Prints out the error to `stderr` and requeues the resource for another reconciliation after +/// five seconds. +/// +/// # Arguments +/// - `identity_hoprd`: The erroneous resource. +/// - `error`: A reference to the `kube::Error` that occurred during reconciliation. +/// - `_context`: Unused argument. Context Data "injected" automatically by kube-rs. +pub fn on_error( + identity_hoprd: Arc, + error: &Error, + _context: Arc, +) -> Action { + error!("[IdentityPool] Reconciliation error:\n{:?}.\n{:?}",error, identity_hoprd); + Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY)) +} + +/// Initialize the controller +pub async fn run(client: Client, context_data: Arc) { + let owned_api: Api = Api::::all(client.clone()); + let service_monitor = Api::::all(client.clone()); + let identity_hoprd = Api::::all(client.clone()); + + Controller::new(owned_api, Config::default()) + .owns(service_monitor, Config::default()) + .owns(identity_hoprd, Config::default()) + .shutdown_on_signal() + .run(reconciler, on_error, context_data) + .for_each(|reconciliation_result| async move { + match reconciliation_result { + Ok(_identity_hoprd_resource) => {} + Err(reconciliation_err) => { + let err_string = reconciliation_err.to_string(); + if !err_string.contains("that was not found in local store") { + // https://github.com/kube-rs/kube/issues/712 + error!("[IdentityPool] Reconciliation error: {:?}",reconciliation_err) + } + } + } + }) + .await; +} diff --git a/src/hoprd.rs b/src/hoprd.rs index 682dddd..a9f0313 100644 --- a/src/hoprd.rs +++ b/src/hoprd.rs @@ -1,27 +1,35 @@ -use std::collections::hash_map::DefaultHasher; -use std::sync::Arc; -use std::time::Duration; -use std::hash::{Hash, Hasher}; -use tracing::{debug, info, warn, error}; +use crate::cluster::ClusterHoprdPhaseEnum; use crate::hoprd_deployment_spec::HoprdDeploymentSpec; -use crate::{constants, hoprd_service_monitor, hoprd_ingress, hoprd_deployment, hoprd_secret, hoprd_service, utils, context_data::ContextData, cluster::ClusterHoprd}; -use crate::model::{ClusterHoprdStatusEnum, HoprdStatusEnum, EnablingFlag, HoprdSecret, Error}; -use crate::hoprd_persistence; +use crate::identity_hoprd::{IdentityHoprd, IdentityHoprdPhaseEnum}; +use crate::identity_pool::{IdentityPool, IdentityPoolPhaseEnum}; +use crate::model::Error; +use crate::{ + cluster::ClusterHoprd, constants, context_data::ContextData, hoprd_deployment, hoprd_ingress, + hoprd_service, utils, +}; use chrono::Utc; +use futures::{StreamExt, TryStreamExt}; +use k8s_openapi::api::apps::v1::Deployment; use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::api::WatchParams; +use kube::core::WatchEvent; +use kube::runtime::events::{Event, EventType, Recorder}; use kube::Resource; -use kube::runtime::events::{Recorder, EventType, Event}; -use schemars::JsonSchema; -use serde::{Serialize, Deserialize}; -use serde_json::{json}; use kube::{ api::{Api, Patch, PatchParams, ResourceExt}, client::Client, - runtime::{ - controller::{Action} - }, - CustomResource, Result + runtime::controller::Action, + CustomResource, Result, }; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::hash_map::DefaultHasher; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; +use std::time::Duration; +use tracing::{debug, error, info}; /// Struct corresponding to the Specification (`spec`) part of the `Hoprd` resource, directly /// reflects context of the `hoprds.hoprnet.org.yaml` file to be found in this repository. @@ -29,7 +37,7 @@ use kube::{ #[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Hash)] #[kube( group = "hoprnet.org", - version = "v1alpha", + version = "v1alpha2", kind = "Hoprd", plural = "hoprds", derive = "PartialEq", @@ -38,15 +46,12 @@ use kube::{ #[kube(status = "HoprdStatus", shortname = "hoprd")] #[serde(rename_all = "camelCase")] pub struct HoprdSpec { - pub config: Option, + pub identity_pool_name: String, + pub identity_name: Option, + pub version: String, + pub config: String, pub enabled: Option, - pub network: String, - pub ingress: Option, - pub monitoring: Option, pub deployment: Option, - pub secret: Option, - pub version: String, - } #[derive(Serialize, Deserialize, Debug, PartialEq, Default, Clone, JsonSchema, Hash)] @@ -64,120 +69,191 @@ pub struct HoprdConfig { pub heartbeat_threshold: Option, pub heartbeat_variance: Option, pub on_chain_confirmations: Option, - pub network_quality_threshold: Option + pub network_quality_threshold: Option, } /// The status object of `Hoprd` #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] pub struct HoprdStatus { - pub update_timestamp: i64, - pub status: HoprdStatusEnum, + pub update_timestamp: String, + pub phase: HoprdPhaseEnum, pub checksum: String, + pub identity_name: Option, } -impl Hoprd { +impl Default for HoprdStatus { + fn default() -> Self { + Self { + update_timestamp: Utc::now().to_rfc3339(), + phase: HoprdPhaseEnum::Initializing, + checksum: "init".to_owned(), + identity_name: None + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] +pub enum HoprdPhaseEnum { + // The node is not yet created + Initializing, + /// The node is running + Running, + /// The node is stopped + Stopped, + /// The node is not sync + OutOfSync, + /// Event that triggers when node is modified + Modified, + /// Event that triggers when node is being deleted + Deleting, + /// Event that triggers when node is deleted + Deleted, +} +impl Display for HoprdPhaseEnum { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + HoprdPhaseEnum::Initializing => write!(f, "Initializing"), + HoprdPhaseEnum::Running => write!(f, "Running"), + HoprdPhaseEnum::Stopped => write!(f, "Stopped"), + HoprdPhaseEnum::Modified => write!(f, "Modified"), + HoprdPhaseEnum::OutOfSync => write!(f, "OutOfSync"), + HoprdPhaseEnum::Deleting => write!(f, "Deleting"), + HoprdPhaseEnum::Deleted => write!(f, "Deleted"), + } + } +} + +impl Hoprd { // Creates all the related resources - pub async fn create(&self, context: Arc) -> Result { - let client: Client = context.client.clone(); + pub async fn create(&self, context_data: Arc) -> Result { + let client: Client = context_data.client.clone(); let hoprd_namespace: String = self.namespace().unwrap(); - let hoprd_name: String= self.name_any(); - self.create_event(context.clone(), HoprdStatusEnum::Initializing).await?; - self.update_status(context.clone(), HoprdStatusEnum::Initializing).await?; + let hoprd_name: String = self.name_any(); + self.create_event(context_data.clone(), HoprdPhaseEnum::Initializing).await?; + self.update_phase(context_data.clone(), HoprdPhaseEnum::Initializing, None).await?; info!("Starting to create Hoprd node {hoprd_name} in namespace {hoprd_namespace}"); let owner_reference: Option> = Some(vec![self.controller_owner_ref(&()).unwrap()]); - self.add_finalizer(client.clone(), &hoprd_name, &hoprd_namespace).await.unwrap(); - // Invoke creation of a Kubernetes resources - let mut secret_manager = hoprd_secret::SecretManager::new(context.clone(), self.clone()); - match secret_manager.create_secret().await { - Ok(secret) => { - hoprd_persistence::create_pvc(context.clone(), &self).await?; - let p2p_port = if self.spec.config.as_ref().unwrap().announce.is_some() { - hoprd_ingress::open_port(client.clone(), &hoprd_namespace, &hoprd_name, &context.config.ingress).await.unwrap() - } else { - constants::OPERATOR_P2P_MIN_PORT.parse::().unwrap() - }; - hoprd_deployment::create_deployment(client.clone(), &self, secret, p2p_port).await?; - hoprd_service::create_service(client.clone(), &hoprd_name, &hoprd_namespace, p2p_port, owner_reference.to_owned()).await?; - if self.spec.ingress.as_ref().unwrap_or(&EnablingFlag {enabled: constants::ENABLED}).enabled { - hoprd_ingress::create_ingress(client.clone(), &hoprd_name, &hoprd_namespace,&context.config.ingress, owner_reference.to_owned()).await?; - } - if self.spec.monitoring.as_ref().unwrap_or(&EnablingFlag {enabled: constants::ENABLED}).enabled { - hoprd_service_monitor::create_service_monitor(client.clone(), &hoprd_name, &hoprd_namespace, &secret_manager.hoprd_secret.unwrap(), owner_reference.to_owned()).await?; + if let Some(identity) = self.lock_identity(context_data.clone()).await? + { + self.add_finalizer(client.clone(), &hoprd_name, &hoprd_namespace.to_owned()).await.unwrap(); + let p2p_port = hoprd_ingress::open_port(client.clone(),&hoprd_namespace,&hoprd_name,&context_data.config.ingress).await.unwrap(); + hoprd_deployment::create_deployment(context_data.clone(),&self,&identity,p2p_port,context_data.config.ingress.to_owned()).await?; + match self.wait_deployment(client.clone()).await { + Ok(()) => { + hoprd_service::create_service(context_data.clone(), &hoprd_name, &hoprd_namespace, &self.spec.identity_pool_name, p2p_port, owner_reference.to_owned()).await?; + hoprd_ingress::create_ingress(context_data.clone(),&hoprd_name,&hoprd_namespace,&context_data.config.ingress,owner_reference.to_owned()).await?; + info!("Hoprd node {hoprd_name} in namespace {hoprd_namespace} has been successfully created"); + self.update_phase(context_data.clone(),HoprdPhaseEnum::Running,Some(identity.name_any())).await?; + }, + Err(error) => { + self.create_event(context_data.clone(), HoprdPhaseEnum::OutOfSync).await?; + self.update_phase(context_data.clone(), HoprdPhaseEnum::OutOfSync, None).await?; + return Err(error) } - info!("Hoprd node {hoprd_name} in namespace {hoprd_namespace} has been successfully created"); - Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) - }, - Err(error) => { - error!("Error creating the secret: {}", error); - Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) } - } - + } else { + self.create_event(context_data.clone(), HoprdPhaseEnum::OutOfSync).await?; + self.update_phase(context_data.clone(), HoprdPhaseEnum::OutOfSync, None).await?; + }; + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) } // Creates all the related resources - pub async fn modify(&self, context: Arc) -> Result { - let client: Client = context.client.clone(); + pub async fn modify(&self, context_data: Arc) -> Result { + let client: Client = context_data.client.clone(); let hoprd_namespace: String = self.namespace().unwrap(); - let hoprd_name: String= self.name_any(); - info!("Hoprd node {hoprd_name} in namespace {hoprd_namespace} has been successfully modified"); - self.create_event(context.clone(), HoprdStatusEnum::Reloading).await?; - self.update_status(context.clone(), HoprdStatusEnum::Reloading).await?; - let annotations = utils::get_resource_kinds(client.clone(), utils::ResourceType::Hoprd, utils::ResourceKind::Annotations, &hoprd_name, &hoprd_namespace).await; + let hoprd_name: String = self.name_any(); + let annotations = utils::get_resource_kinds(client.clone(),utils::ResourceType::Hoprd,utils::ResourceKind::Annotations,&hoprd_name,&hoprd_namespace).await; if annotations.contains_key(constants::ANNOTATION_LAST_CONFIGURATION) { let previous_hoprd_text: String = annotations.get_key_value(constants::ANNOTATION_LAST_CONFIGURATION).unwrap().1.parse().unwrap(); match serde_json::from_str::(&previous_hoprd_text) { - Ok(previous_hoprd) => { - self.check_inmutable_fields(&previous_hoprd.spec).unwrap(); - let secret_manager = hoprd_secret::SecretManager::new(context.clone(), self.clone()); - let secret = secret_manager.get_hoprd_secret().await.unwrap(); - if secret.is_some() { - let hoprd_secret = self.spec.secret.as_ref().unwrap_or(&HoprdSecret { secret_name: secret.unwrap().name_any(), ..HoprdSecret::default() }).to_owned(); - hoprd_deployment::modify_deployment(client.clone(), &hoprd_name.to_owned(), &hoprd_namespace.to_owned(), &self.spec.to_owned(), hoprd_secret).await?; - } else { - warn!("Hoprd node {hoprd_name} does not have a linked secret and is inconsistent"); - } - }, - Err(_err) => { - error!("Could not parse the last applied configuration of Hoprd node {hoprd_name}."); + Ok(previous_hoprd) => self.check_inmutable_fields(&previous_hoprd.spec).unwrap(), + Err(_err) => () + }; + }; + if let Some(identity) = self.get_identity(client.clone()).await? { + return self.apply_modification(context_data.clone(), &identity).await + } else { + self.create_event(context_data.clone(), HoprdPhaseEnum::OutOfSync).await?; + self.update_phase(context_data.clone(), HoprdPhaseEnum::OutOfSync, None).await?; + Err(Error::HoprdConfigError(format!("Hoprd node {hoprd_name} does not have a linked identity and is inconsistent"))) + } + } + + async fn apply_modification(&self, context_data: Arc, identity: &IdentityHoprd) -> Result { + let hoprd_namespace: String = self.namespace().unwrap(); + let hoprd_name: String = self.name_any(); + hoprd_deployment::modify_deployment(context_data.clone(), &hoprd_name.to_owned(),&hoprd_namespace.to_owned(),&self.spec.to_owned(),&identity).await?; + match self.wait_deployment(context_data.client.clone()).await { + Ok(()) => { + info!("Hoprd node {hoprd_name} in namespace {hoprd_namespace} has been successfully modified"); + self.create_event(context_data.clone(), HoprdPhaseEnum::Modified).await?; + if self.spec.enabled.unwrap_or(true) { + self.update_phase(context_data.clone(), HoprdPhaseEnum::Running, None).await?; + } else { + self.update_phase(context_data.clone(), HoprdPhaseEnum::Stopped, None).await?; } - } + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + }, + Err(error) => Err(error) } - Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) } // Deletes all the related resources - pub async fn delete(&self, context: Arc) -> Result { + pub async fn delete(&self, context_data: Arc) -> Result { let hoprd_name = self.name_any(); let hoprd_namespace = self.namespace().unwrap(); - let client: Client = context.client.clone(); - self.create_event(context.clone(), HoprdStatusEnum::Deleting).await?; + let client: Client = context_data.client.clone(); + self.create_event(context_data.clone(), HoprdPhaseEnum::Deleting).await?; + self.update_phase(context_data.clone(), HoprdPhaseEnum::Deleting, None).await?; info!("Starting to delete Hoprd node {hoprd_name} from namespace {hoprd_namespace}"); // Deletes any subresources related to this `Hoprd` resources. If and only if all subresources // are deleted, the finalizer is removed and Kubernetes is free to remove the `Hoprd` resource. - if self.spec.config.as_ref().unwrap().announce.is_some() { - hoprd_ingress::close_port(client.clone(), &hoprd_namespace, &hoprd_name, &context.config.ingress).await.unwrap(); - } - if self.spec.monitoring.as_ref().unwrap_or(&EnablingFlag {enabled: constants::ENABLED}).enabled { - hoprd_service_monitor::delete_service_monitor(client.clone(), &hoprd_name, &hoprd_namespace).await?; - } - if self.spec.ingress.as_ref().unwrap_or(&EnablingFlag {enabled: constants::ENABLED}).enabled { - hoprd_ingress::delete_ingress(client.clone(), &hoprd_name, &hoprd_namespace).await?; - } + hoprd_ingress::close_port(client.clone(), &hoprd_namespace, &hoprd_name, &context_data.config.ingress).await.unwrap(); + hoprd_ingress::delete_ingress(client.clone(), &hoprd_name, &hoprd_namespace).await?; hoprd_service::delete_service(client.clone(), &hoprd_name, &hoprd_namespace).await?; hoprd_deployment::delete_depoyment(client.clone(), &hoprd_name, &hoprd_namespace).await.unwrap(); - let secret_manager = hoprd_secret::SecretManager::new(context.clone(), self.clone()); - secret_manager.unlock_secret().await.unwrap(); + if let Some(identity) = self.get_identity(client.clone()).await.unwrap() { + identity.unlock(context_data.clone()).await?; + } // Once all the resources are successfully removed, remove the finalizer to make it possible // for Kubernetes to delete the `Hoprd` resource. - self.create_event(context.clone(), HoprdStatusEnum::Deleted).await?; + self.create_event(context_data.clone(), HoprdPhaseEnum::Deleted).await?; self.delete_finalizer(client.clone(), &hoprd_name, &hoprd_namespace).await?; info!("Hoprd node {hoprd_name} in namespace {hoprd_namespace} has been successfully deleted"); - self.notify_cluster(context.clone()).await.unwrap(); + self.notify_cluster(context_data.clone()).await.unwrap(); Ok(Action::await_change()) // Makes no sense to delete after a successful delete, as the resource is gone } + // Locks a given identity from a Hoprd node + async fn lock_identity(&self, context_data: Arc) -> Result, Error> { + let hoprd_name = Some(self.name_any()); + let identity_pool_name = self.spec.identity_pool_name.to_owned(); + let identity_name = self.spec.identity_name.to_owned(); + { + let mut context_state = context_data.state.write().await; + let identity_pool_option = context_state.get_identity_pool(&self.namespace().unwrap(), &identity_pool_name); + if identity_pool_option.is_some() { + let mut identity_pool_arc = identity_pool_option.unwrap(); + let identity_pool: &mut IdentityPool = Arc::::make_mut(&mut identity_pool_arc); + if let Some(identity) = identity_pool.get_ready_identity(context_data.clone(), identity_name).await? { + identity.update_phase(context_data.clone(), IdentityHoprdPhaseEnum::InUse, hoprd_name.clone()).await?; + identity_pool.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::Locked).await?; + context_state.update_identity_pool(identity_pool.to_owned()); + Ok(Some(identity)) + } else { + error!("Error locking the identity for node {}", hoprd_name.unwrap()); + Ok(None) + } + } else { + error!("Identity pool {} not exists in namespace {}", identity_pool_name, &self.namespace().unwrap()); + Ok(None) + } + } + } + /// Adds a finalizer record into an `Hoprd` kind of resource. If the finalizer already exists, /// this action has no effect. /// @@ -188,16 +264,16 @@ impl Hoprd { /// async fn add_finalizer(&self, client: Client, hoprd_name: &str, hoprd_namespace: &str) -> Result { let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); - let pp = PatchParams::default(); - let patch = json!({ + let patch = Patch::Merge(json!({ "metadata": { "finalizers": [constants::OPERATOR_FINALIZER] } - }); - match api.patch(&hoprd_name, &pp, &Patch::Merge(patch)).await { + })); + match api.patch(&hoprd_name, &PatchParams::default(), &patch).await + { Ok(hopr) => Ok(hopr), Err(error) => { - error!("Could not add finalizer on {hoprd_name}: {:?}", error); + error!("Could not add finalizer on Hoprd node {hoprd_name}: {:?}",error); return Err(Error::HoprdStatusError(format!("Could not add finalizer on {hoprd_name}.").to_owned())); } } @@ -213,170 +289,191 @@ impl Hoprd { /// async fn delete_finalizer(&self, client: Client, hoprd_name: &str, hoprd_namespace: &str) -> Result<(), Error> { let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); - let pp = PatchParams::default(); - let patch = json!({ + let patch = Patch::Merge(json!({ "metadata": { "finalizers": null } - }); + })); if let Some(_) = api.get_opt(&hoprd_name).await? { - match api.patch(&hoprd_name, &pp, &Patch::Merge(patch)).await { + match api.patch(&hoprd_name, &PatchParams::default(), &patch).await + { Ok(_hopr) => Ok(()), - Err(error) => { - Ok(error!("Could not delete finalizer on hoprd node {hoprd_name}: {:?}", error)) - } + Err(error) => Ok(error!("Could not delete finalizer on Hoprd node {hoprd_name}: {:?}", error)) } } else { - Ok(debug!("Hoprd node {hoprd_name} has already been deleted")) + Ok(debug!("Hoprd node {hoprd_name} already deleted")) } } - fn check_inmutable_fields(&self, previous_hoprd: &HoprdSpec) -> Result<(),Error> { - if ! self.spec.network.eq(&previous_hoprd.network) { - return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, network field cannot be changed on {}.", self.name_any()))); + fn check_inmutable_fields(&self, previous_hoprd: &HoprdSpec) -> Result<(), Error> { + if !self.spec.identity_pool_name.eq(&previous_hoprd.identity_pool_name) + { + return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, 'identity_pool_name' field cannot be changed on {}.", self.name_any()))); } - if ! self.spec.secret.eq(&previous_hoprd.secret) { - return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, secret field cannot be changed on {}.", self.name_any()))); + if previous_hoprd.identity_name.is_some() && self.spec.identity_name.is_some() && !self.spec.identity_name.clone().unwrap().eq(&previous_hoprd.identity_name.clone().unwrap()) { + return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, 'identity_name' field cannot be changed on {}.",self.name_any()))); } - if self.spec.config.as_ref().is_some() && self.spec.config.as_ref().unwrap().announce.is_some() && ! self.spec.config.as_ref().unwrap().announce.unwrap().eq(&previous_hoprd.config.as_ref().unwrap().announce.unwrap()) { - return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, announcement flag cannot be changed on {}.", self.name_any()))); + if previous_hoprd.identity_name.is_some() && self.spec.identity_name.is_none() { + return Err(Error::HoprdConfigError(format!("Hoprd configuration is invalid, 'identity_name' cannot be removed on {}.",self.name_any()))); } Ok(()) } - /// Creates an event for ClusterHoprd given the new HoprdStatusEnum - pub async fn create_event(&self, context: Arc, status: HoprdStatusEnum) -> Result<(), Error> { + // Creates an event for ClusterHoprd given the new HoprdStatusEnum + async fn create_event(&self, context: Arc, phase: HoprdPhaseEnum) -> Result<(), Error> { let client: Client = context.client.clone(); - - let ev: Event = match status { - HoprdStatusEnum::Initializing => Event { - type_: EventType::Normal, - reason: "Initializing".to_string(), - note: Some("Initializing Hoprd node".to_owned()), - action: "Starting the process of creating a new node".to_string(), - secondary: None, - }, - HoprdStatusEnum::Creating => Event { - type_: EventType::Normal, - reason: "Creating".to_string(), - note: Some("Creating Hoprd node repository and secrets".to_owned()), - action: "Node secrets are being created".to_string(), - secondary: None, - }, - HoprdStatusEnum::RegisteringInNetwork => Event { - type_: EventType::Normal, - reason: "RegisteringInNetwork".to_string(), - note: Some("Hoprd node created but not registered yet".to_owned()), - action: "Node is registering into the Network registry".to_string(), - secondary: None, - }, - HoprdStatusEnum::Funding => Event { - type_: EventType::Normal, - reason: "Funding".to_string(), - note: Some("Hoprd node created and registered but not funded yet".to_owned()), - action: "Node is being funded with mHopr and xDAI".to_string(), - secondary: None, - }, - HoprdStatusEnum::Stopped => Event { - type_: EventType::Normal, - reason: "Stopped".to_string(), - note: Some("Hoprd node is stopped".to_owned()), - action: "Node has stopped".to_string(), - secondary: None, - }, - HoprdStatusEnum::Running => Event { - type_: EventType::Normal, - reason: "Running".to_string(), - note: Some("Hoprd node is running".to_owned()), - action: "Node has started".to_string(), - secondary: None, - }, - HoprdStatusEnum::Reloading => Event { - type_: EventType::Normal, - reason: "Reloading".to_string(), - note: Some("Hoprd node configuration change detected".to_owned()), - action: "Node reconfigured".to_string(), - secondary: None, - }, - HoprdStatusEnum::Deleting => Event { - type_: EventType::Normal, - reason: "Deleting".to_string(), - note: Some("Hoprd node is being deleted".to_owned()), - action: "Node deletion started".to_string(), - secondary: None, - }, - HoprdStatusEnum::Deleted => Event { - type_: EventType::Normal, - reason: "Deleted".to_string(), - note: Some("Hoprd node is deleted".to_owned()), - action: "Node deletion finished".to_string(), - secondary: None, - }, - HoprdStatusEnum::OutOfSync => Event { - type_: EventType::Warning, - reason: "Out of sync".to_string(), - note: Some("Hoprd node is not sync".to_owned()), - action: "Node sync failed".to_string(), - secondary: None, - } + let ev: Event = match phase { + HoprdPhaseEnum::Initializing => Event { + type_: EventType::Normal, + reason: "Initializing".to_string(), + note: Some("Initializing Hoprd node".to_owned()), + action: "Starting the process of creating a new node".to_string(), + secondary: None, + }, + HoprdPhaseEnum::Running => Event { + type_: EventType::Normal, + reason: "Running".to_string(), + note: Some("Hoprd node is running".to_owned()), + action: "Node has started".to_string(), + secondary: None, + }, + HoprdPhaseEnum::Stopped => Event { + type_: EventType::Normal, + reason: "Stopped".to_string(), + note: Some("Hoprd node is stopped".to_owned()), + action: "Node has stopped".to_string(), + secondary: None, + }, + HoprdPhaseEnum::Modified => Event { + type_: EventType::Normal, + reason: "Modified".to_string(), + note: Some("Hoprd node configuration change detected".to_owned()), + action: "Node reconfigured".to_string(), + secondary: None, + }, + HoprdPhaseEnum::Deleting => Event { + type_: EventType::Normal, + reason: "Deleting".to_string(), + note: Some("Hoprd node is being deleted".to_owned()), + action: "Node deletion started".to_string(), + secondary: None, + }, + HoprdPhaseEnum::Deleted => Event { + type_: EventType::Normal, + reason: "Deleted".to_string(), + note: Some("Hoprd node is deleted".to_owned()), + action: "Node deletion finished".to_string(), + secondary: None, + }, + HoprdPhaseEnum::OutOfSync => Event { + type_: EventType::Warning, + reason: "Out of sync".to_string(), + note: Some("Hoprd node is not sync".to_owned()), + action: "Node sync failed".to_string(), + secondary: None, + }, }; - let recorder: Recorder = context.state.read().await.generate_hoprd_event(client.clone(), self); + let recorder: Recorder = context + .state + .read() + .await + .generate_hoprd_event(client.clone(), self); Ok(recorder.publish(ev).await?) - } - pub async fn update_status(&self, context: Arc, status: HoprdStatusEnum) -> Result<(), Error> { + async fn update_phase(&self, context: Arc, phase: HoprdPhaseEnum, identity_name: Option) -> Result<(), Error> { let client: Client = context.client.clone(); let hoprd_name = self.metadata.name.as_ref().unwrap().to_owned(); let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); - if status.eq(&HoprdStatusEnum::Deleting) || status.eq(&HoprdStatusEnum::Deleted) { + if phase.eq(&HoprdPhaseEnum::Deleting) || phase.eq(&HoprdPhaseEnum::Deleted) { api.get(&hoprd_name).await?; Ok(()) } else { - let mut hasher: DefaultHasher = DefaultHasher::new(); - self.spec.clone().hash(&mut hasher); - let hash: u64 = hasher.finish(); - let status = HoprdStatus { - update_timestamp: Utc::now().timestamp(), - status: status, - checksum: format!("checksum-{}",hash.to_string()) - }; - let pp = PatchParams::default(); - let patch = json!({ - "status": status - }); - match api.patch(&hoprd_name, &pp, &Patch::Merge(patch)).await { + let mut status = self.status.as_ref().unwrap_or(&HoprdStatus::default()).to_owned(); + status.update_timestamp = Utc::now().to_rfc3339(); + status.phase = phase; + status.checksum = self.get_checksum(); + if identity_name.is_some() { + status.identity_name = identity_name; + } + let patch = Patch::Merge(json!({ "status": status })); + match api.patch(&hoprd_name, &PatchParams::default(), &patch).await + { Ok(_) => Ok(()), - Err(error) => { - Ok(error!("Could not update status on node {hoprd_name}: {:?}", error)) - } + Err(error) => Ok(error!("Could not update status on node {hoprd_name}: {:?}",error)) } + } } - -} - - pub async fn notify_cluster(&self, context: Arc) -> Result<(), Error> { - let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); - let api: Api = Api::namespaced(context.client.clone(), &hoprd_namespace.to_owned()); + async fn notify_cluster(&self, context: Arc) -> Result<(), Error> { if let Some(owner_reference) = self.owner_references().to_owned().first() { + let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); + let api: Api = Api::namespaced(context.client.clone(), &hoprd_namespace.to_owned()); if let Some(cluster) = api.get_opt(&owner_reference.name).await? { - if cluster.to_owned().status.unwrap().status != ClusterHoprdStatusEnum::Deleting { - cluster.create_event(context.clone(), ClusterHoprdStatusEnum::OutOfSync, Some(self.name_any().to_owned())).await.unwrap(); - cluster.update_status(context.clone(), ClusterHoprdStatusEnum::OutOfSync).await.unwrap(); - info!("Notifying ClusterHoprd {} that Hoprd node {} is being deleted", &owner_reference.name, self.name_any().to_owned()) + let current_phase = cluster.to_owned().status.unwrap().phase; + if current_phase.ne(&ClusterHoprdPhaseEnum::Deleting) && current_phase.ne(&ClusterHoprdPhaseEnum::NotScaled) { + cluster.create_event(context.clone(), ClusterHoprdPhaseEnum::NodeDeleted, None).await.unwrap(); + cluster.update_phase(context.clone(), ClusterHoprdPhaseEnum::NodeDeleted).await.unwrap(); + info!("Notifying ClusterHoprd {} that hoprd node {} is being deleted", &owner_reference.name, self.name_any().to_owned()) } } else { - println!("[WARN] ClusterHoprd {} not found", &owner_reference.name); + debug!("ClusterHoprd {} not found", &owner_reference.name); } }; - Ok(()) } -} - + pub fn get_checksum(&self) -> String { + let mut hasher: DefaultHasher = DefaultHasher::new(); + self.spec.clone().hash(&mut hasher); + return hasher.finish().to_string(); + } + async fn get_identity(&self, client: Client) -> Result, Error> { + match &self.status { + Some(status) => { + let api: Api = Api::namespaced(client.clone(), &self.namespace().unwrap()); + return match status.to_owned().identity_name { + Some(identity_name) => Ok(api.get_opt(identity_name.as_str()).await.unwrap()), + None => Ok(None), + }; + } + None => Ok(None) + } + } + // Wait for the Hoprd deployment to be created + pub async fn wait_deployment(&self, client: Client) -> Result<(),Error> { + let lp = WatchParams::default().fields(&format!("metadata.name={}", self.name_any())).timeout(constants::OPERATOR_NODE_SYNC_TIMEOUT); + let deployment_api: Api = Api::namespaced(client.clone(), &self.namespace().unwrap()); + let mut stream = deployment_api.watch(&lp, "0").await?.boxed(); + while let Some(deployment) = stream.try_next().await? { + match deployment { + WatchEvent::Added(deployment) => { + if deployment.status.as_ref().unwrap().ready_replicas.unwrap_or(0).eq(&1) { + info!("Hoprd node {} deployment with uid {:?} is ready", self.name_any(), deployment.uid().unwrap()); + return Ok(()) + } + } + WatchEvent::Modified(deployment) => { + if deployment.status.as_ref().unwrap().ready_replicas.unwrap_or(0).eq(&1) { + info!("Hoprd node {} deployment with uid {:?} is ready", self.name_any(), deployment.uid().unwrap()); + return Ok(()) + } + } + WatchEvent::Deleted(_) => { + return Err(Error::ClusterHoprdSynchError("Deleted operation not expected".to_owned())) + } + WatchEvent::Bookmark(_) => { + return Err(Error::ClusterHoprdSynchError("Bookmark operation not expected".to_owned())) + } + WatchEvent::Error(_) => { + return Err(Error::ClusterHoprdSynchError("Error operation not expected".to_owned())) + } + } + } + Err(Error::ClusterHoprdSynchError("Timeout waiting for Hoprd node to be created".to_owned())) + } +} diff --git a/src/hoprd_deployment.rs b/src/hoprd_deployment.rs index ae018f1..4648ee9 100644 --- a/src/hoprd_deployment.rs +++ b/src/hoprd_deployment.rs @@ -1,24 +1,29 @@ +use crate::context_data::ContextData; +use crate::hoprd_deployment_spec::HoprdDeploymentSpec; +use crate::identity_hoprd::IdentityHoprd; +use crate::identity_pool::IdentityPool; +use crate::model::Error; +use crate::operator_config::IngressConfig; +use base64::{Engine as _, engine::general_purpose}; +use crate::{ + constants, + hoprd::{Hoprd, HoprdSpec}, + utils, +}; use k8s_openapi::api::apps::v1::{Deployment, DeploymentSpec, DeploymentStrategy}; use k8s_openapi::api::core::v1::{ - Container, ContainerPort, EnvVar, EnvVarSource, KeyToPath, - PodSpec, PodTemplateSpec, Probe, SecretKeySelector, SecretVolumeSource, - Volume, VolumeMount, Secret, PersistentVolumeClaimVolumeSource, ResourceRequirements, + Container, ContainerPort, EmptyDirVolumeSource, EnvVar, EnvVarSource, + PersistentVolumeClaimVolumeSource, PodSpec, PodTemplateSpec, ResourceRequirements, + SecretKeySelector, Volume, VolumeMount, }; -use tracing::{info}; use k8s_openapi::apimachinery::pkg::apis::meta::v1::{LabelSelector, OwnerReference}; -use kube::api::{DeleteParams, ObjectMeta, PostParams, Patch, PatchParams}; +use kube::api::{DeleteParams, ObjectMeta, Patch, PatchParams, PostParams}; use kube::runtime::wait::{await_condition, conditions}; -use kube::{Api, Client, ResourceExt, Resource}; +use kube::{Api, Client, Resource, ResourceExt}; use serde_json::json; -use std::collections::{BTreeMap}; -use crate::hoprd_deployment_spec::HoprdDeploymentSpec; -use crate::model::{Error}; -use crate::{ - constants, - hoprd::{ Hoprd, HoprdSpec}, - model::{HoprdSecret}, - utils, -}; +use std::collections::BTreeMap; +use std::sync::Arc; +use tracing::info; /// Creates a new deployment for running the hoprd node, /// @@ -26,21 +31,20 @@ use crate::{ /// - `client` - A Kubernetes client to create the deployment with. /// - `hoprd` - Details about the hoprd configuration node /// -pub async fn create_deployment(client: Client, hoprd: &Hoprd, secret: Secret, p2p_port: i32) -> Result { +pub async fn create_deployment(context_data: Arc, hoprd: &Hoprd, identity_hoprd: &IdentityHoprd, p2p_port: i32, ingress_config: IngressConfig) -> Result { let namespace: String = hoprd.namespace().unwrap(); - let name: String= hoprd.name_any(); + let name: String = hoprd.name_any(); let owner_references: Option> = Some(vec![hoprd.controller_owner_ref(&()).unwrap()]); - let hoprd_secret = hoprd.spec.secret.as_ref().unwrap_or(&HoprdSecret { secret_name: secret.name_any(), ..HoprdSecret::default() }).to_owned(); - let node_address = secret.labels().get(constants::LABEL_NODE_ADDRESS).unwrap().to_owned(); - let node_peer_id = secret.labels().get(constants::LABEL_NODE_PEER_ID).unwrap().to_owned(); - let node_network = secret.labels().get(constants::LABEL_NODE_NETWORK).unwrap().to_owned(); - + let identity_pool: IdentityPool = identity_hoprd.get_identity_pool(context_data.client.clone()).await.unwrap(); - let mut labels: BTreeMap = utils::common_lables(&name.to_owned()); - labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "node".to_owned()); - labels.insert(constants::LABEL_NODE_ADDRESS.to_owned(), node_address); - labels.insert(constants::LABEL_NODE_PEER_ID.to_owned(), node_peer_id); - labels.insert(constants::LABEL_NODE_NETWORK.to_owned(), node_network); + let mut labels: BTreeMap = utils::common_lables(context_data.config.instance.name.to_owned(), Some(name.to_owned()), Some("node".to_owned())); + labels.insert(constants::LABEL_NODE_NETWORK.to_owned(),identity_pool.spec.network.clone()); + labels.insert(constants::LABEL_KUBERNETES_IDENTITY_POOL.to_owned(), identity_pool.name_any()); + labels.insert(constants::LABEL_NODE_NATIVE_ADDRESS.to_owned(), identity_hoprd.spec.native_address.to_owned()); + labels.insert(constants::LABEL_NODE_PEER_ID.to_owned(), identity_hoprd.spec.peer_id.to_owned()); + labels.insert(constants::LABEL_NODE_SAFE_ADDRESS.to_owned(), identity_hoprd.spec.safe_address.to_owned()); + labels.insert(constants::LABEL_NODE_MODULE_ADDRESS.to_owned(),identity_hoprd.spec.module_address.to_owned()); + let hoprd_host = format!("{}:{}", ingress_config.public_ip.unwrap(), p2p_port); // Propagating ClusterHopd instance if hoprd.labels().contains_key(constants::LABEL_NODE_CLUSTER) { @@ -57,22 +61,42 @@ pub async fn create_deployment(client: Client, hoprd: &Hoprd, secret: Secret, p2 owner_references, ..ObjectMeta::default() }, - spec: Some(build_deployment_spec(labels, &hoprd.spec, hoprd_secret, &name, p2p_port).await), + spec: Some( + build_deployment_spec( + labels, + &hoprd.spec, + identity_pool, + identity_hoprd, + &hoprd_host, + ) + .await, + ), ..Deployment::default() }; // Create the deployment defined above - let api: Api = Api::namespaced(client.clone(), &namespace); + let api: Api = Api::namespaced(context_data.client.clone(), &namespace); api.create(&PostParams::default(), &deployment).await } -pub async fn build_deployment_spec(labels: BTreeMap, hoprd_spec: &HoprdSpec, hoprd_secret: HoprdSecret, pvc_name: &String, p2p_port: i32) -> DeploymentSpec{ +pub async fn build_deployment_spec(labels: BTreeMap, hoprd_spec: &HoprdSpec, identity_pool: IdentityPool, identity_hoprd: &IdentityHoprd, hoprd_host: &String) -> DeploymentSpec { let image = format!("{}/{}:{}", constants::HOPR_DOCKER_REGISTRY.to_owned(), constants::HOPR_DOCKER_IMAGE_NAME.to_owned(), &hoprd_spec.version.to_owned()); let replicas: i32 = if hoprd_spec.enabled.unwrap_or(true) { 1 } else { 0 }; - let resources: Option = Some(HoprdDeploymentSpec::get_resource_requirements(hoprd_spec.deployment.clone())); - let liveness_probe: Option = Some(HoprdDeploymentSpec::get_liveness_probe(hoprd_spec.deployment.clone())); - let readiness_probe: Option = Some(HoprdDeploymentSpec::get_readiness_probe(hoprd_spec.deployment.clone())); - let startup_probe: Option = Some(HoprdDeploymentSpec::get_startup_probe(hoprd_spec.deployment.clone())); + let resources: Option = Some( + HoprdDeploymentSpec::get_resource_requirements(hoprd_spec.deployment.clone()), + ); + // let liveness_probe: Option = Some(HoprdDeploymentSpec::get_liveness_probe( + // hoprd_spec.deployment.clone(), + // )); + // let readiness_probe: Option = Some(HoprdDeploymentSpec::get_readiness_probe( + // hoprd_spec.deployment.clone(), + // )); + // let startup_probe: Option = Some(HoprdDeploymentSpec::get_startup_probe( + // hoprd_spec.deployment.clone(), + // )); + let volume_mounts: Option> = build_volume_mounts(); + let port = hoprd_host.split(':').collect::>().get(1).unwrap().to_string().parse::().unwrap(); + let encoded_configuration = general_purpose::STANDARD.encode(hoprd_spec.config.to_string()); DeploymentSpec { replicas: Some(replicas), @@ -86,22 +110,43 @@ pub async fn build_deployment_spec(labels: BTreeMap, hoprd_spec: }, template: PodTemplateSpec { spec: Some(PodSpec { + init_containers: Some(vec![Container { + name: "init".to_owned(), + image: Some(image.to_owned()), + env: Some(vec![EnvVar { + name: constants::HOPRD_IDENTITY_FILE.to_owned(), + value: Some(identity_hoprd.spec.identity_file.to_owned()), + ..EnvVar::default() + }, + EnvVar { + name: constants::HOPRD_CONFIGURATION.to_owned(), + value: Some(encoded_configuration), + ..EnvVar::default() + }]), + command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), + args: Some(vec![format!("{} && {}", + "echo $HOPRD_IDENTITY_FILE | base64 -d > /app/hoprd-identity/.hopr-id", + "echo $HOPRD_CONFIGURATION | base64 -d > /app/hoprd-identity/config.yaml") + ]), + volume_mounts: volume_mounts.to_owned(), + ..Container::default() + }]), containers: vec![Container { name: "hoprd".to_owned(), image: Some(image), image_pull_policy: Some("Always".to_owned()), - ports: Some(build_ports(p2p_port).await), - env: Some(build_env_vars(&hoprd_spec, &hoprd_secret, p2p_port)), + ports: Some(build_ports(port)), + env: Some(build_env_vars(&identity_pool, &identity_hoprd, hoprd_host)), // command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), // args: Some(vec!["sleep 99999999".to_owned()]), - liveness_probe, - readiness_probe, - startup_probe, - volume_mounts: Some(build_volume_mounts().await), + // liveness_probe, + // readiness_probe, + // startup_probe, + volume_mounts, resources, ..Container::default() }], - volumes: Some(build_volumes(&hoprd_secret, &pvc_name).await), + volumes: Some(build_volumes(&identity_hoprd.name_any()).await), ..PodSpec::default() }), metadata: Some(ObjectMeta { @@ -113,22 +158,17 @@ pub async fn build_deployment_spec(labels: BTreeMap, hoprd_spec: } } -pub async fn modify_deployment(client: Client, deployment_name: &str, namespace: &str, hoprd_spec: &HoprdSpec, hoprd_secret: HoprdSecret) -> Result { - - let api: Api = Api::namespaced(client.clone(), namespace); - let p2p_port = api.get(deployment_name).await.unwrap() - .spec.unwrap() - .template.spec.unwrap() - .containers.first().as_ref().unwrap() - .ports.as_ref().unwrap() - .iter().find(|container_port| container_port.name.as_ref().unwrap().eq("p2p-tcp")).unwrap() - .container_port; - let mut labels: BTreeMap = utils::common_lables(&deployment_name.to_owned()); - labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "node".to_owned()); - let spec = build_deployment_spec(labels, hoprd_spec, hoprd_secret, &deployment_name.to_owned(), p2p_port).await; - let change_set =json!({ "spec": spec }); - let patch = &Patch::Merge(change_set); - api.patch(&deployment_name, &PatchParams::default(),patch).await +pub async fn modify_deployment(context_data: Arc, deployment_name: &str, namespace: &str, hoprd_spec: &HoprdSpec, identity_hoprd: &IdentityHoprd) -> Result { + let api: Api = Api::namespaced(context_data.client.clone(), namespace); + let deployment = api.get(deployment_name).await.unwrap(); + let hoprd_host = deployment.spec.clone().unwrap().template.spec.unwrap().containers.first().as_ref().unwrap() + .env.as_ref().unwrap().iter() + .find(|&env_var| env_var.name.eq(&constants::HOPRD_HOST.to_owned())).unwrap() + .value.as_ref().unwrap().to_owned(); + let identity_pool: IdentityPool = identity_hoprd.get_identity_pool(context_data.client.clone()).await.unwrap(); + let spec = build_deployment_spec(deployment.labels().to_owned(), hoprd_spec, identity_pool, identity_hoprd, &hoprd_host).await; + let patch = &Patch::Merge(json!({ "spec": spec })); + api.patch(&deployment_name, &PatchParams::default(), patch).await } /// Deletes an existing deployment. @@ -141,7 +181,7 @@ pub async fn modify_deployment(client: Client, deployment_name: &str, namespace: pub async fn delete_depoyment(client: Client, name: &str, namespace: &str) -> Result<(), Error> { let api: Api = Api::namespaced(client, namespace); if let Some(deployment) = api.get_opt(&name).await? { - let uid = deployment.metadata.uid.unwrap(); + let uid = deployment.metadata.uid.unwrap(); api.delete(name, &DeleteParams::default()).await?; await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)).await.unwrap(); Ok(info!("Deployment {name} successfully deleted")) @@ -151,7 +191,7 @@ pub async fn delete_depoyment(client: Client, name: &str, namespace: &str) -> Re } /// Builds the struct VolumeMount to be attached into the Container -async fn build_volume_mounts() -> Vec { +fn build_volume_mounts() -> Option> { let mut volume_mounts = Vec::with_capacity(2); volume_mounts.push(VolumeMount { name: "hoprd-identity".to_owned(), @@ -163,30 +203,18 @@ async fn build_volume_mounts() -> Vec { mount_path: "/app/hoprd-db".to_owned(), ..VolumeMount::default() }); - return volume_mounts; + return Some(volume_mounts); } /// Builds the struct Volume to be included as part of the PodSpec -/// +/// /// # Arguments /// - `secret` - Secret struct used to build the volume for HOPRD_IDENTITY path -async fn build_volumes(secret: &HoprdSecret, pvc_name: &String) -> Vec { +async fn build_volumes(pvc_name: &String) -> Vec { let mut volumes = Vec::with_capacity(2); volumes.push(Volume { name: "hoprd-identity".to_owned(), - secret: Some(SecretVolumeSource { - secret_name: Some(secret.secret_name.to_owned()), - items: Some(vec![KeyToPath { - key: secret - .identity_ref_key - .as_ref() - .unwrap_or(&"HOPRD_IDENTITY".to_owned()) - .to_owned(), - mode: Some(440), - path: ".hopr-id".to_owned(), - }]), - ..SecretVolumeSource::default() - }), + empty_dir: Some(EmptyDirVolumeSource::default()), ..Volume::default() }); @@ -194,7 +222,7 @@ async fn build_volumes(secret: &HoprdSecret, pvc_name: &String) -> Vec { name: "hoprd-db".to_owned(), persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource { claim_name: pvc_name.to_owned(), - read_only: Some(false) + read_only: Some(false), }), ..Volume::default() }); @@ -202,7 +230,7 @@ async fn build_volumes(secret: &HoprdSecret, pvc_name: &String) -> Vec { } /// Build struct ContainerPort -async fn build_ports(p2p_port: i32) -> Vec { +fn build_ports(p2p_port: i32) -> Vec { let mut container_ports = Vec::with_capacity(3); container_ports.push(ContainerPort { @@ -234,30 +262,30 @@ async fn build_ports(p2p_port: i32) -> Vec { ///Build struct environment variable /// -fn build_env_vars(hoprd_spec: &HoprdSpec, secret: &HoprdSecret, p2p_port: i32) -> Vec { - let mut env_vars = build_secret_env_var(secret); - env_vars.extend_from_slice(&build_crd_env_var(&hoprd_spec)); - env_vars.extend_from_slice(&build_default_env_var(p2p_port)); +fn build_env_vars( + identity_pool: &IdentityPool, + identity_hoprd: &IdentityHoprd, + hoprd_host: &String, +) -> Vec { + let mut env_vars = build_secret_env_var(identity_pool); + env_vars.extend_from_slice(&build_crd_env_var(identity_pool, identity_hoprd)); + env_vars.extend_from_slice(&build_default_env_var(hoprd_host)); return env_vars; } /// Build environment variables from secrets -/// +/// /// # Arguments /// - `secret` - Secret struct used to build HOPRD_PASSWORD and HOPRD_API_TOKEN -fn build_secret_env_var(secret: &HoprdSecret) -> Vec { +fn build_secret_env_var(identity_pool: &IdentityPool) -> Vec { let mut env_vars = Vec::with_capacity(2); env_vars.push(EnvVar { name: constants::HOPRD_PASSWORD.to_owned(), value_from: Some(EnvVarSource { secret_key_ref: Some(SecretKeySelector { - key: secret - .password_ref_key - .as_ref() - .unwrap_or(&constants::HOPRD_PASSWORD.to_owned()) - .to_string(), - name: Some(secret.secret_name.to_owned()), + key: constants::IDENTITY_POOL_IDENTITY_PASSWORD_REF_KEY.to_owned(), + name: Some(identity_pool.spec.secret_name.to_owned()), ..SecretKeySelector::default() }), ..EnvVarSource::default() @@ -269,12 +297,8 @@ fn build_secret_env_var(secret: &HoprdSecret) -> Vec { name: constants::HOPRD_API_TOKEN.to_owned(), value_from: Some(EnvVarSource { secret_key_ref: Some(SecretKeySelector { - key: secret - .api_token_ref_key - .as_ref() - .unwrap_or(&constants::HOPRD_API_TOKEN.to_owned()) - .to_string(), - name: Some(secret.secret_name.to_owned()), + key: constants::IDENTITY_POOL_API_TOKEN_REF_KEY.to_owned(), + name: Some(identity_pool.spec.secret_name.to_owned()), ..SecretKeySelector::default() }), ..EnvVarSource::default() @@ -288,127 +312,45 @@ fn build_secret_env_var(secret: &HoprdSecret) -> Vec { /// /// # Arguments /// - `hoprd_spec` - Details about the hoprd configuration node -fn build_crd_env_var(hoprd_spec: &HoprdSpec) -> Vec { +fn build_crd_env_var(identity_pool: &IdentityPool, identity_hoprd: &IdentityHoprd) -> Vec { let mut env_vars = Vec::with_capacity(1); + env_vars.push(EnvVar { - name: constants::HOPRD_NETWORK.to_owned(), - value: Some(hoprd_spec.network.to_owned()), + name: constants::HOPRD_CONFIGURATION_FILE_PATH.to_owned(), + value: Some("/app/hoprd-identity/config.yaml".to_owned()), ..EnvVar::default() }); - let config = hoprd_spec.config.to_owned().unwrap_or_default(); - - if config.announce.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_ANNOUNCE.to_owned(), - value: Some(config.announce.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.provider.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_PROVIDER.to_owned(), - value: Some(config.provider.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.default_strategy.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_DEFAULT_STRATEGY.to_owned(), - value: Some(config.default_strategy.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.max_auto_channels.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_MAX_AUTOCHANNELS.to_owned(), - value: Some(config.max_auto_channels.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.auto_redeem_tickets.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_AUTO_REDEEM_TICKETS.to_owned(), - value: Some(config.auto_redeem_tickets.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.check_unrealized_balance.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_CHECK_UNREALIZED_BALANCE.to_owned(), - value: Some(config.check_unrealized_balance.as_ref().unwrap().to_string(), - ), - ..EnvVar::default() - }); - } - - if config.allow_private_node_connections.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_ALLOW_PRIVATE_NODE_CONNECTIONS.to_owned(), - value: Some(config.allow_private_node_connections.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.test_announce_local_address.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_TEST_ANNOUNCE_LOCAL_ADDRESSES.to_owned(), - value: Some(config.test_announce_local_address.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.heartbeat_interval.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_HEARTBEAT_INTERVAL.to_owned(), - value: Some(config.heartbeat_interval.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } - - if config.heartbeat_threshold.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_HEARTBEAT_THRESHOLD.to_owned(), - value: Some(config.heartbeat_threshold.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } + env_vars.push(EnvVar { + name: constants::HOPRD_NETWORK.to_owned(), + value: Some(identity_pool.spec.network.to_owned()), + ..EnvVar::default() + }); - if config.heartbeat_variance.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_HEARTBEAT_VARIANCE.to_owned(), - value: Some(config.heartbeat_variance.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } + env_vars.push(EnvVar { + name: constants::HOPRD_SAFE_ADDRESS.to_owned(), + value: Some(identity_hoprd.spec.safe_address.to_owned()), + ..EnvVar::default() + }); - if config.on_chain_confirmations.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_ON_CHAIN_CONFIRMATIONS.to_owned(), - value: Some(config.on_chain_confirmations.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } + env_vars.push(EnvVar { + name: constants::HOPRD_MODULE_ADDRESS.to_owned(), + value: Some(identity_hoprd.spec.module_address.to_owned()), + ..EnvVar::default() + }); - if config.network_quality_threshold.is_some() { - env_vars.push(EnvVar { - name: constants::HOPRD_NETWORK_QUALITY_THRESHOLD.to_owned(), - value: Some(config.network_quality_threshold.as_ref().unwrap().to_string()), - ..EnvVar::default() - }); - } + env_vars.push(EnvVar { + name: constants::HOPRD_ANNOUNCE.to_owned(), + value: Some("true".to_owned()), + ..EnvVar::default() + }); return env_vars; } /// Build default environment variables /// -fn build_default_env_var(p2p_port: i32) -> Vec { +fn build_default_env_var(hoprd_host: &String) -> Vec { let mut env_vars = Vec::with_capacity(7); env_vars.push(EnvVar { name: "DEBUG".to_owned(), @@ -427,7 +369,7 @@ fn build_default_env_var(p2p_port: i32) -> Vec { }); env_vars.push(EnvVar { name: constants::HOPRD_HOST.to_owned(), - value: Some(format!("0.0.0.0:{}", p2p_port)), + value: Some(hoprd_host.to_owned()), ..EnvVar::default() }); env_vars.push(EnvVar { diff --git a/src/hoprd_deployment_spec.rs b/src/hoprd_deployment_spec.rs index ad57ec8..40f41c3 100644 --- a/src/hoprd_deployment_spec.rs +++ b/src/hoprd_deployment_spec.rs @@ -1,11 +1,13 @@ -use k8s_openapi::{api::core::v1::{ResourceRequirements, Probe, HTTPGetAction}, apimachinery::pkg::{api::resource::Quantity, util::intstr::IntOrString}}; +use k8s_openapi::{ + api::core::v1::{HTTPGetAction, Probe, ResourceRequirements}, + apimachinery::pkg::{api::resource::Quantity, util::intstr::IntOrString}, +}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap}; - +use std::collections::BTreeMap; /// Struct to define Pod resources types -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone, JsonSchema, Hash)] +#[derive(Serialize, Debug, Deserialize, PartialEq, Clone, JsonSchema, Hash)] pub struct HoprdDeploymentSpec { resources: Option, #[serde(rename(deserialize = "startupProbe"))] @@ -18,75 +20,95 @@ pub struct HoprdDeploymentSpec { impl Default for HoprdDeploymentSpec { fn default() -> Self { + let mut limits: BTreeMap = BTreeMap::new(); + let mut requests: BTreeMap = BTreeMap::new(); + limits.insert("cpu".to_owned(), Quantity("1500m".to_owned())); + limits.insert("memory".to_owned(), Quantity("3Gi".to_owned())); + requests.insert("cpu".to_owned(), Quantity("750m".to_owned())); + requests.insert("memory".to_owned(), Quantity("512Mi".to_owned())); + let resources_spec = serde_yaml::to_string(&ResourceRequirements { + requests: Some(requests), + limits: Some(limits), + }) + .unwrap(); - let mut limits: BTreeMap = BTreeMap::new(); - let mut requests: BTreeMap = BTreeMap::new(); - limits.insert("cpu".to_owned(), Quantity("1500m".to_owned())); - limits.insert("memory".to_owned(), Quantity("2Gi".to_owned())); - requests.insert("cpu".to_owned(), Quantity("750m".to_owned())); - requests.insert("memory".to_owned(), Quantity("256Mi".to_owned())); - let resources_spec = serde_yaml::to_string(&ResourceRequirements { - requests: Some(requests), - limits: Some(limits) - - }).unwrap(); - - let default_probe = Probe { - http_get: Some(HTTPGetAction { - path: Some("/healthcheck/v1/version".to_owned()), - port: IntOrString::Int(8080), - ..HTTPGetAction::default() - }), - failure_threshold: Some(6), - initial_delay_seconds: Some(30), - period_seconds: Some(20), - success_threshold: Some(1), - timeout_seconds: Some(5), - ..Probe::default() - }; - let default_probe_string = Some(serde_yaml::to_string(&default_probe).unwrap()); + let default_probe = Probe { + http_get: Some(HTTPGetAction { + path: Some("/healthcheck/v1/version".to_owned()), + port: IntOrString::Int(8080), + ..HTTPGetAction::default() + }), + failure_threshold: Some(6), + initial_delay_seconds: Some(30), + period_seconds: Some(20), + success_threshold: Some(1), + timeout_seconds: Some(5), + ..Probe::default() + }; + let default_probe_string = Some(serde_yaml::to_string(&default_probe).unwrap()); - Self { resources: Some(resources_spec), startup_probe: default_probe_string.clone(), liveness_probe: default_probe_string.clone(), readiness_probe: default_probe_string.clone() } + Self { + resources: Some(resources_spec), + startup_probe: default_probe_string.clone(), + liveness_probe: default_probe_string.clone(), + readiness_probe: default_probe_string.clone(), + } } } - impl HoprdDeploymentSpec { - - pub fn get_resource_requirements(hoprd_deployment_spec: Option) -> ResourceRequirements { + pub fn get_resource_requirements( + hoprd_deployment_spec: Option, + ) -> ResourceRequirements { let default_deployment_spec = HoprdDeploymentSpec::default(); - let hoprd_deployment_spec = hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); - let resource_requirements_string = hoprd_deployment_spec.resources.as_ref().unwrap_or(&default_deployment_spec.resources.as_ref().unwrap()); - let resource_requirements: ResourceRequirements = serde_yaml::from_str(resource_requirements_string).unwrap(); + let hoprd_deployment_spec = + hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); + let resource_requirements_string = hoprd_deployment_spec + .resources + .as_ref() + .unwrap_or(&default_deployment_spec.resources.as_ref().unwrap()); + let resource_requirements: ResourceRequirements = + serde_yaml::from_str(resource_requirements_string).unwrap(); resource_requirements } - pub fn get_liveness_probe(hoprd_deployment_spec: Option) -> Probe { - let default_deployment_spec = HoprdDeploymentSpec::default(); - let hoprd_deployment_spec = hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); - let liveness_probe_string = hoprd_deployment_spec.liveness_probe.as_ref().unwrap_or(&default_deployment_spec.liveness_probe.as_ref().unwrap()); - let liveness_probe: Probe = serde_yaml::from_str(liveness_probe_string).unwrap(); - liveness_probe - } - - pub fn get_startup_probe(hoprd_deployment_spec: Option) -> Probe { - let default_deployment_spec = HoprdDeploymentSpec::default(); - let hoprd_deployment_spec = hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); - let startup_probe_string = hoprd_deployment_spec.startup_probe.as_ref().unwrap_or(&default_deployment_spec.startup_probe.as_ref().unwrap()); - let startup_probe: Probe = serde_yaml::from_str(startup_probe_string).unwrap(); - startup_probe - } + // pub fn get_liveness_probe(hoprd_deployment_spec: Option) -> Probe { + // let default_deployment_spec = HoprdDeploymentSpec::default(); + // let hoprd_deployment_spec = + // hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); + // let liveness_probe_string = hoprd_deployment_spec + // .liveness_probe + // .as_ref() + // .unwrap_or(&default_deployment_spec.liveness_probe.as_ref().unwrap()); + // let liveness_probe: Probe = serde_yaml::from_str(liveness_probe_string).unwrap(); + // liveness_probe + // } - pub fn get_readiness_probe(hoprd_deployment_spec: Option) -> Probe { - let default_deployment_spec = HoprdDeploymentSpec::default(); - let hoprd_deployment_spec = hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); - let readiness_probe_string = hoprd_deployment_spec.readiness_probe.as_ref().unwrap_or(&default_deployment_spec.readiness_probe.as_ref().unwrap()); - let readiness_probe: Probe = serde_yaml::from_str(readiness_probe_string).unwrap(); - readiness_probe - } + // pub fn get_startup_probe(hoprd_deployment_spec: Option) -> Probe { + // let default_deployment_spec = HoprdDeploymentSpec::default(); + // let hoprd_deployment_spec = + // hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); + // let startup_probe_string = hoprd_deployment_spec + // .startup_probe + // .as_ref() + // .unwrap_or(&default_deployment_spec.startup_probe.as_ref().unwrap()); + // let startup_probe: Probe = serde_yaml::from_str(startup_probe_string).unwrap(); + // startup_probe + // } + // pub fn get_readiness_probe(hoprd_deployment_spec: Option) -> Probe { + // let default_deployment_spec = HoprdDeploymentSpec::default(); + // let hoprd_deployment_spec = + // hoprd_deployment_spec.unwrap_or(default_deployment_spec.clone()); + // let readiness_probe_string = hoprd_deployment_spec + // .readiness_probe + // .as_ref() + // .unwrap_or(&default_deployment_spec.readiness_probe.as_ref().unwrap()); + // let readiness_probe: Probe = serde_yaml::from_str(readiness_probe_string).unwrap(); + // readiness_probe + // } } -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone, JsonSchema, Hash)] +#[derive(Serialize, Debug, Deserialize, PartialEq, Clone, JsonSchema, Hash)] pub struct EnablingFlag { - pub enabled: bool + pub enabled: bool, } diff --git a/src/hoprd_ingress.rs b/src/hoprd_ingress.rs index 83ae505..a4b89d1 100644 --- a/src/hoprd_ingress.rs +++ b/src/hoprd_ingress.rs @@ -1,12 +1,27 @@ use json_patch::{PatchOperation, ReplaceOperation}; -use k8s_openapi::{api::{networking::v1::{Ingress, IngressSpec, IngressRule, HTTPIngressRuleValue, HTTPIngressPath, IngressBackend, IngressServiceBackend, ServiceBackendPort, IngressTLS}, core::v1::ConfigMap}, apimachinery::pkg::apis::meta::v1::OwnerReference, serde_value::Value}; -use kube::{Api, Client, Error, core::ObjectMeta, api::{PostParams, DeleteParams, PatchParams, Patch}, runtime::wait::{conditions, await_condition}}; +use k8s_openapi::{ + api::{ + core::v1::ConfigMap, + networking::v1::{ + HTTPIngressPath, HTTPIngressRuleValue, Ingress, IngressBackend, IngressRule, + IngressServiceBackend, IngressSpec, IngressTLS, ServiceBackendPort, + }, + }, + apimachinery::pkg::apis::meta::v1::OwnerReference, + serde_value::Value, +}; +use kube::{ + api::{DeleteParams, Patch, PatchParams, PostParams}, + core::ObjectMeta, + runtime::wait::{await_condition, conditions}, + Api, Client, Error, +}; use serde_json::json; -use std::{collections::{BTreeMap}}; -use tracing::{info, error}; +use std::{collections::BTreeMap, sync::Arc}; +use tracing::{error, info}; -use crate::{utils, operator_config::IngressConfig, constants}; -use crate::model::{Error as HoprError}; +use crate::model::Error as HoprError; +use crate::{constants, context_data::ContextData, operator_config::IngressConfig, utils}; /// Creates a new Ingress for accessing the hoprd node, /// @@ -16,43 +31,60 @@ use crate::model::{Error as HoprError}; /// - `namespace` - Namespace to create the Kubernetes Deployment in. /// - `ingress` - Ingress Details /// -pub async fn create_ingress(client: Client, service_name: &str, namespace: &str, ingress_config: &IngressConfig, owner_references: Option>) -> Result { - let labels: BTreeMap = utils::common_lables(&service_name.to_owned()); - let annotations: BTreeMap = ingress_config.annotations.as_ref().unwrap_or(&BTreeMap::new()).clone(); +pub async fn create_ingress( + context: Arc, + service_name: &str, + namespace: &str, + ingress_config: &IngressConfig, + owner_references: Option>, +) -> Result { + let labels: Option> = Some(utils::common_lables( + context.config.instance.name.to_owned(), + Some(service_name.to_owned()), + None, + )); + let annotations: BTreeMap = ingress_config + .annotations + .as_ref() + .unwrap_or(&BTreeMap::new()) + .clone(); - let hostname = format!("{}.{}.{}", service_name, namespace, ingress_config.dns_domain); + let hostname = format!( + "{}.{}.{}", + service_name, namespace, ingress_config.dns_domain + ); // Definition of the ingress let ingress: Ingress = Ingress { metadata: ObjectMeta { name: Some(service_name.to_owned()), namespace: Some(namespace.to_owned()), - labels: Some(labels.clone()), + labels, annotations: Some(annotations), owner_references, ..ObjectMeta::default() }, spec: Some(IngressSpec { ingress_class_name: Some(ingress_config.ingress_class_name.to_string()), - rules: Some(vec![IngressRule{ + rules: Some(vec![IngressRule { host: Some(hostname.to_owned()), - http: Some(HTTPIngressRuleValue{ + http: Some(HTTPIngressRuleValue { paths: vec![HTTPIngressPath { backend: IngressBackend { service: Some(IngressServiceBackend { name: service_name.to_owned(), - port: Some(ServiceBackendPort{ + port: Some(ServiceBackendPort { name: Some("api".to_owned()), ..ServiceBackendPort::default() - }) + }), }), ..IngressBackend::default() }, path_type: "ImplementationSpecific".to_string(), ..HTTPIngressPath::default() - }] + }], }), - ..IngressRule::default() + ..IngressRule::default() }]), tls: Some(vec![IngressTLS { hosts: Some(vec![hostname.to_owned()]), @@ -65,7 +97,7 @@ pub async fn create_ingress(client: Client, service_name: &str, namespace: &str, }; // Create the Ingress defined above - let api: Api = Api::namespaced(client, namespace); + let api: Api = Api::namespaced(context.client.clone(), namespace); api.create(&PostParams::default(), &ingress).await } @@ -81,87 +113,121 @@ pub async fn delete_ingress(client: Client, name: &str, namespace: &str) -> Resu if let Some(ingress) = api.get_opt(&name).await? { let uid = ingress.metadata.uid.unwrap(); api.delete(name, &DeleteParams::default()).await?; - await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)).await.unwrap(); + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); Ok(info!("Ingress {name} successfully deleted")) } else { - Ok(info!("Ingress {name} in namespace {namespace} about to delete not found")) + Ok(info!( + "Ingress {name} in namespace {namespace} about to delete not found" + )) } } - /// Creates a new Ingress for accessing the hoprd node, /// -pub async fn open_port(client: Client, service_namespace: &str, service_name: &str, ingress_config: &IngressConfig) -> Result { +pub async fn open_port( + client: Client, + service_namespace: &str, + service_name: &str, + ingress_config: &IngressConfig, +) -> Result { let namespace = ingress_config.namespace.as_ref().unwrap(); let api: Api = Api::namespaced(client.clone(), namespace); let port: i32 = get_port(client.clone(), ingress_config).await.unwrap(); let pp = PatchParams::default(); - let patch = json!({ - "data": { - port.to_string().to_owned() : format!("{}/{}:{}", service_namespace, service_name, port.to_owned()) - } - }); - match api.patch("ingress-nginx-tcp", &pp, &Patch::Merge(patch.clone())).await { - Ok(_) => {}, - Err(error) => { - error!("Could not open Nginx tcp port: {:?}", error); - return Err(HoprError::HoprdConfigError(format!("Could not open Nginx tcp port").to_owned())); - } + let patch = Patch::Merge(json!({ + "data": { + port.to_string().to_owned() : format!("{}/{}:{}", service_namespace, service_name, port.to_owned()) + } + })); + match api.patch("ingress-nginx-tcp", &pp, &patch.clone()).await { + Ok(_) => {} + Err(error) => { + error!("Could not open Nginx tcp port: {:?}", error); + return Err(HoprError::HoprdConfigError( + format!("Could not open Nginx tcp port").to_owned(), + )); + } }; - match api.patch("ingress-nginx-udp", &pp, &Patch::Merge(patch.clone())).await { - Ok(_) => {}, - Err(error) => { - error!("Could not open Nginx udp port: {:?}", error); - return Err(HoprError::HoprdConfigError(format!("Could not open Nginx udp port").to_owned())); - } + match api.patch("ingress-nginx-udp", &pp, &patch.clone()).await { + Ok(_) => {} + Err(error) => { + error!("Could not open Nginx udp port: {:?}", error); + return Err(HoprError::HoprdConfigError( + format!("Could not open Nginx udp port").to_owned(), + )); + } }; - info!("Nginx p2p port {port} for Hoprd node {service_name} have been opened"); + info!("Nginx p2p port {port} for Hoprd node {service_name} opened"); Ok(port) } -async fn get_port(client: Client, ingress_config: &IngressConfig) -> Result { +async fn get_port(client: Client, ingress_config: &IngressConfig) -> Result { let api: Api = Api::namespaced(client, &ingress_config.namespace.as_ref().unwrap()); if let Some(config_map) = api.get_opt("ingress-nginx-tcp").await? { let data = config_map.data.unwrap(); - let min_port = ingress_config.p2p_port_min.as_ref().unwrap().parse::().unwrap_or(constants::OPERATOR_P2P_MIN_PORT.parse::().unwrap()); - let max_port = ingress_config.p2p_port_max.as_ref().unwrap().parse::().unwrap_or(constants::OPERATOR_P2P_MAX_PORT.parse::().unwrap()); - let ports: Vec<&str> = data.keys() - .filter(|port| port.parse::().unwrap() >= min_port ) - .filter(|port| port.parse::().unwrap() <= max_port ) + let min_port = ingress_config + .p2p_port_min + .as_ref() + .unwrap() + .parse::() + .unwrap_or(constants::OPERATOR_P2P_MIN_PORT.parse::().unwrap()); + let max_port = ingress_config + .p2p_port_max + .as_ref() + .unwrap() + .parse::() + .unwrap_or(constants::OPERATOR_P2P_MAX_PORT.parse::().unwrap()); + let ports: Vec<&str> = data + .keys() + .filter(|port| port.parse::().unwrap() >= min_port) + .filter(|port| port.parse::().unwrap() <= max_port) .map(|x| x.as_str()) - .clone().collect::>(); + .clone() + .collect::>(); match find_next_port(ports, ingress_config.p2p_port_min.as_ref()).parse::() { Ok(port) => Ok(port), Err(error) => { error!("Could not parse port number: {:?}", error); - Err(HoprError::HoprdConfigError(format!("Could not parse port number").to_owned())) + Err(HoprError::HoprdConfigError( + format!("Could not parse port number").to_owned(), + )) } } } else { - Err(HoprError::HoprdConfigError(format!("Could not get new free port").to_owned())) + Err(HoprError::HoprdConfigError( + format!("Could not get new free port").to_owned(), + )) } - } /// Find the next port available -fn find_next_port(ports: Vec<&str>, min_port: Option<&String>) -> String { +fn find_next_port(ports: Vec<&str>, min_port: Option<&String>) -> String { if ports.is_empty() { - return min_port.unwrap_or(&constants::OPERATOR_P2P_MIN_PORT.to_owned()).to_owned(); + return min_port + .unwrap_or(&constants::OPERATOR_P2P_MIN_PORT.to_owned()) + .to_owned(); } if ports.len() == 1 { - return (ports[0].parse::().unwrap()+1).to_string() + return (ports[0].parse::().unwrap() + 1).to_string(); } for i in 1..ports.len() { - if ports[i].parse::().unwrap() - ports[i-1].parse::().unwrap() > 1 { - return (ports[i-1].parse::().unwrap()+1).to_string() + if ports[i].parse::().unwrap() - ports[i - 1].parse::().unwrap() > 1 { + return (ports[i - 1].parse::().unwrap() + 1).to_string(); } } - return (ports[ports.len()-1].parse::().unwrap()+1).to_string() + return (ports[ports.len() - 1].parse::().unwrap() + 1).to_string(); } /// Creates a new Ingress for accessing the hoprd node, /// -pub async fn close_port(client: Client, service_namespace: &str, service_name: &str, ingress_config: &IngressConfig) -> Result<(), HoprError> { +pub async fn close_port( + client: Client, + service_namespace: &str, + service_name: &str, + ingress_config: &IngressConfig, +) -> Result<(), HoprError> { let namespace = ingress_config.namespace.as_ref().unwrap(); let api: Api = Api::namespaced(client.clone(), namespace); let service_fqn = format!("{}/{}", service_namespace, service_name); @@ -169,73 +235,94 @@ pub async fn close_port(client: Client, service_namespace: &str, service_name: & // TCP let tcp_config_map = api.get("ingress-nginx-tcp").await.unwrap(); - let new_data = tcp_config_map.to_owned().data.unwrap_or(BTreeMap::new()).into_iter() - .filter(|entry| ! entry.1.contains(&service_fqn)) + let new_data = tcp_config_map + .to_owned() + .data + .unwrap_or(BTreeMap::new()) + .into_iter() + .filter(|entry| !entry.1.contains(&service_fqn)) .collect::>(); - let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation{ + let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation { path: "/data".to_owned(), - value: json!(new_data) + value: json!(new_data), })]); let patch: Patch<&Value> = Patch::Json::<&Value>(json_patch); - match api.patch(&tcp_config_map.metadata.name.unwrap(), pp, &patch).await { - Ok(_) => {}, - Err(error) => { - error!("Could not close Nginx tcp-port: {:?}", error); - return Err(HoprError::HoprdConfigError(format!("Could not close Nginx tcp-port").to_owned())); - } + match api + .patch(&tcp_config_map.metadata.name.unwrap(), pp, &patch) + .await + { + Ok(_) => {} + Err(error) => { + error!("Could not close Nginx tcp-port: {:?}", error); + return Err(HoprError::HoprdConfigError( + format!("Could not close Nginx tcp-port").to_owned(), + )); + } }; // UDP let udp_config_map = api.get("ingress-nginx-udp").await.unwrap(); - let new_data = udp_config_map.to_owned().data.unwrap_or(BTreeMap::new()).into_iter() - .filter(|entry| ! entry.1.contains(&service_fqn)) + let new_data = udp_config_map + .to_owned() + .data + .unwrap_or(BTreeMap::new()) + .into_iter() + .filter(|entry| !entry.1.contains(&service_fqn)) .collect::>(); - let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation{ + let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation { path: "/data".to_owned(), - value: json!(new_data) + value: json!(new_data), })]); let patch: Patch<&Value> = Patch::Json::<&Value>(json_patch); - match api.patch(&udp_config_map.metadata.name.unwrap(), pp, &patch).await { - Ok(_) => {}, - Err(error) => { - error!("Could not close Nginx udp-port: {:?}", error); - return Err(HoprError::HoprdConfigError(format!("Could not close Nginx udp-port").to_owned())); - } + match api + .patch(&udp_config_map.metadata.name.unwrap(), pp, &patch) + .await + { + Ok(_) => {} + Err(error) => { + error!("Could not close Nginx udp-port: {:?}", error); + return Err(HoprError::HoprdConfigError( + format!("Could not close Nginx udp-port").to_owned(), + )); + } }; info!("Nginx p2p port for Hoprd node {service_name} have been closed"); Ok(()) } - #[cfg(test)] mod tests { use super::*; #[test] fn test_find_next_port_empty() { - let gap_in_middle= vec![]; - assert_eq!(find_next_port(gap_in_middle, None), constants::OPERATOR_P2P_MIN_PORT.to_string()); + let gap_in_middle = vec![]; + assert_eq!( + find_next_port(gap_in_middle, None), + constants::OPERATOR_P2P_MIN_PORT.to_string() + ); } #[test] fn test_find_next_port_first() { let min_port = constants::OPERATOR_P2P_MIN_PORT.to_string(); - let first_port= vec![min_port.as_str()]; - assert_eq!(find_next_port(first_port, None), (constants::OPERATOR_P2P_MIN_PORT.parse::().unwrap() + 1).to_string()); + let first_port = vec![min_port.as_str()]; + assert_eq!( + find_next_port(first_port, None), + (constants::OPERATOR_P2P_MIN_PORT.parse::().unwrap() + 1).to_string() + ); } #[test] fn test_find_next_port_gap_in_middle() { - let gap_in_middle= vec!["9000","9001","9003","9004"]; + let gap_in_middle = vec!["9000", "9001", "9003", "9004"]; assert_eq!(find_next_port(gap_in_middle, None), "9002"); } #[test] fn test_find_next_port_last() { - let last= vec!["9000","9001","9002","9003"]; + let last = vec!["9000", "9001", "9002", "9003"]; assert_eq!(find_next_port(last, None), "9004"); } - - -} \ No newline at end of file +} diff --git a/src/hoprd_jobs.rs b/src/hoprd_jobs.rs deleted file mode 100644 index f60dbe3..0000000 --- a/src/hoprd_jobs.rs +++ /dev/null @@ -1,382 +0,0 @@ -use k8s_openapi::api::batch::v1::{Job, JobSpec}; -use k8s_openapi::api::core::v1::{ - Container, EnvVar, EnvVarSource, KeyToPath, - PodSpec, PodTemplateSpec, SecretKeySelector, SecretVolumeSource, - Volume, VolumeMount, ConfigMapVolumeSource, EmptyDirVolumeSource -}; -use tracing::{info}; -use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; -use kube::{ResourceExt}; -use kube::{Api, Client, runtime::wait::{await_condition, conditions}}; -use kube::api::{ObjectMeta, PostParams}; -use std::collections::{BTreeMap}; -use crate::hoprd::Hoprd; -use crate::hoprd_deployment_spec::HoprdDeploymentSpec; -use crate::model::{HoprdSecret, Error}; -use crate::operator_config::{OperatorConfig}; -use crate::{ - constants, - utils, -}; -use rand::{distributions::Alphanumeric, Rng}; - - -pub struct HoprdJob { - client: Client, - config: OperatorConfig, - hoprd: Hoprd - -} - - -impl HoprdJob { - - pub fn new(client: Client,config: OperatorConfig, hoprd: Hoprd) -> Self { - Self { client, config, hoprd } - } - - /// Creates a new Job for creating a hoprd node - /// - /// # Arguments - /// - `hoprd` - Hoprd node - /// - `owner_references` - Secret reference that owns this job execution - /// - pub async fn execute_job_create_node(&self, hoprd_secret: &HoprdSecret, owner_references: Option>) -> Result<(), Error> { - let hoprd_name = &self.hoprd.name_any(); - let random_string: String = rand::thread_rng().sample_iter(&Alphanumeric).take(5).map(char::from).collect(); - let job_name: String = format!("job-create-{}-{}",&hoprd_name.to_owned(),&random_string.to_ascii_lowercase()); - let namespace: String = self.config.instance.namespace.clone(); - let mut labels: BTreeMap = utils::common_lables(&hoprd_name.to_owned()); - labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "create-node".to_owned()); - - let create_node_args: Vec = vec!["/app/scripts/create-identity.sh".to_owned()]; - let create_secret_args: Vec = vec!["/app/scripts/create-secret.sh".to_owned()]; - let mut env_vars: Vec = self.build_env_vars(&hoprd_secret, &true).await; - env_vars.push(EnvVar { - name: constants::SECRET_NAME.to_owned(), - value: Some(hoprd_secret.secret_name.to_owned()), - ..EnvVar::default() - }); - let volume_mounts: Vec = self.build_volume_mounts(&true).await; - let volumes: Vec = self.build_volumes(hoprd_secret, &true).await; - // Definition of the Job - let create_node_job: Job = Job { - metadata: ObjectMeta { - name: Some(job_name.to_owned()), - namespace: Some(namespace), - owner_references, - labels: Some(labels.clone()), - ..ObjectMeta::default() - }, - spec: Some(JobSpec { - parallelism: Some(1), - completions: Some(1), - backoff_limit: Some(1), - active_deadline_seconds: Some(constants::OPERATOR_JOB_TIMEOUT.try_into().unwrap()), - template: PodTemplateSpec { - spec: Some(PodSpec { - init_containers: Some(vec![Container { - name: "hopli".to_owned(), - image: Some(self.config.hopli_image.to_owned()), - image_pull_policy: Some("Always".to_owned()), - command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), - args: Some(create_node_args), - env: Some(env_vars.to_owned()), - volume_mounts: Some(volume_mounts.to_owned()), - resources: Some(HoprdDeploymentSpec::get_resource_requirements(None)), - ..Container::default() - }]), - containers: vec![Container { - name: "kubectl".to_owned(), - image: Some("registry.hub.docker.com/bitnami/kubectl:1.24".to_owned()), - image_pull_policy: Some("Always".to_owned()), - command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), - args: Some(create_secret_args), - env: Some(env_vars), - volume_mounts: Some(volume_mounts), - resources:Some(HoprdDeploymentSpec::get_resource_requirements(None)), - ..Container::default() - }], - service_account_name: Some(self.config.instance.name.to_owned()), - volumes: Some(volumes), - restart_policy: Some("Never".to_owned()), - ..PodSpec::default() - }), - metadata: Some(ObjectMeta { - labels: Some(labels), - ..ObjectMeta::default() - }), - }, - ..JobSpec::default() - }), - ..Job::default() - }; - - // Create the Job defined above - info!("Job {} started", &job_name.to_owned()); - let api: Api = Api::namespaced(self.client.clone(), &self.config.instance.namespace); - api.create(&PostParams::default(), &create_node_job).await.unwrap(); - let job_completed = await_condition(api, &job_name, conditions::is_job_completed()); - match tokio::time::timeout(std::time::Duration::from_secs(constants::OPERATOR_JOB_TIMEOUT), job_completed).await { - Ok(_) => Ok(info!("Job {} completed successfully", &job_name.to_owned())), - Err(_error) => { - Err(Error::JobExecutionError(format!(" Job execution for {} failed", &job_name.to_owned()).to_owned())) - } - } - } - - /// Creates a new Job for registering hoprd node in Network Registry - /// - /// # Arguments - /// - `hoprd` - Hoprd node - /// - `owner_references` - Secret reference that owns this job execution - /// - pub async fn execute_job_registering_node(&self, hoprd_secret: &HoprdSecret, owner_references: Option>) -> Result<(), Error> { - let hoprd_name = &self.hoprd.name_any(); - let random_string: String = rand::thread_rng().sample_iter(&Alphanumeric).take(5).map(char::from).collect(); - let job_name: String = format!("job-register-{}-{}",&hoprd_name.to_owned(),&random_string.to_ascii_lowercase()); - let namespace: String = self.config.instance.namespace.to_owned(); - let mut labels: BTreeMap = utils::common_lables(&hoprd_name.to_owned()); - labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "registe-node".to_owned()); - let command_args = vec!["/app/scripts/register-node.sh".to_owned()]; - let env_vars: Vec = self.build_env_vars(&hoprd_secret, &false).await; - - let volume_mounts: Vec = self.build_volume_mounts(&false).await; - let volumes: Vec = self.build_volumes(hoprd_secret, &false).await; - // Definition of the Job - let registering_job: Job = self.build_job(job_name.to_owned(), namespace, owner_references, self.config.hopli_image.to_owned(), labels, command_args, env_vars, volume_mounts, volumes); - - // Create the Job defined above - info!("Job {} started", &job_name.to_owned()); - let api: Api = Api::namespaced(self.client.clone(), &self.config.instance.namespace.to_owned()); - api.create(&PostParams::default(), ®istering_job).await.unwrap(); - let job_completed = await_condition(api, &job_name, conditions::is_job_completed()); - match tokio::time::timeout(std::time::Duration::from_secs(constants::OPERATOR_JOB_TIMEOUT), job_completed).await { - Ok(_) => Ok(info!("Job {} completed successfully", &job_name.to_owned())), - Err(_error) => { - Err(Error::JobExecutionError(format!(" Job execution for {} failed", &job_name.to_owned()).to_owned())) - } - } - } - - /// Creates a new Job for funding hoprd node - /// - /// # Arguments - /// - `hoprd` - Hoprd node - /// - `owner_references` - Secret reference that owns this job execution - /// - pub async fn execute_job_funding_node(&self, hoprd_secret: &HoprdSecret, owner_references: Option>) -> Result<(), Error> { - let hoprd_name = &self.hoprd.name_any(); - let random_string: String = rand::thread_rng().sample_iter(&Alphanumeric).take(5).map(char::from).collect(); - let job_name: String = format!("job-fund-{}-{}",&hoprd_name.to_owned(),&random_string.to_ascii_lowercase()); - let namespace: String = self.config.instance.namespace.to_owned(); - let mut labels: BTreeMap = utils::common_lables(&hoprd_name.to_owned()); - labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "fund-node".to_owned()); - let command_args = vec!["/app/scripts/fund-node.sh".to_owned()]; - let env_vars: Vec = self.build_env_vars(&hoprd_secret, &false).await; - - - let volume_mounts: Vec = self.build_volume_mounts(&false).await; - let volumes: Vec = self.build_volumes(hoprd_secret, &false).await; - // Definition of the Job - let funding_job: Job = self.build_job(job_name.to_owned(), namespace, owner_references, self.config.hopli_image.to_owned(), labels, command_args, env_vars, volume_mounts, volumes); - - // Create the Job defined above - info!("Job {} started", &job_name.to_owned()); - let api: Api = Api::namespaced(self.client.clone(), &self.config.instance.namespace.to_owned()); - api.create(&PostParams::default(), &funding_job).await.unwrap(); - let job_completed = await_condition(api, &job_name, conditions::is_job_completed()); - match tokio::time::timeout(std::time::Duration::from_secs(constants::OPERATOR_JOB_TIMEOUT), job_completed).await { - Ok(_) => Ok(info!("Job {} completed successfully", &job_name.to_owned())), - Err(_error) => { - Err(Error::JobExecutionError(format!(" Job execution for {} failed", &job_name.to_owned()).to_owned())) - } - } - } - - /// Builds the Job Spec which is similar to all jobs - fn build_job(&self, job_name: String, namespace: String, owner_references: Option>, image: String, labels: BTreeMap, command_args: Vec, env_vars: Vec, volume_mounts: Vec, volumes: Vec) -> Job { - Job { - metadata: ObjectMeta { - name: Some(job_name), - namespace: Some(namespace), - labels: Some(labels.clone()), - owner_references: owner_references.to_owned(), - ..ObjectMeta::default() - }, - spec: Some(JobSpec { - parallelism: Some(1), - completions: Some(1), - backoff_limit: Some(1), - active_deadline_seconds: Some(constants::OPERATOR_JOB_TIMEOUT.try_into().unwrap()), - template: PodTemplateSpec { - spec: Some(PodSpec { - containers: vec![Container { - name: "hopli".to_owned(), - image: Some(image), - image_pull_policy: Some("Always".to_owned()), - command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), - args: Some(command_args), - env: Some(env_vars), - volume_mounts: Some(volume_mounts), - resources: Some(HoprdDeploymentSpec::get_resource_requirements(None)), - ..Container::default() - }], - volumes: Some(volumes), - restart_policy: Some("Never".to_owned()), - ..PodSpec::default() - }), - metadata: Some(ObjectMeta { - labels: Some(labels), - ..ObjectMeta::default() - }), - }, - ..JobSpec::default() - }), - ..Job::default() - } - - } - - /// Builds the struct VolumeMount to be attached into the Container - async fn build_volume_mounts(&self, is_create_node_job: &bool) -> Vec { - let mut volume_mounts = Vec::with_capacity(2); - if is_create_node_job.to_owned() { - volume_mounts.push(VolumeMount { - name: "hopr-repo-volume".to_owned(), - mount_path: "/app/node_secrets".to_owned(), - ..VolumeMount::default() - }); - } else { - volume_mounts.push(VolumeMount { - name: "hoprd-identity".to_owned(), - mount_path: "/app/hoprd-identity".to_owned(), - ..VolumeMount::default() - }); - } - volume_mounts.push(VolumeMount { - name: "hopr-script-volume".to_owned(), - mount_path: "/app/scripts".to_owned(), - ..VolumeMount::default() - }); - return volume_mounts; - } - - /// Builds the struct Volume to be included as part of the PodSpec - async fn build_volumes(&self, secret: &HoprdSecret, is_create_node_job: &bool) -> Vec { - let mut volumes = Vec::with_capacity(2); - - if is_create_node_job.to_owned() { - - volumes.push(Volume { - name: "hopr-repo-volume".to_owned(), - empty_dir: Some(EmptyDirVolumeSource::default()), - ..Volume::default() - }); - } else { - volumes.push(Volume { - name: "hoprd-identity".to_owned(), - secret: Some(SecretVolumeSource { - secret_name: Some(secret.secret_name.to_owned()), - items: Some(vec![KeyToPath { - key: secret - .identity_ref_key - .as_ref() - .unwrap_or(&"HOPRD_IDENTITY".to_owned()) - .to_owned(), - mode: Some(440), - path: ".hopr-id".to_owned(), - }]), - ..SecretVolumeSource::default() - }), - ..Volume::default() - }); - } - let configmap_name = format!("{}-scripts", self.config.instance.name); - volumes.push(Volume { - name: "hopr-script-volume".to_owned(), - config_map: Some(ConfigMapVolumeSource { - name: Some(configmap_name.to_owned()), - default_mode: Some(0o550), - ..ConfigMapVolumeSource::default() - }), - ..Volume::default() - }); - return volumes; - } - - ///Build Job environment variables - async fn build_env_vars(&self, hoprd_secret: &HoprdSecret, is_create_node_job: &bool) -> Vec { - let mut env_vars = Vec::with_capacity(2); - if ! is_create_node_job { - env_vars.push(EnvVar { - name: "IDENTITY_PASSWORD".to_owned(), - value_from: Some(EnvVarSource { - secret_key_ref: Some(SecretKeySelector { - key: hoprd_secret - .password_ref_key - .as_ref() - .unwrap_or(&constants::HOPRD_PASSWORD.to_owned()) - .to_string(), - name: Some(hoprd_secret.secret_name.to_owned()), - ..SecretKeySelector::default() - }), - ..EnvVarSource::default() - }), - ..EnvVar::default() - }); - let labels = utils::get_resource_kinds(self.client.clone(), utils::ResourceType::Secret, utils::ResourceKind::Labels, &hoprd_secret.secret_name.to_owned(), &self.config.instance.namespace.to_owned()).await; - if labels.contains_key(constants::LABEL_NODE_ADDRESS) { - let node_address: String = labels.get_key_value(constants::LABEL_NODE_ADDRESS).unwrap().1.parse().unwrap(); - env_vars.push(EnvVar { - name: constants::HOPRD_ADDRESS.to_owned(), - value: Some(node_address.to_owned()), - ..EnvVar::default() - }); - } - - if labels.contains_key(constants::LABEL_NODE_PEER_ID) { - let node_peer_id: String = labels.get_key_value(constants::LABEL_NODE_PEER_ID).unwrap().1.parse().unwrap(); - env_vars.push(EnvVar { - name: constants::HOPRD_PEER_ID.to_owned(), - value: Some(node_peer_id.to_owned()), - ..EnvVar::default() - }); - } - } else { - env_vars.push(EnvVar { - name: constants::OPERATOR_INSTANCE_NAMESPACE.to_owned(), - value: Some(self.config.instance.namespace.to_owned()), - ..EnvVar::default() - }); - } - env_vars.push(EnvVar { - name: constants::HOPRD_NETWORK.to_owned(), - value: Some(self.hoprd.spec.network.to_owned()), - ..EnvVar::default() - }); - - env_vars.push(EnvVar { - name: constants::HOPR_PRIVATE_KEY.to_owned(), - value_from: Some(EnvVarSource { - secret_key_ref: Some(SecretKeySelector { - key: constants::HOPR_PRIVATE_KEY.to_owned(), - name: Some(self.config.instance.secret_name.to_owned()), - ..SecretKeySelector::default() - }), - ..EnvVarSource::default() - }), - ..EnvVar::default() - }); - - env_vars.push(EnvVar { - name: constants::HOPLI_ETHERSCAN_API_KEY.to_owned(), - value: Some("DummyValue".to_owned()), - ..EnvVar::default() - }); - - return env_vars; - } - -} - diff --git a/src/hoprd_secret.rs b/src/hoprd_secret.rs deleted file mode 100644 index 2352780..0000000 --- a/src/hoprd_secret.rs +++ /dev/null @@ -1,381 +0,0 @@ -use k8s_openapi::{api::{core::v1::Secret}, apimachinery::pkg::apis::meta::v1::OwnerReference, ByteString}; -use kube::{Api, Client, api::{ Patch, ListParams, PatchParams, DeleteParams, PostParams}, ResourceExt, Resource, core::{ObjectMeta}}; -use serde_json::{json}; -use std::{collections::{BTreeMap}, sync::Arc}; -use tracing::{debug, info, warn, error}; -use rand::{distributions::Alphanumeric, Rng}; -use async_recursion::async_recursion; -use crate::{ - model::{ HoprdSecret, Error}, operator_config::OperatorConfig, utils, constants, hoprd_jobs::{HoprdJob}, hoprd::Hoprd, context_data::ContextData -}; - -/// Action to be taken upon an `Hoprd` resource during reconciliation -enum SecretStatus { - /// The secret details has not been provided within the Hoprd configuration - NotSpecified, - /// The secret has been specified but does not exist yet - NotExists, - /// The secret exists and but it is not ready to be used because is not registered in the NetworkRegistry - NotRegistered, - /// The secret exists and but it is not ready to be used because is not funded - NotFunded, - /// The secret exists and it is currently being used by other existing node - Locked, - /// The secret exists and it is ready to be used - Ready, - /// The secret is in unknown status - Unknown -} - -pub struct SecretManager { - context: Arc, - client: Client, - operator_config: OperatorConfig, - hoprd: Hoprd, - pub hoprd_secret: Option, - job_manager: HoprdJob - -} - -impl SecretManager { - - pub fn new(context: Arc, hoprd: Hoprd) -> Self { - let client = context.client.clone(); - let operator_config = context.config.clone(); - let job_manager = HoprdJob::new(client.clone(), operator_config.clone(), hoprd.clone()); - Self { context, client, operator_config: operator_config, hoprd, hoprd_secret: None, job_manager } - } - - /// Evaluates the existence of the secret required labels and its correctness - /// - /// # Arguments - /// - `secret_labels` - Labels assigned to the secret - /// - fn check_secret_labels(&self, secret_labels: &BTreeMap) -> Result { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - if secret_labels.contains_key(constants::LABEL_NODE_NETWORK) { - let network_label: String = secret_labels.get_key_value(constants::LABEL_NODE_NETWORK).unwrap().1.parse().unwrap(); - if ! network_label.eq(&self.hoprd.spec.network.to_owned()) { - error!("The secret specified {secret_name} belongs to '{network_label}' network which is different from the specified '{}' network", self.hoprd.spec.network); - return Ok(false); - } - } else { - error!("The secret specified {secret_name} does not contain label {} which is mandatory", constants::LABEL_NODE_NETWORK); - return Ok(false); - } - Ok(true) - } - - /// Gets the first secret that is ready to be used - async fn get_first_secret_ready(&self) -> Result, Error> { - let api: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - let label_selector: String = format!("{}={},{}={},{}", - constants::LABEL_NODE_NETWORK, self.hoprd.spec.network, - constants::LABEL_NODE_LOCKED, "false", - constants::LABEL_NODE_PEER_ID); - let lp = ListParams::default().labels(&label_selector); - let secrets = api.list(&lp).await?; - Ok(secrets.items.first().map(|secret| secret.to_owned())) - } - - /// Gets the Kubernetes secret linked to the Hoprd node by its OwnedReferences - pub async fn get_hoprd_secret(&self) -> Result, Error> { - let api: Api = Api::namespaced(self.client.clone(),& self.operator_config.instance.namespace); - let label_selector: String = format!("{}={},{}={}", - constants::LABEL_NODE_NETWORK, self.hoprd.spec.network, - constants::LABEL_NODE_LOCKED, "true"); - let lp = ListParams::default().labels(&label_selector); - let secrets = api.list(&lp).await?; - let secret = secrets - .iter() - .find(|secret| { - let empty_references = &Vec::new(); - let reference = secret.metadata.owner_references.as_ref().unwrap_or(empty_references).first(); - reference.is_some() && reference.unwrap().name == self.hoprd.name_any() - }) - .map(|secret| secret.to_owned()); - Ok(secret) - } - - /// Gets the Kubernetes secret used by the hoprd-operator to register and fund nodes - pub async fn get_wallet_secret(&self) -> Result { - let api: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - if let Some(wallet_secret) = api.get_opt(&self.operator_config.instance.secret_name).await? { - Ok(wallet_secret) - } else { - Err(Error::SecretStatusError("[ERROR] Could not get wallet secret".to_owned())) - } - } - - /// Evaluates the status of the secret based on `SecretStatus` to determine later which actions need to be taken - async fn determine_secret_status(&mut self) -> Result { - return if self.hoprd.spec.secret.is_none() && self.hoprd_secret.is_none() { - info!("Hoprd node {:?} has not specified a secret in its spec", self.hoprd.name_any()); - Ok(SecretStatus::NotSpecified) - } else { - let client: Client = self.client.clone(); - let operator_namespace = &self.operator_config.instance.namespace.to_owned(); - let hoprd_secret = match self.hoprd.spec.secret.as_ref() { - Some(secret) => { - self.hoprd_secret = Some(secret.to_owned()); - secret - }, - None => self.hoprd_secret.as_ref().unwrap() - }; - let api_secrets: Api = Api::namespaced(client.clone(), &operator_namespace); - let secret_name = hoprd_secret.secret_name.to_owned(); - - - if let Some(secret) = api_secrets.get_opt(&secret_name).await? { - let empty_map = &BTreeMap::new(); - let secret_annotations: &BTreeMap = secret.metadata.annotations.as_ref().unwrap_or_else(|| empty_map); - let secret_labels: &BTreeMap = secret.metadata.labels.as_ref().unwrap_or_else(|| empty_map); - if ! self.check_secret_labels(secret_labels).unwrap() { - return Ok(SecretStatus::Unknown) - } - if secret_annotations.contains_key(constants::ANNOTATION_HOPRD_NETWORK_REGISTRY) { - let network_registry_annotation: bool = secret_annotations.get_key_value(constants::ANNOTATION_HOPRD_NETWORK_REGISTRY).unwrap().1.parse().unwrap(); - if ! network_registry_annotation { - info!("The secret {} exists but is not registered", secret_name); - return Ok(SecretStatus::NotRegistered) - } - } else { - info!("The secret {} exists but is not registered", secret_name); - return Ok(SecretStatus::NotRegistered) - } - if secret_annotations.contains_key(constants::ANNOTATION_HOPRD_FUNDED) { - let node_funded_annotation: bool = secret_annotations.get_key_value(constants::ANNOTATION_HOPRD_FUNDED).unwrap().1.parse().unwrap(); - if ! node_funded_annotation { - info!("The secret {secret_name} exists but is not funded"); - return Ok(SecretStatus::NotFunded) - } - } else { - info!("The secret {secret_name} exists but is not funded"); - return Ok(SecretStatus::NotFunded) - } - if secret_labels.contains_key(constants::LABEL_NODE_LOCKED) { - let node_locked_annotation = secret_labels.get_key_value(constants::LABEL_NODE_LOCKED).unwrap().1.parse().unwrap(); - if node_locked_annotation { - return Ok(SecretStatus::Locked); - } - } - info!("Hoprd node {:?} is ready to use the available secret {secret_name}", self.hoprd.name_any()); - return Ok(SecretStatus::Ready); - } else { - info!("Hoprd node {:?} has specified a secret {secret_name} which does not exists yet", self.hoprd.name_any()); - return Ok(SecretStatus::NotExists); - }; - }; - } - - /// Creates a new secret for storing sensitive data of the hoprd node, - #[async_recursion] - pub async fn create_secret(&mut self) -> Result { - return match self.determine_secret_status().await? { - SecretStatus::NotSpecified => self.do_status_not_specified().await, - SecretStatus::NotExists => self.do_status_not_exists().await, - SecretStatus::NotRegistered => self.do_status_not_registered().await, - SecretStatus::NotFunded => self.do_status_not_funded().await, - SecretStatus::Locked => self.do_status_locked().await, - SecretStatus::Ready => self.do_status_ready().await, - SecretStatus::Unknown => Err(Error::HoprdStatusError(format!("The secret is in unknown status").to_owned())) - } - } - - /// Unlocks a given secret from a Hoprd node - pub async fn unlock_secret(&self) -> Result<(), Error> { - let client: Client = self.client.clone(); - let operator_namespace = &self.operator_config.instance.namespace.to_owned(); - let api: Api = Api::namespaced(client.clone(), &operator_namespace); - if let Some(secret) = self.get_hoprd_secret().await? { - let secret_name = &secret.metadata.name.unwrap(); - utils::update_secret_label(&api.clone(), &secret_name, constants::LABEL_NODE_LOCKED, &"false".to_string()).await?; - utils::delete_secret_annotations(&api.clone(), &secret_name, constants::ANNOTATION_REPLICATOR_NAMESPACES).await?; - let wallet_secret = self.get_wallet_secret().await.unwrap(); - let owner_references: Option> = Some(vec![wallet_secret.controller_owner_ref(&()).unwrap()]); - let patch = Patch::Merge(json!({ - "metadata": { - "ownerReferences": owner_references - } - })); - return match api.patch(secret_name, &PatchParams::default(), &patch).await { - Ok(_) => { - let api_secrets: Api = Api::namespaced(client.clone(), &self.hoprd.namespace().unwrap().to_owned()); - if let Some(_secret) = api_secrets.get_opt(&secret_name).await? { - api_secrets.delete(&secret_name, &DeleteParams::default()).await?; - } - Ok(info!("The secret '{secret_name}' has been unlocked")) - }, - Err(error) => { - error!("Could not delete secret owned references for '{secret_name}': {:?}", error); - Err(Error::HoprdStatusError(format!("Could not delete secret owned references for '{secret_name}'.").to_owned())) - } - }; - } else { - Ok(warn!("The hoprd node did not own a secret {:?}", &self.hoprd.name_any().to_owned())) - } - } - - /// The secret has not been specified in the config. The config of the node will be updated with the parameters for a new secret - async fn do_status_not_specified(&mut self) -> Result { - match self.get_first_secret_ready().await { - Ok(secret) => { - match secret { - Some(secret) => { - let secret_name = secret.metadata.name.unwrap(); - self.hoprd_secret = Some(HoprdSecret { secret_name: secret_name.to_owned(), ..HoprdSecret::default() }); - return self.create_secret().await; - } - None => { - let random_string: String = rand::thread_rng().sample_iter(&Alphanumeric).take(5).map(char::from).collect(); - let mut secret_name = String::from("hoprd-node-"); - secret_name.push_str(&self.hoprd.spec.network.replace("_", "-")); - secret_name.push_str(&"-"); - secret_name.push_str(&random_string.to_lowercase()); - self.hoprd_secret = Some(HoprdSecret { secret_name: secret_name.to_owned(), ..HoprdSecret::default() }); - return self.do_status_not_exists().await; - } - } - } - Err(_err) => { - println!("[ERROR]: {:?}", _err); - return Err(Error::SecretStatusError( - format!("Could not retrieve a previous existing secret") - .to_owned() - )); - } - } - } - - /// The secret does not exists yet but has been specified in the config. A Job will be triggered to get the elements needed for running node: - async fn do_status_not_exists(&mut self) -> Result { - self.hoprd.create_event(self.context.clone(), crate::model::HoprdStatusEnum::Creating).await?; - self.hoprd.update_status(self.context.clone(), crate::model::HoprdStatusEnum::Creating).await?; - let secret = self.create_secret_resource().await.unwrap(); - let owner_reference: Option> = Some(vec![secret.controller_owner_ref(&()).unwrap()]); - match self.job_manager.execute_job_create_node(&self.hoprd_secret.as_ref().unwrap(), owner_reference).await { - Ok(_) => self.do_status_not_registered().await, - Err(err) => { - self.delete_finalizer().await?; - let api_secret: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - api_secret.delete(&secret.name_any(), &DeleteParams::default()).await? - .map_left(|_| info!("Deleting empty secret: {:?}", &secret.name_any())) - .map_right(|_| info!("Deleted empty secret: {:?}", &secret.name_any())); - Err(err) - } - } - } - - /// The secret exists but can not be used yet as it is not registered. Before using it will trigger a Job to register the node - async fn do_status_not_registered(&mut self) -> Result { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - let api: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - let secret = api.get(&secret_name).await.unwrap(); - let owner_reference: Option> = Some(vec![secret.controller_owner_ref(&()).unwrap()]); - self.hoprd.create_event(self.context.clone(), crate::model::HoprdStatusEnum::RegisteringInNetwork).await?; - self.hoprd.update_status(self.context.clone(), crate::model::HoprdStatusEnum::RegisteringInNetwork).await?; - self.job_manager.execute_job_registering_node( &self.hoprd_secret.as_ref().unwrap(), owner_reference).await?; - utils::update_secret_annotations(&api, &secret_name,constants::ANNOTATION_HOPRD_NETWORK_REGISTRY, "true").await?; - self.do_status_not_funded().await - } - - /// The secret exists but can not be used yet as it is not funded. Before using it will trigger a Job to fund the node - async fn do_status_not_funded(&mut self) -> Result { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - let api: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - let secret = api.get(&secret_name).await.unwrap(); - let owner_reference: Option> = Some(vec![secret.controller_owner_ref(&()).unwrap()]); - self.hoprd.create_event(self.context.clone(), crate::model::HoprdStatusEnum::Funding).await?; - self.hoprd.update_status(self.context.clone(), crate::model::HoprdStatusEnum::Funding).await?; - self.job_manager.execute_job_funding_node( &self.hoprd_secret.as_ref().unwrap(), owner_reference).await?; - let api: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - utils::update_secret_annotations(&api, &secret_name,constants::ANNOTATION_HOPRD_FUNDED, "true").await?; - return self.do_status_ready().await; - } - - /// The secret exists but it is locked by other node. It will raise an error specifying that the secret reference needs to be updated to an other secret or just remove it to create a new one. - async fn do_status_locked(&mut self) -> Result { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - return Err(Error::SecretStatusError( - format!("The secret {secret_name} in namespace {} is already locked by other hoprd node. See details above.", self.hoprd.namespace().unwrap()) - .to_owned() - )); - } - - /// The secret exists and is ready to be used by the hoprd node. It will create the annotations and labels for locking the secret - async fn do_status_ready(&mut self) -> Result { - let hoprd_namespace = &self.hoprd.namespace().unwrap(); - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - let api_secret: Api = Api::namespaced(self.client.clone(), &self.operator_config.instance.namespace); - utils::update_secret_annotations(&api_secret, &secret_name, constants::ANNOTATION_REPLICATOR_NAMESPACES, hoprd_namespace).await?; - self.hoprd.create_event(self.context.clone(), crate::model::HoprdStatusEnum::Running).await?; - self.hoprd.update_status(self.context.clone(), crate::model::HoprdStatusEnum::Running).await?; - utils::update_secret_label(&api_secret, &secret_name, constants::LABEL_NODE_LOCKED, &"true".to_string()).await?; - let owner_reference: Option> = Some(vec![self.hoprd.controller_owner_ref(&()).unwrap()]); - let patch = Patch::Merge(json!({ - "metadata": { - "ownerReferences": owner_reference - } - })); - match api_secret.patch(&secret_name, &PatchParams::default(), &patch).await { - Ok(secret) => Ok(secret), - Err(error) => { - println!("[ERROR]: {:?}", error); - Err(Error::HoprdStatusError(format!("Could not update secret owned references for '{secret_name}'.").to_owned())) - } - } - } - - /// Creates the underlying Kubernetes Secret resource - async fn create_secret_resource(&mut self) -> Result { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - let operator_namespace = &self.operator_config.instance.namespace; - let mut labels: BTreeMap = utils::common_lables(&secret_name.to_owned()); - labels.insert(constants::LABEL_NODE_NETWORK.to_owned(), self.hoprd.spec.network.to_owned()); - labels.insert(constants::LABEL_NODE_LOCKED.to_owned(), "false".to_owned()); - let mut data: BTreeMap = BTreeMap::new(); - data.insert(constants::HOPRD_METRICS_PASSWORD.to_owned(), ByteString("".to_owned().into_bytes())); - - let deployment: Secret = Secret { - metadata: ObjectMeta { - name: Some(secret_name.to_owned()), - namespace: Some(operator_namespace.to_owned()), - labels: Some(labels.clone()), - finalizers: Some(vec![constants::FINALIZER_SECRET.to_owned()]), - ..ObjectMeta::default() - }, - data: Some(data), - type_: Some("Opaque".to_owned()), - ..Secret::default() - }; - - // Create the secret defined above - let api: Api = Api::namespaced(self.client.clone(), operator_namespace); - Ok(api.create(&PostParams::default(), &deployment).await?) - } - - /// Removes all finalizers from the secret - async fn delete_finalizer(&self) -> Result<(), Error> { - let secret_name: String = self.hoprd_secret.as_ref().unwrap().secret_name.to_owned(); - let operator_namespace = &self.operator_config.instance.namespace; - let api: Api = Api::namespaced(self.client.clone(), operator_namespace); - let patch = &Patch::Merge(json!({ - "metadata": { - "finalizers": null - } - })); - if let Some(_) = api.get_opt(&secret_name).await? { - match api.patch(&secret_name, &PatchParams::default(), patch).await { - Ok(_hopr) => Ok(()), - Err(error) => { - println!("[ERROR]: {:?}", error); - return Err(Error::HoprdStatusError(format!("Could not delete finalizer on secret {secret_name}.").to_owned())); - } - } - } else { - Ok(debug!("Secret {secret_name} has already been deleted")) - } - } - -} - diff --git a/src/hoprd_service.rs b/src/hoprd_service.rs index 16480ab..13af82c 100644 --- a/src/hoprd_service.rs +++ b/src/hoprd_service.rs @@ -1,9 +1,17 @@ -use k8s_openapi::{api::core::v1::{ Service, ServicePort, ServiceSpec }, apimachinery::pkg::{util::intstr::IntOrString, apis::meta::v1::OwnerReference}}; -use kube::{Api, Client, Error, core::ObjectMeta, api::{PostParams, DeleteParams}, runtime::wait::{await_condition, conditions}}; -use std::collections::{BTreeMap}; -use tracing::{info}; +use k8s_openapi::{ + api::core::v1::{Service, ServicePort, ServiceSpec}, + apimachinery::pkg::{apis::meta::v1::OwnerReference, util::intstr::IntOrString}, +}; +use kube::{ + api::{DeleteParams, PostParams}, + core::ObjectMeta, + runtime::wait::{await_condition, conditions}, + Api, Client, Error, +}; +use std::{collections::BTreeMap, sync::Arc}; +use tracing::info; -use crate::{utils}; +use crate::{constants, context_data::ContextData, utils}; /// Creates a new service for accessing the hoprd node, /// @@ -12,8 +20,9 @@ use crate::{utils}; /// - `name` - Name of the service to be created /// - `namespace` - Namespace to create the Kubernetes Deployment in. /// -pub async fn create_service(client: Client, name: &str, namespace: &str, p2p_port: i32, owner_references: Option>) -> Result { - let labels: BTreeMap = utils::common_lables(&name.to_owned()); +pub async fn create_service(context_data: Arc, name: &str, namespace: &str, identity_pool_name: &str, p2p_port: i32, owner_references: Option>) -> Result { + let mut labels: BTreeMap = utils::common_lables(context_data.config.instance.name.to_owned(),Some(name.to_owned()),None); + labels.insert(constants::LABEL_KUBERNETES_IDENTITY_POOL.to_owned(),identity_pool_name.to_owned()); // Definition of the service. Alternatively, a YAML representation could be used as well. let service: Service = Service { @@ -34,33 +43,33 @@ pub async fn create_service(client: Client, name: &str, namespace: &str, p2p_por }; // Create the service defined above - let service_api: Api = Api::namespaced(client, namespace); + let service_api: Api = Api::namespaced(context_data.client.clone(), namespace); service_api.create(&PostParams::default(), &service).await } - fn service_ports(p2p_port: i32) -> Vec { - vec![ServicePort { - name: Some("api".to_owned()), - port: 3001, - protocol: Some("TCP".to_owned()), - target_port: Some(IntOrString::String("api".to_owned())), - ..ServicePort::default() - }, + vec![ ServicePort { - name: Some("p2p-tcp".to_owned()), - port: p2p_port, - protocol: Some("TCP".to_owned()), - target_port: Some(IntOrString::Int(p2p_port)), - ..ServicePort::default() - }, + name: Some("api".to_owned()), + port: 3001, + protocol: Some("TCP".to_owned()), + target_port: Some(IntOrString::String("api".to_owned())), + ..ServicePort::default() + }, ServicePort { - name: Some("p2p-udp".to_owned()), - port: p2p_port, - protocol: Some("UDP".to_owned()), - target_port: Some(IntOrString::Int(p2p_port)), - ..ServicePort::default() - } + name: Some("p2p-tcp".to_owned()), + port: p2p_port, + protocol: Some("TCP".to_owned()), + target_port: Some(IntOrString::Int(p2p_port)), + ..ServicePort::default() + }, + ServicePort { + name: Some("p2p-udp".to_owned()), + port: p2p_port, + protocol: Some("UDP".to_owned()), + target_port: Some(IntOrString::Int(p2p_port)), + ..ServicePort::default() + }, ] } @@ -74,11 +83,15 @@ fn service_ports(p2p_port: i32) -> Vec { pub async fn delete_service(client: Client, name: &str, namespace: &str) -> Result<(), Error> { let api: Api = Api::namespaced(client, namespace); if let Some(service) = api.get_opt(&name).await? { - let uid = service.metadata.uid.unwrap(); + let uid = service.metadata.uid.unwrap(); api.delete(name, &DeleteParams::default()).await?; - await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)).await.unwrap(); + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); Ok(info!("Service {name} successfully deleted")) } else { - Ok(info!("Service {name} in namespace {namespace} about to delete not found")) + Ok(info!( + "Service {name} in namespace {namespace} about to delete not found" + )) } } diff --git a/src/hoprd_service_monitor.rs b/src/hoprd_service_monitor.rs deleted file mode 100644 index d1c5849..0000000 --- a/src/hoprd_service_monitor.rs +++ /dev/null @@ -1,158 +0,0 @@ - - -use std::collections::BTreeMap; -use tracing::{info}; -use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; -use kube::api::{DeleteParams, PostParams}; -use kube::core::ObjectMeta; -use kube::Error; -use kube::runtime::wait::{await_condition, conditions}; -use kube::{Client, Api}; - -use crate::model::HoprdSecret; -use crate::servicemonitor::{ServiceMonitorSpec, ServiceMonitorEndpoints, ServiceMonitorEndpointsBasicAuth, ServiceMonitorEndpointsBasicAuthUsername, ServiceMonitorNamespaceSelector, ServiceMonitorSelector, ServiceMonitorEndpointsBasicAuthPassword, ServiceMonitorEndpointsRelabelings, ServiceMonitorEndpointsRelabelingsAction}; -use crate::{ - constants, - servicemonitor::ServiceMonitor, - utils, -}; - - -/// Creates a new serviceMonitor to enable the monitoring with Prometheus of the hoprd node, -/// -/// # Arguments -/// - `client` - A Kubernetes client to create the deployment with. -/// - `name` - Name of the deployment to be created -/// - `namespace` - Namespace to create the Kubernetes Deployment in. -/// - `hoprd_spec` - Details about the hoprd configuration node -/// -pub async fn create_service_monitor(client: Client, name: &str, namespace: &str, hoprd_secret: &HoprdSecret, owner_references: Option>) -> Result { - let labels: BTreeMap = utils::common_lables(&name.to_owned()); - let api: Api = Api::namespaced(client, namespace); - - - let service_monitor: ServiceMonitor = ServiceMonitor { - metadata: ObjectMeta { - labels: Some(labels.clone()), - name: Some(name.to_owned()), - namespace: Some(namespace.to_owned()), - owner_references, - ..ObjectMeta::default() - }, - spec: ServiceMonitorSpec { - endpoints: vec![ServiceMonitorEndpoints { - interval:Some("15s".to_owned()), - path: Some("/api/v2/node/metrics".to_owned()), - port:Some("api".to_owned()), - basic_auth: Some(ServiceMonitorEndpointsBasicAuth{ - username:Some(ServiceMonitorEndpointsBasicAuthUsername{ - key: hoprd_secret - .api_token_ref_key.as_ref().unwrap_or(&constants::HOPRD_API_TOKEN.to_owned()) - .to_string(), - name: Some(hoprd_secret.secret_name.to_owned()), - optional:Some(false) - }), - password:Some(ServiceMonitorEndpointsBasicAuthPassword { - key: hoprd_secret.metrics_password_ref_key.as_ref().unwrap_or(&constants::HOPRD_METRICS_PASSWORD.to_owned()) - .to_string(), - name: Some(hoprd_secret.secret_name.to_owned()), - optional:Some(false) - }), - }), - authorization: None, - bearer_token_file: None, - bearer_token_secret: None, - follow_redirects: None, - honor_labels: None, - honor_timestamps: None, - metric_relabelings: None, - oauth2: None, - params: None, - proxy_url: None, - relabelings: Some(build_metric_relabel()), - scheme: None, - scrape_timeout: None, - target_port: None, - tls_config: None }], - job_label: Some(name.to_owned()), - namespace_selector: Some(ServiceMonitorNamespaceSelector { - match_names: Some(vec![ namespace.to_owned() ]), - any: Some(false) - }), - selector: ServiceMonitorSelector { - match_labels: Some(labels), - match_expressions: None - }, - label_limit: None, - label_name_length_limit: None, - label_value_length_limit: None, - pod_target_labels: None, - sample_limit: None, - target_labels: None, - target_limit: None, - } - }; - - // Create the serviceMonitor defined above - api.create(&PostParams::default(), &service_monitor).await - - -} - -pub fn build_metric_relabel() -> Vec { - let mut metrics = Vec::with_capacity(2); - metrics.push(ServiceMonitorEndpointsRelabelings { - action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), - source_labels: Some(vec!["__meta_kubernetes_pod_label_hoprds_hoprnet_org_network".to_owned()]), - target_label: Some("hoprd_network".to_owned()), - modulus: None, regex: None, replacement: None, separator: None - }); - metrics.push(ServiceMonitorEndpointsRelabelings { - action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), - source_labels: Some(vec!["__meta_kubernetes_pod_container_image".to_owned()]), - target_label: Some("hoprd_version".to_owned()), - regex: Some("(.*):(.*)".to_owned()), - replacement: Some("${2}".to_owned()), - separator: Some(":".to_owned()), - modulus: None - }); - metrics.push(ServiceMonitorEndpointsRelabelings { - action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), - source_labels: Some(vec!["__meta_kubernetes_pod_node_name".to_owned()]), - target_label: Some("server_name".to_owned()), - modulus: None, regex: None, replacement: None, separator: None - }); - metrics.push(ServiceMonitorEndpointsRelabelings { - action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), - source_labels: Some(vec!["__meta_kubernetes_pod_label_hoprds_hoprnet_org_address".to_owned()]), - target_label: Some("hoprd_address".to_owned()), - modulus: None, regex: None, replacement: None, separator: None - }); - metrics.push(ServiceMonitorEndpointsRelabelings { - action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), - source_labels: Some(vec!["__meta_kubernetes_pod_label_hoprds_hoprnet_org_peerId".to_owned()]), - target_label: Some("hoprd_peer_id".to_owned()), - modulus: None, regex: None, replacement: None, separator: None - }); - metrics -} - - -/// Deletes an existing serviceMonitor. -/// -/// # Arguments: -/// - `client` - A Kubernetes client to delete the ServiceMonitor with -/// - `name` - Name of the ServiceMonitor to delete -/// - `namespace` - Namespace the existing ServiceMonitor resides in -/// -pub async fn delete_service_monitor(client: Client, name: &str, namespace: &str) -> Result<(), Error> { - let api: Api = Api::namespaced(client, namespace); - if let Some(service_monitor) = api.get_opt(&name).await? { - let uid = service_monitor.metadata.uid.unwrap(); - api.delete(name, &DeleteParams::default()).await?; - await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)).await.unwrap(); - Ok(info!("ServiceMonitor {name} successfully deleted")) - } else { - Ok(info!("ServiceMonitor {name} in namespace {namespace} about to delete not found")) - } -} diff --git a/src/identity_hoprd.rs b/src/identity_hoprd.rs new file mode 100644 index 0000000..75e44f6 --- /dev/null +++ b/src/identity_hoprd.rs @@ -0,0 +1,346 @@ +use crate::identity_hoprd_persistence; +use crate::identity_pool::{IdentityPool, IdentityPoolPhaseEnum}; +use crate::model::Error; +use crate::{constants, context_data::ContextData}; +use chrono::Utc; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::runtime::events::Recorder; +use kube::Resource; +use kube::{ + api::{Api, Patch, PatchParams, ResourceExt}, + client::Client, + runtime::{ + controller::Action, + events::{Event, EventType}, + }, + CustomResource, Result, +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::hash_map::DefaultHasher; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; +use std::time::Duration; +use tracing::{debug, error, info, warn}; + +/// Struct corresponding to the Specification (`spec`) part of the `Hoprd` resource, directly +/// reflects context of the `hoprds.hoprnet.org.yaml` file to be found in this repository. +/// The `Hoprd` struct will be generated by the `CustomResource` derive macro. +#[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Hash)] +#[kube( + group = "hoprnet.org", + version = "v1alpha2", + kind = "IdentityHoprd", + plural = "identityhoprds", + derive = "PartialEq", + namespaced +)] +#[kube(status = "IdentityHoprdStatus", shortname = "identityhoprd")] +#[serde(rename_all = "camelCase")] +pub struct IdentityHoprdSpec { + pub identity_pool_name: String, + pub identity_file: String, + pub peer_id: String, + pub native_address: String, + pub safe_address: String, + pub module_address: String, +} + +/// The status object of `Hoprd` +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct IdentityHoprdStatus { + pub update_timestamp: String, + pub phase: IdentityHoprdPhaseEnum, + pub checksum: String, + pub hoprd_node_name: Option, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] +pub enum IdentityHoprdPhaseEnum { + // The IdentityHoprd is initializing after first creationg + Initialized, + /// The IdentityHoprd is failed + Failed, + // The IdentityHoprd is synching + Synching, + // The IdentityHoprd is ready to be used + Ready, + // The IdentityHoprd is being used + InUse, + /// The IdentityHoprd is being deleted + Deleting, +} + +impl Display for IdentityHoprdPhaseEnum { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + IdentityHoprdPhaseEnum::Initialized => write!(f, "Initialized"), + IdentityHoprdPhaseEnum::Failed => write!(f, "Failed"), + IdentityHoprdPhaseEnum::Synching => write!(f, "Synching"), + IdentityHoprdPhaseEnum::Ready => write!(f, "Ready"), + IdentityHoprdPhaseEnum::InUse => write!(f, "InUse"), + IdentityHoprdPhaseEnum::Deleting => write!(f, "Deleting"), + } + } +} + +impl IdentityHoprd { + /// Handle the creation of IdentityHoprd resource + pub async fn create(&self, context_data: Arc) -> Result { + let client: Client = context_data.client.clone(); + let identity_namespace: String = self.namespace().unwrap(); + let identity_name: String = self.name_any(); + let identity_pool_name: String = self.spec.identity_pool_name.to_owned(); + + info!("Starting to create identity {identity_name} in namespace {identity_namespace}"); + self.add_finalizer(client.clone(), &identity_name, &identity_namespace).await.unwrap(); + self.add_owner_reference(client.clone()).await?; + identity_hoprd_persistence::create_pvc(context_data.clone(), &self).await?; + self.create_event(context_data.clone(), IdentityHoprdPhaseEnum::Initialized, None).await?; + self.update_phase(context_data.clone(), IdentityHoprdPhaseEnum::Initialized, None).await?; + // TODO: Validate data + // - Is registered in network + // - Is funded (safe and node) + // - SafeAddress is correct + // - ModuleAddress is correct + + // Update pool to decrease identities + let mut updated = false; + { + let mut context_state = context_data.state.write().await; + let identity_pool_option = context_state.get_identity_pool(&self.namespace().unwrap(), &identity_pool_name); + if identity_pool_option.is_some() { + let mut identity_pool_arc = identity_pool_option.unwrap(); + let identity_pool: &mut IdentityPool = Arc::::make_mut(&mut identity_pool_arc); + identity_pool.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::IdentityCreated).await?; + context_state.update_identity_pool(identity_pool.to_owned()); + updated = true; + } + } + if updated { + // These instructions need to be done out of the context_data lock + self.create_event(context_data.clone(), IdentityHoprdPhaseEnum::Ready, None).await?; + self.update_phase(context_data.clone(), IdentityHoprdPhaseEnum::Ready, None).await?; + } else { + error!("Identity pool {} not exists in namespace {}", identity_pool_name, &self.namespace().unwrap()); + } + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } + + /// Handle the modification of IdentityHoprd resource + pub async fn modify(&self) -> Result { + error!("The resource cannot be modified"); + Err(Error::OperationNotSupported( + format!("The resource cannot be modified").to_owned(), + )) + } + + // Handle the deletion of IdentityHoprd resource + pub async fn delete(&self, context_data: Arc) -> Result { + let identity_name = self.name_any(); + let identity_namespace = self.namespace().unwrap(); + let client: Client = context_data.client.clone(); + if let Some(status) = self.status.as_ref() { + if !status.phase.eq(&IdentityHoprdPhaseEnum::InUse) { + self.create_event(context_data.clone(), IdentityHoprdPhaseEnum::Deleting, None).await?; + self.update_phase(context_data.clone(), IdentityHoprdPhaseEnum::Deleting, None).await?; + info!("Starting to delete identity {identity_name} from namespace {identity_namespace}"); + + { + let mut context_state = context_data.state.write().await; + let identity_pool_option = context_state.get_identity_pool(&self.namespace().unwrap(), &self.spec.identity_pool_name); + if identity_pool_option.is_some() { + let mut identity_pool_arc = identity_pool_option.unwrap(); + let identity_pool: &mut IdentityPool = Arc::::make_mut(&mut identity_pool_arc); + identity_pool.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::IdentityDeleted).await?; + context_state.update_identity_pool(identity_pool.to_owned()); + } else { + warn!("Identity pool {} not exists in namespace {}", &self.spec.identity_pool_name, &self.namespace().unwrap()); + } + } + + // TODO Drain funds + self.delete_finalizer(client.clone(), &identity_name, &identity_namespace).await?; + info!("Identity {identity_name} in namespace {identity_namespace} has been successfully deleted"); + Ok(Action::await_change()) // Makes no sense to delete after a successful delete, as the resource is gone + } else { + error!("Cannot delete an identity in state {}", status.phase); + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } + } else { + error!("IdentityHoprd {} was not correctly initialized", &identity_name); + self.delete_finalizer(client.clone(), &identity_name, &identity_namespace).await?; + Ok(Action::await_change()) + } + + } + + /// Adds a finalizer in IdentityHoprd to prevent deletion of the resource by Kubernetes API and allow the controller to safely manage its deletion + async fn add_finalizer(&self, client: Client, identity_name: &str, identity_namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client.clone(), &identity_namespace.to_owned()); + let patch = Patch::Merge(json!({ + "metadata": { + "finalizers": [constants::OPERATOR_FINALIZER] + } + })); + match api.patch(&identity_name, &PatchParams::default(), &patch).await + { + Ok(_) => Ok(()), + Err(error) => { + error!("Could not add finalizer on {identity_name}: {:?}", error); + Err(Error::HoprdStatusError(format!("Could not add finalizer on {identity_name}.").to_owned())) + } + } + } + + /// Deletes the finalizer of IdentityHoprd resource, so the resource can be freely deleted by Kubernetes API + async fn delete_finalizer(&self, client: Client, identity_name: &str, identity_namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client.clone(), &identity_namespace.to_owned()); + let patch = Patch::Merge(json!({ + "metadata": { + "finalizers": null + } + })); + if let Some(_) = api.get_opt(&identity_name).await? { + match api.patch(&identity_name, &PatchParams::default(), &patch).await { + Ok(_) => Ok(()), + Err(error) => Ok(error!("Could not delete finalizer on {identity_name}: {:?}", error)) + } + } else { + Ok(debug!("Identity {identity_name} already deleted")) + } + } + + /// Creates an event for IdentityHoprd given the new IdentityHoprdStatusEnum + pub async fn create_event(&self, context: Arc, phase: IdentityHoprdPhaseEnum, hoprd_name: Option) -> Result<(), Error> { + let client: Client = context.client.clone(); + let ev: Event = match phase { + IdentityHoprdPhaseEnum::Initialized => Event { + type_: EventType::Normal, + reason: "Initialized".to_string(), + note: Some("Initialized node identity".to_owned()), + action: "Starting the process of creating a new identity".to_string(), + secondary: None, + }, + IdentityHoprdPhaseEnum::Failed => Event { + type_: EventType::Warning, + reason: "Failed".to_string(), + note: Some("Failed to bootstrap identity".to_owned()), + action: "Identity bootstrapping failed".to_string(), + secondary: None, + }, + IdentityHoprdPhaseEnum::Synching => Event { + type_: EventType::Normal, + reason: "Synching".to_string(), + note: Some("Starting to sync an identity".to_owned()), + action: "Synching failed identity".to_string(), + secondary: None, + }, + IdentityHoprdPhaseEnum::Ready => Event { + type_: EventType::Normal, + reason: "Ready".to_string(), + note: Some("Identity ready to be used".to_owned()), + action: "Identity is ready to be used by a Hoprd node".to_string(), + secondary: None, + }, + IdentityHoprdPhaseEnum::InUse => Event { + type_: EventType::Normal, + reason: "InUse".to_string(), + note: Some(format!( + "Identity being used by Hoprd node {}", + hoprd_name.unwrap_or("unknown".to_owned()) + )), + action: "Identity is being used".to_string(), + secondary: None, + }, + IdentityHoprdPhaseEnum::Deleting => Event { + type_: EventType::Normal, + reason: "Deleting".to_string(), + note: Some("Identity is being deleted".to_owned()), + action: "Identity deletion started".to_string(), + secondary: None, + }, + }; + let recorder: Recorder = context.state.read().await.generate_identity_hoprd_event(client.clone(), self); + Ok(recorder.publish(ev).await?) + } + + /// Updates the status of IdentityHoprd + pub async fn update_phase(&self, context: Arc, phase: IdentityHoprdPhaseEnum, hoprd_name: Option) -> Result<(), Error> { + let client: Client = context.client.clone(); + let identity_hoprd_name = self.metadata.name.as_ref().unwrap().to_owned(); + let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); + + let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); + if phase.eq(&IdentityHoprdPhaseEnum::Deleting) { + Ok(()) + } else { + let mut hasher: DefaultHasher = DefaultHasher::new(); + self.spec.clone().hash(&mut hasher); + let checksum: String = hasher.finish().to_string(); + let status = IdentityHoprdStatus { + update_timestamp: Utc::now().to_rfc3339(), + phase, + checksum, + hoprd_node_name: hoprd_name, + }; + let patch = Patch::Merge(json!({ "status": status })); + + match api.patch(&identity_hoprd_name, &PatchParams::default(), &patch).await + { + Ok(_identity) => Ok(()), + Err(error) => Ok(error!("Could not update status on {identity_hoprd_name}: {:?}",error)) + } + } + } + + pub async fn get_identity_pool(&self, client: Client) -> Result { + let api: Api = Api::namespaced(client.clone(), &self.namespace().unwrap()); + return Ok(api.get(&self.spec.identity_pool_name).await.unwrap()); + } + + // Unlocks a given identity from a Hoprd node + pub async fn unlock(&self, context_data: Arc) -> Result<(), Error> { + if self.status.as_ref().unwrap().phase.eq(&IdentityHoprdPhaseEnum::InUse) + { + self.create_event(context_data.clone(), IdentityHoprdPhaseEnum::Ready, None).await?; + self.update_phase(context_data.clone(), IdentityHoprdPhaseEnum::Ready, None).await?; + + // Update pool to decrease locks + { + let mut context_state = context_data.state.write().await; + let mut identity_pool_arc = context_state.get_identity_pool(&self.namespace().unwrap(), &self.spec.identity_pool_name).unwrap(); + let identity_pool: &mut IdentityPool = Arc::::make_mut(&mut identity_pool_arc); + identity_pool.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::Unlocked).await?; + context_state.update_identity_pool(identity_pool.to_owned()); + } + Ok(()) + } else { + Ok(warn!("The identity cannot be unlock because it is in status {:?}", &self.status)) + } + } + + pub async fn add_owner_reference(&self, client: Client) -> Result<(), Error> { + let identity_pool = self.get_identity_pool(client.clone()).await.unwrap(); + let identity_name = self.name_any(); + let identity_pool_owner_reference: Option> = Some(vec![identity_pool.controller_owner_ref(&()).unwrap()]); + let api: Api = Api::namespaced(client.clone(), &identity_pool.namespace().unwrap()); + let patch = Patch::Merge(json!({ + "metadata": { + "ownerReferences": identity_pool_owner_reference + } + })); + let _updated = match api.patch(&identity_name, &PatchParams::default(), &patch).await + { + Ok(secret) => Ok(secret), + Err(error) => { + println!("[ERROR]: {:?}", error); + Err(Error::HoprdStatusError(format!("Could not update secret owned references for '{identity_name}'.").to_owned())) + } + }; + Ok(()) + } +} diff --git a/src/hoprd_persistence.rs b/src/identity_hoprd_persistence.rs similarity index 63% rename from src/hoprd_persistence.rs rename to src/identity_hoprd_persistence.rs index 34f5a44..d369cdd 100644 --- a/src/hoprd_persistence.rs +++ b/src/identity_hoprd_persistence.rs @@ -1,33 +1,33 @@ - -use k8s_openapi::api::core::v1::{ PersistentVolumeClaim, PersistentVolumeClaimSpec, ResourceRequirements}; +use crate::context_data::ContextData; +use k8s_openapi::api::core::v1::{ + PersistentVolumeClaim, PersistentVolumeClaimSpec, ResourceRequirements, +}; use k8s_openapi::apimachinery::pkg::api::resource::Quantity; -use k8s_openapi::apimachinery::pkg::apis::meta::v1::{ OwnerReference}; -use kube::api::{ ObjectMeta, PostParams}; -use kube::{Api, ResourceExt, Resource}; -use std::collections::{BTreeMap}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::api::{ObjectMeta, PostParams}; +use kube::{Api, Resource, ResourceExt}; +use std::collections::BTreeMap; use std::sync::Arc; -use crate::context_data::ContextData; - -use crate::{hoprd::{ Hoprd}, utils}; +use crate::identity_hoprd::IdentityHoprd; +use crate::utils; /// Creates the Persitence Volume Claim -pub async fn create_pvc(context: Arc, hoprd: &Hoprd) -> Result { +pub async fn create_pvc(context: Arc, identity_hoprd: &IdentityHoprd) -> Result { let client = context.client.clone(); - let namespace: String = hoprd.namespace().unwrap(); - let name: String= hoprd.name_any(); - let owner_references: Option> = Some(vec![hoprd.controller_owner_ref(&()).unwrap()]); - let labels: BTreeMap = utils::common_lables(&name.to_owned()); + let namespace: String = identity_hoprd.namespace().unwrap(); + let name: String = identity_hoprd.name_any(); + let owner_references: Option> = Some(vec![identity_hoprd.controller_owner_ref(&()).unwrap()]); + let labels: Option> = Some(utils::common_lables(context.config.instance.name.to_owned(), Some(name.to_owned()), None)); let mut resource: BTreeMap = BTreeMap::new(); resource.insert("storage".to_string(), Quantity(context.config.persistence.size.to_owned())); - // Definition of the deployment. Alternatively, a YAML representation could be used as well. let pvc: PersistentVolumeClaim = PersistentVolumeClaim { metadata: ObjectMeta { name: Some(name.to_owned()), namespace: Some(namespace.to_owned()), - labels: Some(labels.clone()), + labels, owner_references, ..ObjectMeta::default() }, diff --git a/src/identity_pool.rs b/src/identity_pool.rs new file mode 100644 index 0000000..b874a3d --- /dev/null +++ b/src/identity_pool.rs @@ -0,0 +1,687 @@ +use crate::hoprd_deployment_spec::HoprdDeploymentSpec; +use crate::identity_hoprd::{IdentityHoprd, IdentityHoprdPhaseEnum}; +use crate::model::Error; +use crate::{constants, context_data::ContextData}; +use crate::{identity_pool_service_account, identity_pool_service_monitor, utils}; +use chrono::Utc; +use k8s_openapi::api::batch::v1::{Job, JobSpec}; +use k8s_openapi::api::core::v1::{ + Container, EmptyDirVolumeSource, EnvVar, EnvVarSource, PodSpec, PodTemplateSpec, + SecretKeySelector, Volume, VolumeMount, +}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::api::{ListParams, PostParams}; +use kube::core::ObjectMeta; +use kube::runtime::conditions; +use kube::runtime::events::Recorder; +use kube::runtime::wait::await_condition; +use kube::{ + api::{Api, Patch, PatchParams}, + client::Client, + runtime::{ + controller::Action, + events::{Event, EventType}, + }, + CustomResource, Result, +}; +use kube::{Resource, ResourceExt}; +use rand::{distributions::Alphanumeric, Rng}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::hash_map::DefaultHasher; +use std::collections::BTreeMap; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::sync::Arc; +use std::time::Duration; +use tracing::{debug, error, info, warn}; + +/// Struct corresponding to the Specification (`spec`) part of the `Hoprd` resource, directly +/// reflects context of the `hoprds.hoprnet.org.yaml` file to be found in this repository. +/// The `Hoprd` struct will be generated by the `CustomResource` derive macro. +#[derive(CustomResource, Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Hash)] +#[kube( + group = "hoprnet.org", + version = "v1alpha2", + kind = "IdentityPool", + plural = "identitypools", + derive = "PartialEq", + namespaced +)] +#[kube(status = "IdentityPoolStatus", shortname = "identitypool")] +#[serde(rename_all = "camelCase")] +pub struct IdentityPoolSpec { + pub network: String, + pub secret_name: String, + pub min_ready_identities: i32, +} + +/// The status object of `Hoprd` +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct IdentityPoolStatus { + pub update_timestamp: String, + pub phase: IdentityPoolPhaseEnum, + pub size: i32, + pub locked: i32, + pub checksum: String, +} + +impl Default for IdentityPoolStatus { + fn default() -> Self { + Self { + update_timestamp: Utc::now().to_rfc3339(), + phase: IdentityPoolPhaseEnum::Initialized, + size: 0, + locked: 0, + checksum: "init".to_owned(), + } + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] +pub enum IdentityPoolPhaseEnum { + // Status that represent when the IdentityPool is initialized after creation by creating the serviceMonitor + Initialized, + /// Status that represent when the IdentityPool initialization validation has failed + Failed, + // Status that represent when the IdentityPool is out of synchronization. It requires to create new identities + OutOfSync, + // Status that represent when the IdentityPool is ready to be used + Ready, + /// Status that represent when the IdentityPool is being deleted + Deleting, + // Event that represent when the IdentityPool has locked an identity + Locked, + // Event that represent when the IdentityPool is unlocking an identity + Unlocking, + // Event that represent when the IdentityPool has unlocked an identity + Unlocked, + // Event that represents when the IdentityPool is syncronizing by creating new required identities + CreatingIdentity, + // Event that represents when the IdentityPool has created a new identity + IdentityCreated, + // Event that represents when the IdentityPool has created a new identity + IdentityDeleted, +} + +impl Display for IdentityPoolPhaseEnum { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + IdentityPoolPhaseEnum::Initialized => write!(f, "Initialized"), + IdentityPoolPhaseEnum::Failed => write!(f, "Failed"), + IdentityPoolPhaseEnum::OutOfSync => write!(f, "OutOfSync"), + IdentityPoolPhaseEnum::Ready => write!(f, "Ready"), + IdentityPoolPhaseEnum::Deleting => write!(f, "Deleting"), + IdentityPoolPhaseEnum::Locked => write!(f, "Locked"), + IdentityPoolPhaseEnum::Unlocking => write!(f, "Unlocking"), + IdentityPoolPhaseEnum::Unlocked => write!(f, "Unlocked"), + IdentityPoolPhaseEnum::CreatingIdentity => write!(f, "CreatingIdentity"), + IdentityPoolPhaseEnum::IdentityCreated => write!(f, "IdentityCreated"), + IdentityPoolPhaseEnum::IdentityDeleted => write!(f, "IdentityDeleted"), + } + } +} + +impl IdentityPool { + /// Handle the creation of IdentityPool resource + pub async fn create(&mut self, context_data: Arc) -> Result { + let client: Client = context_data.client.clone(); + let identity_pool_namespace: String = self.namespace().unwrap(); + let identity_pool_name: String = self.name_any(); + let owner_references: Option> = + Some(vec![self.controller_owner_ref(&()).unwrap()]); + info!("Starting to create identity {identity_pool_name} in namespace {identity_pool_namespace}"); + self.add_finalizer(client.clone(), &identity_pool_name, &identity_pool_namespace).await.unwrap(); + identity_pool_service_monitor::create_service_monitor(context_data.clone(), &identity_pool_name, &identity_pool_namespace, &self.spec.secret_name, owner_references.to_owned()).await?; + identity_pool_service_account::create_rbac(context_data.clone(), &identity_pool_namespace, &identity_pool_name,owner_references.to_owned()).await?; + // TODO: Validate data + // - Check that the secret exist and contains the required keys + // - Does the wallet private key have permissions in Network to register new nodes and create safes ? + // - Does the wallet private key have enough funds to work ? + self.create_event(context_data.clone(), IdentityPoolPhaseEnum::Initialized, None).await?; + self.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::Initialized).await?; + info!("Identity {identity_pool_name} in namespace {identity_pool_namespace} has been successfully created"); + if self.spec.min_ready_identities == 0 { + self.create_event(context_data.clone(), IdentityPoolPhaseEnum::Ready, None).await?; + self.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::Ready).await?; + } else { + self.create_event(context_data.clone(),IdentityPoolPhaseEnum::OutOfSync,Some(self.spec.min_ready_identities.to_string()),).await?; + self.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::OutOfSync).await?; + info!("Identity {identity_pool_name} in namespace {identity_pool_namespace} requires to create {} new identities", self.spec.min_ready_identities); + } + context_data.state.write().await.add_identity_pool(self.clone()); + Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))) + } + + /// Handle the modification of IdentityPool resource + pub async fn modify(&mut self, context: Arc) -> Result { + let client: Client = context.client.clone(); + let identity_pool_namespace: String = self.namespace().unwrap(); + let identity_pool_name: String = self.name_any(); + let annotations = utils::get_resource_kinds( + client.clone(), + utils::ResourceType::IdentityPool, + utils::ResourceKind::Annotations, + &identity_pool_name, + &identity_pool_namespace, + ).await; + if annotations.contains_key(constants::ANNOTATION_LAST_CONFIGURATION) { + let previous_text: String = annotations + .get_key_value(constants::ANNOTATION_LAST_CONFIGURATION) + .unwrap() + .1 + .parse() + .unwrap(); + match serde_json::from_str::(&previous_text) { + Ok(previous_identity_pool) => { + self.check_inmutable_fields(&previous_identity_pool.spec).unwrap(); + info!("Identity pool {identity_pool_name} in namespace {identity_pool_namespace} has been successfully modified"); + if self.status.as_ref().unwrap().size - self.status.as_ref().unwrap().locked - self.spec.min_ready_identities < 0 { + let pending = self.spec.min_ready_identities - self.status.as_ref().unwrap().locked - self.status.as_ref().unwrap().size; + self.create_event(context.clone(),IdentityPoolPhaseEnum::OutOfSync,Some(pending.to_string())).await?; + self.update_phase(context.client.clone(), IdentityPoolPhaseEnum::OutOfSync).await?; + info!("Identity {identity_pool_name} in namespace {identity_pool_namespace} requires to create {} new identities", self.spec.min_ready_identities); + }else { + self.create_event(context.clone(),IdentityPoolPhaseEnum::Ready, None).await?; + self.update_phase(context.client.clone(), IdentityPoolPhaseEnum::Ready).await?; + } + }, + Err(_err) => { + error!("Could not parse the last applied configuration from {identity_pool_name}."); + } + } + } + Ok(Action::requeue(Duration::from_secs(constants::RECONCILE_FREQUENCY))) + } + + // Handle the deletion of IdentityPool resource + pub async fn delete(&mut self, context_data: Arc) -> Result { + let identity_pool_namespace = self.namespace().unwrap(); + let identity_pool_name = self.name_any(); + if self.status.as_ref().unwrap().locked == 0 { + let client: Client = context_data.client.clone(); + self.update_phase(context_data.client.clone(), IdentityPoolPhaseEnum::Deleting).await?; + self.create_event(context_data.clone(), IdentityPoolPhaseEnum::Deleting, None).await?; + info!("Starting to delete identity {identity_pool_name} from namespace {identity_pool_namespace}"); + identity_pool_service_monitor::delete_service_monitor(client.clone(), &identity_pool_name, &identity_pool_namespace).await?; + identity_pool_service_account::delete_rbac(client.clone(), &identity_pool_namespace, &identity_pool_name).await?; + self.delete_finalizer(client.clone(), &identity_pool_name, &identity_pool_namespace).await?; + context_data.state.write().await.remove_identity_pool(&identity_pool_namespace, &identity_pool_name); + info!("Identity {identity_pool_name} in namespace {identity_pool_namespace} has been successfully deleted"); + Ok(Action::await_change()) // Makes no sense to delete after a successful delete, as the resource is gone + } else { + warn!("Cannot delete an identity pool with identities in use"); + Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))) + } + } + + pub async fn sync(&mut self, context: Arc) -> Result { + let mut current_ready_identities = self.status.as_ref().unwrap().size - self.status.as_ref().unwrap().locked; + let mut iterations = (self.spec.min_ready_identities - current_ready_identities) * 2; + if self.are_active_jobs(context.clone()).await.unwrap() { + warn!("Skipping synchornization for {} in namespace {} as there is still one job in progress", self.name_any(), self.namespace().unwrap()); + } else { + while current_ready_identities < self.spec.min_ready_identities && iterations > 0 { + iterations -= 1; + // Invoke Job + match self.create_new_identity(context.clone()).await { + Ok(()) => current_ready_identities += 1, + Err(error) => { + error!("Could not create identity: {:?}", error); + iterations = 0; + } + }; + } + if current_ready_identities >= self.spec.min_ready_identities { + self.create_event(context.clone(), IdentityPoolPhaseEnum::Ready, None).await?; + } else { + self.create_event(context.clone(), IdentityPoolPhaseEnum::OutOfSync, Some((self.spec.min_ready_identities - current_ready_identities).to_string())).await?; + info!("Identity {} in namespace {} failed to create required identities", self.name_any(), self.namespace().unwrap()); + } + } + Ok(Action::requeue(Duration::from_secs( + constants::RECONCILE_FREQUENCY, + ))) + } + + fn check_inmutable_fields(&self, previous_identity: &IdentityPoolSpec) -> Result<(), Error> { + if !self.spec.network.eq(&previous_identity.network) + { + return Err(Error::HoprdConfigError(format!("Configuration is invalid, 'network' field cannot be changed on {}.", self.name_any()))); + } + if !self.spec.secret_name.eq(&previous_identity.secret_name) { + return Err(Error::HoprdConfigError(format!("Configuration is invalid, 'secret_name' field cannot be changed on {}.", self.name_any()))); + } + Ok(()) + } + + /// Adds a finalizer in IdentityPool to prevent deletion of the resource by Kubernetes API and allow the controller to safely manage its deletion + async fn add_finalizer(&self, client: Client, identity_name: &str, identity_namespace: &str) -> Result<(), Error> { + let api: Api = + Api::namespaced(client.clone(), &identity_namespace.to_owned()); + let patch = Patch::Merge(json!({ + "metadata": { + "finalizers": [constants::OPERATOR_FINALIZER] + } + })); + match api + .patch(&identity_name, &PatchParams::default(), &patch) + .await + { + Ok(_) => Ok(()), + Err(error) => { + error!( + "Could not add finalizer on {identity_name}: {:?}", + error + ); + return Err(Error::HoprdStatusError( + format!("Could not add finalizer on {identity_name}.") + .to_owned(), + )); + } + } + } + + /// Deletes the finalizer of IdentityPool resource, so the resource can be freely deleted by Kubernetes API + async fn delete_finalizer(&self, client: Client, identity_name: &str, identity_namespace: &str) -> Result<(), Error> { + let api: Api = + Api::namespaced(client.clone(), &identity_namespace.to_owned()); + let patch = Patch::Merge(json!({ + "metadata": { + "finalizers": null + } + })); + if let Some(_) = api.get_opt(&identity_name).await? { + match api + .patch(&identity_name, &PatchParams::default(), &patch) + .await + { + Ok(_) => Ok(()), + Err(error) => Ok(error!( + "Could not delete finalizer on {identity_name}: {:?}", + error + )), + } + } else { + Ok(debug!( + "Identity {identity_name} already deleted" + )) + } + } + + /// Creates an event for IdentityPool given the new IdentityPoolStatusEnum + pub async fn create_event(&self, context: Arc, phase: IdentityPoolPhaseEnum, attribute: Option) -> Result<(), Error> { + let client: Client = context.client.clone(); + let ev: Event = match phase { + IdentityPoolPhaseEnum::Initialized => Event { + type_: EventType::Normal, + reason: "Initialized".to_string(), + note: Some("Initializing identity pool".to_owned()), + action: "The service monitor has been created".to_string(), + secondary: None + }, + IdentityPoolPhaseEnum::Failed => Event { + type_: EventType::Warning, + reason: "Failed".to_string(), + note: Some("Failed to bootstrap identity pool".to_owned()), + action: "Identity pool bootstrap validations have failed".to_string(), + secondary: None + }, + IdentityPoolPhaseEnum::OutOfSync => Event { + type_: EventType::Normal, + reason: "OutOfSync".to_string(), + note: Some(format!("The identity pool is out of sync. There are {} identities pending to be created", attribute.unwrap())), + action: "The identity pool need to create more identities".to_string(), + secondary: None + }, + IdentityPoolPhaseEnum::Ready => Event { + type_: EventType::Normal, + reason: "Ready".to_string(), + note: Some("Identity pool ready to be used".to_owned()), + action: "Identity pool is ready to be used by a Hoprd node".to_string(), + secondary: None + }, + IdentityPoolPhaseEnum::Deleting => Event { + type_: EventType::Normal, + reason: "Deleting".to_string(), + note: Some("Identity pool is being deleted".to_owned()), + action: "Identity pool deletion started".to_string(), + secondary: None + }, + IdentityPoolPhaseEnum::Locked => Event { + type_: EventType::Normal, + reason: "Locked".to_string(), + note: Some(format!("Identity {} locked from pool", attribute.as_ref().unwrap())), + action: format!("Identity {} locked from pool", attribute.as_ref().unwrap()), + secondary: None + }, + IdentityPoolPhaseEnum::Unlocking => Event { + type_: EventType::Normal, + reason: "Unlocking".to_string(), + note: Some(format!("Unlokcing identity {} from pool", attribute.as_ref().unwrap())), + action: format!("Unlokcing identity {} from pool", attribute.as_ref().unwrap()), + secondary: None + }, + IdentityPoolPhaseEnum::Unlocked => Event { + type_: EventType::Normal, + reason: "Unlocked".to_string(), + note: Some(format!("Identity {} unlocked from pool", attribute.as_ref().unwrap())), + action: format!("Identity {} unlocked from pool", attribute.as_ref().unwrap()), + secondary: None + }, + IdentityPoolPhaseEnum::CreatingIdentity => Event { + type_: EventType::Normal, + reason: "CreatingIdentity".to_string(), + note: Some(format!("Creating identity {}", attribute.as_ref().unwrap())), + action: format!("Creating identity {}", attribute.as_ref().unwrap()), + secondary: None + }, + IdentityPoolPhaseEnum::IdentityCreated => Event { + type_: EventType::Normal, + reason: "IdentityCreated".to_string(), + note: Some(format!("Identity pool created identity {}", attribute.as_ref().unwrap())), + action: format!("Identity pool created identity {}", attribute.as_ref().unwrap()), + secondary: None + }, + IdentityPoolPhaseEnum::IdentityDeleted => Event { + type_: EventType::Normal, + reason: "IdentityDeleted".to_string(), + note: Some(format!("Identity pool deregistered identity {}", attribute.as_ref().unwrap())), + action: format!("Identity pool deregistered identity {}", attribute.as_ref().unwrap()), + secondary: None + }, + }; + let recorder: Recorder = context.state.read().await.generate_identity_pool_event(client.clone(), self); + Ok(recorder.publish(ev).await?) + } + + /// Updates the status of IdentityPool + pub async fn update_phase(&mut self, client: Client, phase: IdentityPoolPhaseEnum) -> Result<(), Error> { + let identity_hoprd_name = self.metadata.name.as_ref().unwrap().to_owned(); + let hoprd_namespace = self.metadata.namespace.as_ref().unwrap().to_owned(); + let mut identity_pool_status = self + .status + .as_ref() + .unwrap_or(&IdentityPoolStatus::default()) + .to_owned(); + + let api: Api = Api::namespaced(client.clone(), &hoprd_namespace.to_owned()); + if phase.eq(&IdentityPoolPhaseEnum::Deleting) { + Ok(()) + } else { + let mut hasher: DefaultHasher = DefaultHasher::new(); + self.spec.clone().hash(&mut hasher); + let checksum: String = hasher.finish().to_string(); + identity_pool_status.update_timestamp = Utc::now().to_rfc3339(); + identity_pool_status.checksum = checksum; + identity_pool_status.phase = phase; + if phase.eq(&IdentityPoolPhaseEnum::IdentityCreated) { + if (identity_pool_status.size - identity_pool_status.locked + 1) + >= self.spec.min_ready_identities + { + identity_pool_status.phase = IdentityPoolPhaseEnum::Ready; + } else { + identity_pool_status.phase = IdentityPoolPhaseEnum::OutOfSync; + } + identity_pool_status.size = identity_pool_status.size + 1 + } else if phase.eq(&IdentityPoolPhaseEnum::IdentityDeleted) { + if (identity_pool_status.size - identity_pool_status.locked - 1) + >= self.spec.min_ready_identities + { + identity_pool_status.phase = IdentityPoolPhaseEnum::Ready; + } else { + identity_pool_status.phase = IdentityPoolPhaseEnum::OutOfSync; + } + identity_pool_status.size = identity_pool_status.size - 1 + }; + + if phase.eq(&IdentityPoolPhaseEnum::Locked) { + if (identity_pool_status.size - identity_pool_status.locked - 1) + >= self.spec.min_ready_identities + { + identity_pool_status.phase = IdentityPoolPhaseEnum::Ready; + } else { + identity_pool_status.phase = IdentityPoolPhaseEnum::OutOfSync; + } + identity_pool_status.locked = identity_pool_status.locked + 1 + } else if phase.eq(&IdentityPoolPhaseEnum::Unlocked) { + if (identity_pool_status.size - identity_pool_status.locked + 1) + >= self.spec.min_ready_identities + { + identity_pool_status.phase = IdentityPoolPhaseEnum::Ready; + } else { + identity_pool_status.phase = IdentityPoolPhaseEnum::OutOfSync; + } + identity_pool_status.locked = identity_pool_status.locked - 1 + }; + let patch = Patch::Merge(json!({ + "status": identity_pool_status + })); + + match api + .patch(&identity_hoprd_name, &PatchParams::default(), &patch) + .await + { + Ok(_identity) => { + self.status = Some(identity_pool_status); + Ok(()) + }, + Err(error) => Ok(error!( + "Could not update status on {identity_hoprd_name}: {:?}", + error + )), + } + } + } + + /// Gets the first identity in ready status + pub async fn get_ready_identity(&mut self, context_data: Arc, identity_name: Option) -> Result, Error> { + let api: Api = Api::namespaced(context_data.client.clone(),&self.namespace().unwrap().to_owned()); + let namespace_identities_list = api.list(&ListParams::default()).await; + let namespace_identities = namespace_identities_list.expect("Could not list namespace identities"); + let pool_identities: Vec = namespace_identities.iter().cloned() + .filter(|identity| { + identity.metadata.owner_references.as_ref().unwrap().first().unwrap().name.eq(&self.name_any()) + }).collect(); + + let identity: Option = match identity_name.clone() { + Some(provided_identity_name) => { + let found = pool_identities.iter().find(|&identity| identity.metadata.name.clone().unwrap().eq(&provided_identity_name)).cloned(); + if found.is_none() { + warn!("The identity provided {} does not exist", provided_identity_name); + None + } else if found.as_ref().unwrap().status.as_ref().unwrap().phase.eq(&IdentityHoprdPhaseEnum::Ready) { + found + } else { + let status = found.as_ref().unwrap().status.as_ref().unwrap().to_owned(); + warn!("The identity {} is in phase {} and might be used by {}", provided_identity_name, status.phase, status.hoprd_node_name.unwrap_or("unknown".to_owned())); + None + } + }, + None => { // No identity has been provided + let ready_pool_identity: Option = pool_identities.iter().cloned() + .find(|identity| identity.status.as_ref().unwrap().phase.eq(&IdentityHoprdPhaseEnum::Ready)); + if ready_pool_identity.is_none() { + warn!("There are no identities ready to be used in this pool {}", self.name_any()); + } + ready_pool_identity + } + }; + return Ok(identity) + + } + + async fn are_active_jobs(&self, context: Arc) -> Result { + let namespace: String = self.metadata.namespace.as_ref().unwrap().to_owned(); + let api: Api = Api::namespaced(context.client.clone(), &namespace); + let label_selector: String = format!( + "{}={},{}={},{}={}", + constants::LABEL_KUBERNETES_NAME, + context.config.instance.name.to_owned(), + constants::LABEL_KUBERNETES_COMPONENT, + "create-identity".to_owned(), + constants::LABEL_KUBERNETES_IDENTITY_POOL, + self.name_any() + ); + let lp = ListParams::default().labels(&label_selector); + let jobs = api.list(&lp).await.unwrap().items; + let active_jobs: Vec<&Job> = jobs.iter().filter(|&job| job.status.as_ref().unwrap().active.is_some()).collect(); + Ok(active_jobs.len() > 0) + } + + async fn create_new_identity(&self, context: Arc) -> Result<(), Error> { + let identity_name = format!("{}-{}", self.name_any(), self.status.as_ref().unwrap().size + 1); + self.create_event(context.clone(), IdentityPoolPhaseEnum::CreatingIdentity,Some(identity_name.to_owned())).await?; + let random_string: String = rand::thread_rng().sample_iter(&Alphanumeric).take(5).map(char::from).collect(); + let job_name: String = format!("create-identity-{}-{}", &identity_name.to_owned(), random_string.to_ascii_lowercase()); + let namespace: String = self.metadata.namespace.as_ref().unwrap().to_owned(); + let owner_references: Option> = Some(vec![self.controller_owner_ref(&()).unwrap()]); + let mut labels: BTreeMap = utils::common_lables(context.config.instance.name.to_owned(), Some(identity_name.to_owned()), Some("job-create-identity".to_owned())); + labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), "create-identity".to_owned()); + labels.insert(constants::LABEL_KUBERNETES_IDENTITY_POOL.to_owned(), self.name_any()); + let create_identity_args: Vec = vec![format!("curl {}/create-identity.sh -s | bash", constants::OPERATOR_JOB_SCRIPT_URL.to_owned())]; + let create_resource_args: Vec = vec!["/app/hoprd-identity-created/create-resource.sh".to_owned()]; + let env_vars: Vec = vec![ + EnvVar { + name: constants::IDENTITY_POOL_IDENTITY_PASSWORD_REF_KEY.to_owned(), + value_from: Some(EnvVarSource { + secret_key_ref: Some(SecretKeySelector { + key: constants::IDENTITY_POOL_IDENTITY_PASSWORD_REF_KEY.to_owned(), + name: Some(self.spec.secret_name.to_owned()), + ..SecretKeySelector::default() + }), + ..EnvVarSource::default() + }), + ..EnvVar::default() + }, + EnvVar { + name: constants::IDENTITY_POOL_WALLET_PRIVATE_KEY_REF_KEY.to_owned(), + value_from: Some(EnvVarSource { + secret_key_ref: Some(SecretKeySelector { + key: constants::IDENTITY_POOL_WALLET_PRIVATE_KEY_REF_KEY.to_owned(), + name: Some(self.spec.secret_name.to_owned()), + ..SecretKeySelector::default() + }), + ..EnvVarSource::default() + }), + ..EnvVar::default() + }, + EnvVar { + name: "JOB_SCRIPT_URL".to_owned(), + value: Some(format!( + "{}/create-resource.sh", + constants::OPERATOR_JOB_SCRIPT_URL + )), + ..EnvVar::default() + }, + EnvVar { + name: "JOB_NAMESPACE".to_owned(), + value: Some(namespace.to_owned()), + ..EnvVar::default() + }, + EnvVar { + name: "IDENTITY_POOL_NAME".to_owned(), + value: Some(self.name_any()), + ..EnvVar::default() + }, + EnvVar { + name: "IDENTITY_NAME".to_owned(), + value: Some(identity_name.to_owned()), + ..EnvVar::default() + }, + EnvVar { + name: constants::HOPRD_NETWORK.to_owned(), + value: Some(self.spec.network.to_owned()), + ..EnvVar::default() + }, + ]; + let volumes: Vec = vec![Volume { + name: "hoprd-identity-created".to_owned(), + empty_dir: Some(EmptyDirVolumeSource::default()), + ..Volume::default() + }]; + let volume_mounts: Vec = vec![VolumeMount { + name: "hoprd-identity-created".to_owned(), + mount_path: "/app/hoprd-identity-created".to_owned(), + ..VolumeMount::default() + }]; + + // Definition of the Job + let create_node_job: Job = Job { + metadata: ObjectMeta { + name: Some(job_name.to_owned()), + namespace: Some(namespace.to_owned()), + owner_references, + labels: Some(labels.clone()), + ..ObjectMeta::default() + }, + spec: Some(JobSpec { + parallelism: Some(1), + completions: Some(1), + backoff_limit: Some(1), + active_deadline_seconds: Some(constants::OPERATOR_JOB_TIMEOUT.try_into().unwrap()), + template: PodTemplateSpec { + spec: Some(PodSpec { + init_containers: Some(vec![Container { + name: "hopli".to_owned(), + image: Some(context.config.hopli_image.to_owned()), + image_pull_policy: Some("Always".to_owned()), + command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), + args: Some(create_identity_args), + env: Some(env_vars.to_owned()), + volume_mounts: Some(volume_mounts.to_owned()), + resources: Some(HoprdDeploymentSpec::get_resource_requirements(None)), + ..Container::default() + }]), + containers: vec![Container { + name: "kubectl".to_owned(), + image: Some("registry.hub.docker.com/bitnami/kubectl:1.24".to_owned()), + image_pull_policy: Some("IfNotPresent".to_owned()), + command: Some(vec!["/bin/bash".to_owned(), "-c".to_owned()]), + args: Some(create_resource_args), + env: Some(env_vars), + volume_mounts: Some(volume_mounts), + resources: Some(HoprdDeploymentSpec::get_resource_requirements(None)), + ..Container::default() + }], + service_account_name: Some(self.name_any()), + volumes: Some(volumes), + restart_policy: Some("Never".to_owned()), + ..PodSpec::default() + }), + metadata: Some(ObjectMeta { + labels: Some(labels), + ..ObjectMeta::default() + }), + }, + ..JobSpec::default() + }), + ..Job::default() + }; + // Create the Job defined above + info!("Job {} started", &job_name.to_owned()); + let api: Api = Api::namespaced(context.client.clone(), &namespace); + api.create(&PostParams::default(), &create_node_job).await.unwrap(); + let job_completed = await_condition(api, &job_name, conditions::is_job_completed()); + match tokio::time::timeout(std::time::Duration::from_secs(constants::OPERATOR_JOB_TIMEOUT), job_completed).await.unwrap() + { + Ok(job_option) => match job_option { + Some(job) => { + if job.status.unwrap().failed.is_none() { + Ok(info!("Job {} completed successfully", &job_name.to_owned())) + } else { + Err(Error::JobExecutionError(format!("Job pod execution for {} failed", &job_name.to_owned()).to_owned())) + } + } + None => Err(Error::JobExecutionError(format!("Job execution for {} failed", &job_name.to_owned()).to_owned())) + }, + Err(_error) => Err(Error::JobExecutionError(format!("Job timeout for {}", &job_name.to_owned()).to_owned())) + } + } +} diff --git a/src/identity_pool_service_account.rs b/src/identity_pool_service_account.rs new file mode 100644 index 0000000..2bc0ff3 --- /dev/null +++ b/src/identity_pool_service_account.rs @@ -0,0 +1,170 @@ +use k8s_openapi::api::core::v1::ServiceAccount; +use k8s_openapi::api::rbac::v1::{PolicyRule, Role, RoleBinding, RoleRef, Subject}; +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::api::{DeleteParams, PostParams}; +use kube::core::ObjectMeta; +use kube::runtime::wait::{await_condition, conditions}; +use kube::{Api, Client}; +use std::sync::Arc; +use tracing::{info,error}; + +use crate::context_data::ContextData; + +use crate::model::Error; +use crate::utils; + +pub async fn create_rbac( + context_data: Arc, + namespace: &String, + name: &String, + owner_references: Option>, +) -> Result<(), Error> { + create_service_account(context_data.clone(), namespace,name, owner_references.to_owned()).await.unwrap(); + create_role(context_data.clone(), namespace, name, owner_references.to_owned()).await.unwrap(); + create_role_binding(context_data.clone(), namespace, name, owner_references.to_owned()).await.unwrap(); + Ok(()) +} + +pub async fn delete_rbac(client: Client, namespace: &String, name: &String) -> Result<(), Error> { + delete_service_account(client.clone(), namespace, name).await?; + delete_role(client.clone(), namespace, name).await?; + delete_role_binding(client.clone(), namespace, name).await?; + Ok(()) +} + +/// Creates a new service Account for the IdentityPool +async fn create_service_account(context_data: Arc, namespace: &String, name: &String, owner_references: Option>) -> Result { + let labels = utils::common_lables(context_data.config.instance.name.to_owned(), Some(name.to_owned()), None); + let api: Api = Api::namespaced(context_data.client.clone(), namespace); + let service_account: ServiceAccount = ServiceAccount { + metadata: ObjectMeta { + labels: Some(labels.clone()), + name: Some(name.to_owned()), + namespace: Some(namespace.to_owned()), + owner_references, + ..ObjectMeta::default() + }, + ..ServiceAccount::default() + }; + match api.create(&PostParams::default(), &service_account).await { + Ok(sa) => Ok(sa), + Err(error) => { + error!("Could not create ServiceAccount {:?}", error); + Err(Error::HoprdConfigError(format!("Could not create ServiceAccount for {} in namespace {}.", name, namespace))) + } + } +} + +async fn create_role(context_data: Arc, namespace: &String, name: &String, owner_references: Option>) -> Result { + let labels = utils::common_lables(context_data.config.instance.name.to_owned(), Some(name.to_owned()), None); + let api: Api = Api::namespaced(context_data.client.clone(), namespace); + let role: Role = Role { + metadata: ObjectMeta { + labels: Some(labels.clone()), + name: Some(name.to_owned()), + namespace: Some(namespace.to_owned()), + owner_references, + ..ObjectMeta::default() + }, + rules: Some(vec![PolicyRule { + api_groups: Some(vec!["hoprnet.org".to_owned()]), + resources: Some(vec!["identityhoprds".to_owned()]), + verbs: vec![ + "get".to_owned(), + "list".to_owned(), + "watch".to_owned(), + "create".to_owned(), + "delete".to_owned(), + ], + ..PolicyRule::default() + }]), + }; + match api.create(&PostParams::default(), &role).await { + Ok(role) => Ok(role), + Err(error) => { + error!("Could not create Role {:?}", error); + Err(Error::HoprdConfigError(format!("Could not create Role for {} in namespace {}.", name, namespace))) + } + + } +} + +async fn create_role_binding(context_data: Arc, namespace: &String, name: &String, owner_references: Option>) -> Result { + let labels = utils::common_lables(context_data.config.instance.name.to_owned(), Some(name.to_owned()), None); + let api: Api = Api::namespaced(context_data.client.clone(), namespace); + let role_binding: RoleBinding = RoleBinding { + metadata: ObjectMeta { + labels: Some(labels.clone()), + name: Some(name.to_owned()), + namespace: Some(namespace.to_owned()), + owner_references, + ..ObjectMeta::default() + }, + role_ref: RoleRef { + name: name.to_owned(), + kind: "Role".to_owned(), + ..RoleRef::default() + }, + subjects: Some(vec![Subject { + name: name.to_owned(), + kind: "ServiceAccount".to_owned(), + ..Subject::default() + }]), + }; + match api.create(&PostParams::default(), &role_binding).await { + Ok(rb) => Ok(rb), + Err(error) => { + error!("Could not create RoleBinding {:?}", error); + Err(Error::HoprdConfigError(format!("Could not create RoleBinding for {} in namespace {}.", name, namespace))) + } + + } +} + +async fn delete_service_account(client: Client, name: &str, namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client, namespace); + if let Some(service_account) = api.get_opt(&name).await? { + let uid = service_account.metadata.uid.unwrap(); + api.delete(name, &DeleteParams::default()).await?; + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); + Ok(info!("ServiceAccount {name} successfully deleted")) + } else { + Ok(info!( + "ServiceAccount {name} in namespace {namespace} about to delete not found" + )) + } +} + +async fn delete_role(client: Client, name: &str, namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client, namespace); + if let Some(role) = api.get_opt(&name).await? { + let uid = role.metadata.uid.unwrap(); + api.delete(name, &DeleteParams::default()).await?; + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); + Ok(info!("Role {name} successfully deleted")) + } else { + Ok(info!( + "Role {name} in namespace {namespace} about to delete not found" + )) + } +} + +async fn delete_role_binding(client: Client, name: &str, namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client, namespace); + if let Some(role_binding) = api.get_opt(&name).await? { + let uid = role_binding.metadata.uid.unwrap(); + api.delete(name, &DeleteParams::default()).await?; + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); + Ok(info!("RoleBinding {name} successfully deleted")) + } else { + Ok(info!( + "RoleBinding {name} in namespace {namespace} about to delete not found" + )) + } +} diff --git a/src/identity_pool_service_monitor.rs b/src/identity_pool_service_monitor.rs new file mode 100644 index 0000000..bae84e8 --- /dev/null +++ b/src/identity_pool_service_monitor.rs @@ -0,0 +1,211 @@ +use k8s_openapi::apimachinery::pkg::apis::meta::v1::OwnerReference; +use kube::api::{DeleteParams, PostParams}; +use kube::core::ObjectMeta; +use kube::runtime::wait::{await_condition, conditions}; +use kube::Error; +use kube::{Api, Client}; +use std::collections::BTreeMap; +use std::sync::Arc; +use tracing::info; + +use crate::context_data::ContextData; +use crate::servicemonitor::{ + ServiceMonitorEndpoints, ServiceMonitorEndpointsBasicAuth, + ServiceMonitorEndpointsBasicAuthPassword, ServiceMonitorEndpointsBasicAuthUsername, + ServiceMonitorEndpointsRelabelings, ServiceMonitorEndpointsRelabelingsAction, + ServiceMonitorNamespaceSelector, ServiceMonitorSelector, ServiceMonitorSpec, +}; +use crate::{constants, servicemonitor::ServiceMonitor}; + +/// Creates a new serviceMonitor to enable the monitoring with Prometheus of the hoprd node, +/// +/// # Arguments +/// - `client` - A Kubernetes client to create the deployment with. +/// - `name` - Name of the deployment to be created +/// - `namespace` - Namespace to create the Kubernetes Deployment in. +/// - `hoprd_spec` - Details about the hoprd configuration node +/// +pub async fn create_service_monitor(context_data: Arc, name: &str, namespace: &str, secret_name: &String, owner_references: Option>) -> Result { + let mut labels: BTreeMap = BTreeMap::new(); + labels.insert(constants::LABEL_KUBERNETES_NAME.to_owned(), context_data.config.instance.name.to_owned()); + labels.insert(constants::LABEL_KUBERNETES_IDENTITY_POOL.to_owned(),name.to_owned()); + + let api: Api = Api::namespaced(context_data.client.clone(), namespace); + + let service_monitor: ServiceMonitor = ServiceMonitor { + metadata: ObjectMeta { + labels: Some(labels.clone()), + name: Some(name.to_owned()), + namespace: Some(namespace.to_owned()), + owner_references, + ..ObjectMeta::default() + }, + spec: ServiceMonitorSpec { + endpoints: vec![ServiceMonitorEndpoints { + interval: Some("15s".to_owned()), + path: Some("/api/v3/node/metrics".to_owned()), + port: Some("api".to_owned()), + basic_auth: Some(ServiceMonitorEndpointsBasicAuth { + username: Some(ServiceMonitorEndpointsBasicAuthUsername { + key: constants::IDENTITY_POOL_API_TOKEN_REF_KEY.to_owned(), + name: Some(secret_name.to_owned()), + optional: Some(false), + }), + password: Some(ServiceMonitorEndpointsBasicAuthPassword { + key: constants::IDENTITY_POOL_METRICS_PASSWORD_REF_KEY.to_owned(), + name: Some(secret_name.to_owned()), + optional: Some(false), + }), + }), + authorization: None, + bearer_token_file: None, + bearer_token_secret: None, + follow_redirects: None, + honor_labels: None, + honor_timestamps: None, + metric_relabelings: None, + oauth2: None, + params: None, + proxy_url: None, + relabelings: Some(build_metric_relabel()), + scheme: None, + scrape_timeout: None, + target_port: None, + tls_config: None, + }], + job_label: Some(name.to_owned()), + namespace_selector: Some(ServiceMonitorNamespaceSelector { + match_names: Some(vec![namespace.to_owned()]), + any: Some(false), + }), + selector: ServiceMonitorSelector { + match_labels: Some(labels), + match_expressions: None, + }, + label_limit: None, + label_name_length_limit: None, + label_value_length_limit: None, + pod_target_labels: None, + sample_limit: None, + target_labels: None, + target_limit: None, + }, + }; + + // Create the serviceMonitor defined above + api.create(&PostParams::default(), &service_monitor).await +} + +pub fn build_metric_relabel() -> Vec { + let mut metrics = Vec::with_capacity(2); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_hoprds_hoprnet_org_network".to_owned(), + ]), + target_label: Some("hoprd_network".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec!["__meta_kubernetes_pod_container_image".to_owned()]), + target_label: Some("hoprd_version".to_owned()), + regex: Some("(.*):(.*)".to_owned()), + replacement: Some("${2}".to_owned()), + separator: Some(":".to_owned()), + modulus: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_app_kubernetes_io_instance".to_owned(), + ]), + target_label: Some("node_name".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_app_kubernetes_io_instance".to_owned(), + ]), + target_label: Some("nodename".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_hoprds_hoprnet_org_safe_address".to_owned(), + ]), + target_label: Some("hoprd_safe_address".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_hoprds_hoprnet_org_module_address".to_owned(), + ]), + target_label: Some("hoprd_module_address".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_hoprds_hoprnet_org_address".to_owned(), + ]), + target_label: Some("hoprd_address".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics.push(ServiceMonitorEndpointsRelabelings { + action: Some(ServiceMonitorEndpointsRelabelingsAction::Replace), + source_labels: Some(vec![ + "__meta_kubernetes_pod_label_hoprds_hoprnet_org_peerId".to_owned(), + ]), + target_label: Some("hoprd_peer_id".to_owned()), + modulus: None, + regex: None, + replacement: None, + separator: None, + }); + metrics +} + +/// Deletes an existing serviceMonitor. +/// +/// # Arguments: +/// - `client` - A Kubernetes client to delete the ServiceMonitor with +/// - `name` - Name of the ServiceMonitor to delete +/// - `namespace` - Namespace the existing ServiceMonitor resides in +/// +pub async fn delete_service_monitor(client: Client, name: &str,namespace: &str) -> Result<(), Error> { + let api: Api = Api::namespaced(client, namespace); + if let Some(service_monitor) = api.get_opt(&name).await? { + let uid = service_monitor.metadata.uid.unwrap(); + api.delete(name, &DeleteParams::default()).await?; + await_condition(api, &name.to_owned(), conditions::is_deleted(&uid)) + .await + .unwrap(); + Ok(info!("ServiceMonitor {name} successfully deleted")) + } else { + Ok(info!( + "ServiceMonitor {name} in namespace {namespace} about to delete not found" + )) + } +} diff --git a/src/main.rs b/src/main.rs index 34cb6a1..2e52d2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,58 +1,70 @@ -use std::{sync::{Arc}, env}; -use kube::{Result, Client}; +use kube::{Client, Result}; +use std::{env, sync::Arc}; pub mod model; -mod hoprd_persistence; -mod operator_config; + +mod bootstrap_operator; +mod cluster; +mod constants; +mod context_data; +mod controller_cluster; +mod controller_hoprd; +mod controller_identity; +mod controller_identity_pool; +mod hoprd; mod hoprd_deployment; mod hoprd_deployment_spec; -mod hoprd; -mod cluster; -mod bootstrap_operator; mod hoprd_ingress; -mod hoprd_jobs; -mod hoprd_secret; mod hoprd_service; -mod hoprd_service_monitor; +mod identity_hoprd; +mod identity_hoprd_persistence; +mod identity_pool; +mod identity_pool_service_account; +mod identity_pool_service_monitor; +mod operator_config; mod servicemonitor; -mod controller_hoprd; -mod controller_cluster; -mod context_data; mod utils; -mod constants; -use crate:: context_data::ContextData; +use crate::context_data::ContextData; use futures::{ future::FutureExt, // for `.fuse()` pin_mut, select, }; -use tracing::{info}; -use tracing_subscriber::{FmtSubscriber, filter::{EnvFilter}}; +use tracing::info; +use tracing_subscriber::{filter::EnvFilter, FmtSubscriber}; #[tokio::main] async fn main() -> Result<()> { - // Initialize Tracing crate - let subscriber = FmtSubscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .finish(); + let subscriber = FmtSubscriber::builder().with_env_filter(EnvFilter::from_default_env()).finish(); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let version: &str = env!("CARGO_PKG_VERSION"); info!("Starting hoprd-operator {}", version); let client: Client = Client::try_default().await.expect("Failed to create kube Client"); let context_data: Arc = Arc::new(ContextData::new(client.clone()).await); + ContextData::sync_identities(context_data.clone()).await; // Initiatilize Kubernetes controller state bootstrap_operator::start(client.clone(), context_data.clone()).await; + let controller_identity_pool = + controller_identity_pool::run(client.clone(), context_data.clone()).fuse(); + let controller_identity_hoprd = + controller_identity::run(client.clone(), context_data.clone()).fuse(); let controller_hoprd = controller_hoprd::run(client.clone(), context_data.clone()).fuse(); let controller_cluster = controller_cluster::run(client.clone(), context_data.clone()).fuse(); - pin_mut!(controller_hoprd, controller_cluster); + pin_mut!( + controller_identity_pool, + controller_identity_hoprd, + controller_hoprd, + controller_cluster + ); select! { + () = controller_identity_pool => println!("Controller IdentityPool exited"), + () = controller_identity_hoprd => println!("Controller IdentityHoprd exited"), () = controller_hoprd => println!("Controller Hoprd exited"), () = controller_cluster => println!("Controller ClusterHoprd exited"), } Ok(()) } - diff --git a/src/model.rs b/src/model.rs index f070f5a..a592dff 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,98 +1,3 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use std::{fmt::{self, Display}}; - - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] -pub enum HoprdStatusEnum { - // The node is not yet created - Initializing, - // The node repo is being created - Creating, - // The node is not registered - RegisteringInNetwork, - /// The node is not funded - Funding, - /// The node is stopped - Stopped, - /// The node is running - Running, - /// The node is reconfigured - Reloading, - /// The node is being deleted - Deleting, - /// The node is deleted - Deleted, - /// The node is not sync - OutOfSync -} - -impl Display for HoprdStatusEnum { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - HoprdStatusEnum::Initializing => write!(f, "Initializing"), - HoprdStatusEnum::Creating => write!(f, "Creating"), - HoprdStatusEnum::RegisteringInNetwork => write!(f, "RegisteringInNetwork"), - HoprdStatusEnum::Funding => write!(f, "Funding"), - HoprdStatusEnum::Stopped => write!(f, "Stopped"), - HoprdStatusEnum::Running => write!(f, "Running"), - HoprdStatusEnum::Reloading => write!(f, "Reloading"), - HoprdStatusEnum::Deleting => write!(f, "Deleting"), - HoprdStatusEnum::Deleted => write!(f, "Deleted"), - HoprdStatusEnum::OutOfSync => write!(f, "OutOfSync") - } - } -} - - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, JsonSchema, Copy)] -pub enum ClusterHoprdStatusEnum { - // The HoprdCluster is initializing the nodes - Initializing, - // The HoprdCluster is synching with its nodes - Synching, - // The HoprdCluster is synchronized with its nodes - InSync, - /// The HoprdCluster is being deleted - Deleting, - /// The HoprdCluster is not synchronized with its nodes - OutOfSync -} - -impl Display for ClusterHoprdStatusEnum { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ClusterHoprdStatusEnum::Initializing => write!(f, "Initializing"), - ClusterHoprdStatusEnum::Synching => write!(f, "Synching"), - ClusterHoprdStatusEnum::InSync => write!(f, "InSync"), - ClusterHoprdStatusEnum::Deleting => write!(f, "Deleting"), - ClusterHoprdStatusEnum::OutOfSync => write!(f, "OutOfSync") - } - } -} - -/// Struct corresponding to the details of the secret which contains the sensitive data to run the node -#[derive(Serialize, Debug, Default, Deserialize, PartialEq, Clone, JsonSchema, Hash)] -#[serde(rename_all = "camelCase")] -pub struct HoprdSecret { - - pub secret_name: String, - - pub password_ref_key: Option, - - pub api_token_ref_key: Option, - - pub identity_ref_key: Option, - - pub metrics_password_ref_key: Option -} - -#[derive(Serialize, Debug, Deserialize, PartialEq, Clone, JsonSchema, Hash)] -pub struct EnablingFlag { - pub enabled: bool -} - - /// All errors possible to occur during reconciliation #[derive(Debug, thiserror::Error)] pub enum Error { @@ -108,7 +13,7 @@ pub enum Error { /// The secret is in an Unknown status #[error("Invalid Hoprd Secret status: {0}")] - SecretStatusError(String), + IdentityStatusError(String), /// The hoprd is in an Unknown status #[error("Invalid Hoprd status: {0}")] @@ -125,4 +30,12 @@ pub enum Error { /// The Job execution did not complete successfully #[error("Job Execution failed: {0}")] JobExecutionError(String), -} \ No newline at end of file + + /// The Job execution did not complete successfully + #[error("This action is not supported: {0}")] + OperationNotSupported(String), + + /// The Job execution did not complete successfully + #[error("There is an issue with the identity: {0}")] + IdentityIssue(String), +} diff --git a/src/operator_config.rs b/src/operator_config.rs index 7cd2329..8a8a0c9 100644 --- a/src/operator_config.rs +++ b/src/operator_config.rs @@ -1,30 +1,27 @@ use serde::{Deserialize, Serialize}; -use std::{collections::{BTreeMap}}; - +use std::collections::BTreeMap; #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Hash)] pub struct OperatorConfig { pub instance: OperatorInstance, pub ingress: IngressConfig, pub hopli_image: String, - pub persistence: PersistenceConfig + pub persistence: PersistenceConfig, } - #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Hash)] pub struct OperatorInstance { pub name: String, pub namespace: String, - pub secret_name: String } - #[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Hash)] pub struct IngressConfig { pub ingress_class_name: String, pub dns_domain: String, - pub namespace: Option, + pub namespace: Option, pub annotations: Option>, + pub public_ip: Option, pub p2p_port_min: Option, pub p2p_port_max: Option, pub selector_labels: Option>, diff --git a/src/servicemonitor.rs b/src/servicemonitor.rs index e15b1f9..015ac2a 100644 --- a/src/servicemonitor.rs +++ b/src/servicemonitor.rs @@ -4,45 +4,82 @@ use kube::CustomResource; use schemars::JsonSchema; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; /// Specification of desired Service selection for target discovery by Prometheus. #[derive(CustomResource, Serialize, Deserialize, Clone, Debug, JsonSchema)] -#[kube(group = "monitoring.coreos.com", version = "v1", kind = "ServiceMonitor", plural = "servicemonitors")] +#[kube( + group = "monitoring.coreos.com", + version = "v1", + kind = "ServiceMonitor", + plural = "servicemonitors" +)] #[kube(namespaced)] pub struct ServiceMonitorSpec { /// A list of endpoints allowed as part of this ServiceMonitor. pub endpoints: Vec, - /// Chooses the label of the Kubernetes `Endpoints`. Its value will be used for the `job`-label's value of the created metrics. + /// Chooses the label of the Kubernetes `Endpoints`. Its value will be used for the `job`-label's value of the created metrics. /// Default & fallback value: the name of the respective Kubernetes `Endpoint`. #[serde(default, skip_serializing_if = "Option::is_none", rename = "jobLabel")] pub job_label: Option, /// Per-scrape limit on number of labels that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelLimit")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "labelLimit" + )] pub label_limit: Option, /// Per-scrape limit on length of labels name that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelNameLengthLimit")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "labelNameLengthLimit" + )] pub label_name_length_limit: Option, /// Per-scrape limit on length of labels value that will be accepted for a sample. Only valid in Prometheus versions 2.27.0 and newer. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "labelValueLengthLimit")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "labelValueLengthLimit" + )] pub label_value_length_limit: Option, /// Selector to select which namespaces the Kubernetes Endpoints objects are discovered from. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "namespaceSelector")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "namespaceSelector" + )] pub namespace_selector: Option, /// PodTargetLabels transfers labels on the Kubernetes `Pod` onto the created metrics. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "podTargetLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "podTargetLabels" + )] pub pod_target_labels: Option>, /// SampleLimit defines per-scrape limit on number of scraped samples that will be accepted. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "sampleLimit")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "sampleLimit" + )] pub sample_limit: Option, /// Selector to select Endpoints objects. pub selector: ServiceMonitorSelector, /// TargetLabels transfers labels from the Kubernetes `Service` onto the created metrics. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "targetLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "targetLabels" + )] pub target_labels: Option>, /// TargetLimit defines a limit on the number of scraped targets that will be accepted. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "targetLimit")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "targetLimit" + )] pub target_limit: Option, } @@ -56,25 +93,49 @@ pub struct ServiceMonitorEndpoints { #[serde(default, skip_serializing_if = "Option::is_none", rename = "basicAuth")] pub basic_auth: Option, /// File to read bearer token for scraping targets. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "bearerTokenFile")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "bearerTokenFile" + )] pub bearer_token_file: Option, /// Secret to mount to read bearer token for scraping targets. The secret needs to be in the same namespace as the service monitor and accessible by the Prometheus Operator. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "bearerTokenSecret")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "bearerTokenSecret" + )] pub bearer_token_secret: Option, /// FollowRedirects configures whether scrape requests follow HTTP 3xx redirects. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "followRedirects")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "followRedirects" + )] pub follow_redirects: Option, /// HonorLabels chooses the metric's labels on collisions with target labels. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "honorLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "honorLabels" + )] pub honor_labels: Option, /// HonorTimestamps controls whether Prometheus respects the timestamps present in scraped data. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "honorTimestamps")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "honorTimestamps" + )] pub honor_timestamps: Option, /// Interval at which metrics should be scraped If not specified Prometheus' global scrape interval is used. #[serde(default, skip_serializing_if = "Option::is_none")] pub interval: Option, /// MetricRelabelConfigs to apply to samples before ingestion. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "metricRelabelings")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "metricRelabelings" + )] pub metric_relabelings: Option>, /// OAuth2 for the URL. Only valid in Prometheus versions 2.27.0 and newer. #[serde(default, skip_serializing_if = "Option::is_none")] @@ -98,10 +159,18 @@ pub struct ServiceMonitorEndpoints { #[serde(default, skip_serializing_if = "Option::is_none")] pub scheme: Option, /// Timeout after which the scrape is ended If not specified, the Prometheus global scrape timeout is used unless it is less than `Interval` in which the latter is used. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "scrapeTimeout")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "scrapeTimeout" + )] pub scrape_timeout: Option, /// Name or number of the target port of the Pod behind the Service, the port must be specified with container port property. Mutually exclusive with port. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "targetPort")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "targetPort" + )] pub target_port: Option, /// TLS configuration to use when scraping the endpoint #[serde(default, skip_serializing_if = "Option::is_none", rename = "tlsConfig")] @@ -201,10 +270,18 @@ pub struct ServiceMonitorEndpointsMetricRelabelings { #[serde(default, skip_serializing_if = "Option::is_none")] pub separator: Option, /// The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "sourceLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "sourceLabels" + )] pub source_labels: Option>, /// Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "targetLabel")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "targetLabel" + )] pub target_label: Option, } @@ -237,7 +314,11 @@ pub struct ServiceMonitorEndpointsOauth2 { #[serde(rename = "clientSecret")] pub client_secret: ServiceMonitorEndpointsOauth2ClientSecret, /// Parameters to append to the token URL - #[serde(default, skip_serializing_if = "Option::is_none", rename = "endpointParams")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "endpointParams" + )] pub endpoint_params: Option>, /// OAuth2 scopes used for the token request #[serde(default, skip_serializing_if = "Option::is_none")] @@ -316,10 +397,18 @@ pub struct ServiceMonitorEndpointsRelabelings { #[serde(default, skip_serializing_if = "Option::is_none")] pub separator: Option, /// The source labels select values from existing labels. Their content is concatenated using the configured separator and matched against the configured regular expression for the replace, keep, and drop actions. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "sourceLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "sourceLabels" + )] pub source_labels: Option>, /// Label to which the resulting value is written in a replace action. It is mandatory for replace actions. Regex capture groups are available. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "targetLabel")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "targetLabel" + )] pub target_label: Option, } @@ -358,7 +447,11 @@ pub struct ServiceMonitorEndpointsTlsConfig { #[serde(default, skip_serializing_if = "Option::is_none", rename = "certFile")] pub cert_file: Option, /// Disable target certificate validation. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "insecureSkipVerify")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "insecureSkipVerify" + )] pub insecure_skip_verify: Option, /// Path to the client key file in the Prometheus container for the targets. #[serde(default, skip_serializing_if = "Option::is_none", rename = "keyFile")] @@ -367,7 +460,11 @@ pub struct ServiceMonitorEndpointsTlsConfig { #[serde(default, skip_serializing_if = "Option::is_none", rename = "keySecret")] pub key_secret: Option, /// Used to verify the hostname for the targets. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "serverName")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "serverName" + )] pub server_name: Option, } @@ -465,7 +562,11 @@ pub struct ServiceMonitorNamespaceSelector { #[serde(default, skip_serializing_if = "Option::is_none")] pub any: Option, /// List of namespace names to select from. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchNames")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "matchNames" + )] pub match_names: Option>, } @@ -473,10 +574,18 @@ pub struct ServiceMonitorNamespaceSelector { #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] pub struct ServiceMonitorSelector { /// matchExpressions is a list of label selector requirements. The requirements are ANDed. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchExpressions")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "matchExpressions" + )] pub match_expressions: Option>, /// matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - #[serde(default, skip_serializing_if = "Option::is_none", rename = "matchLabels")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + rename = "matchLabels" + )] pub match_labels: Option>, } @@ -491,4 +600,3 @@ pub struct ServiceMonitorSelectorMatchExpressions { #[serde(default, skip_serializing_if = "Option::is_none")] pub values: Option>, } - diff --git a/src/utils.rs b/src/utils.rs index ec35079..e077d6d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,75 +1,101 @@ -use std::{collections::{BTreeMap}, fmt::{Display, Formatter, self}}; -use json_patch::{PatchOperation, ReplaceOperation}; -use k8s_openapi::{api::{core::v1::{ Secret}}}; -use kube::{Api, api::{ Patch, PatchParams}, Client}; -use serde_json::{Value, json}; -use crate::{constants, model::{Error}, hoprd::{Hoprd}, cluster::ClusterHoprd}; +use crate::{cluster::ClusterHoprd, constants, hoprd::Hoprd, identity_pool::IdentityPool}; +use kube::{Api, Client}; +use std::{ + collections::BTreeMap, + fmt::{self, Display, Formatter}, +}; -pub fn common_lables(instance_name: &String) -> BTreeMap { +pub fn common_lables(name: String, instance: Option, component: Option) -> BTreeMap { let mut labels: BTreeMap = BTreeMap::new(); - labels.insert(constants::LABEL_KUBERNETES_NAME.to_owned(), "hoprd".to_owned()); - labels.insert(constants::LABEL_KUBERNETES_INSTANCE.to_owned(), instance_name.to_owned()); + labels.insert(constants::LABEL_KUBERNETES_NAME.to_owned(), name); + match instance { + Some(instance) => { + labels.insert(constants::LABEL_KUBERNETES_INSTANCE.to_owned(), instance); + } + None => {} + } + match component { + Some(component) => { + labels.insert(constants::LABEL_KUBERNETES_COMPONENT.to_owned(), component); + } + None => {} + } return labels; } pub enum ResourceType { - Secret, + IdentityPool, Hoprd, - ClusterHoprd + ClusterHoprd, } impl Display for ResourceType { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { - ResourceType::Secret => write!(f, "Secret"), + ResourceType::IdentityPool => write!(f, "IdentityPool"), ResourceType::Hoprd => write!(f, "Hoprd"), - ResourceType::ClusterHoprd => write!(f, "ClusterHoprd") + ResourceType::ClusterHoprd => write!(f, "ClusterHoprd"), } } } -#[derive( PartialEq, Clone)] +#[derive(PartialEq, Clone)] pub enum ResourceKind { Labels, - Annotations + Annotations, } impl Display for ResourceKind { fn fmt(&self, f: &mut Formatter) -> fmt::Result { match self { ResourceKind::Labels => write!(f, "Labels"), - ResourceKind::Annotations => write!(f, "Annotations") + ResourceKind::Annotations => write!(f, "Annotations"), } } } - -pub async fn get_resource_kinds(client: Client, resource_type: ResourceType, resource_kind: ResourceKind, resource_name: &str, resource_namespace: &str) -> BTreeMap { +pub async fn get_resource_kinds( + client: Client, + resource_type: ResourceType, + resource_kind: ResourceKind, + resource_name: &str, + resource_namespace: &str, +) -> BTreeMap { let empty_map: &BTreeMap = &BTreeMap::new(); match resource_type { - ResourceType::Secret => { - let api_secret: Api = Api::namespaced(client.clone(), &resource_namespace); - match api_secret.get_opt(&resource_name).await.unwrap() { - Some(secret) => { + ResourceType::IdentityPool => { + let api_identity: Api = Api::namespaced(client.clone(), &resource_namespace); + match api_identity.get_opt(&resource_name).await.unwrap() { + Some(identity) => { if resource_kind.eq(&ResourceKind::Labels) { - secret.metadata.labels.as_ref().unwrap_or_else(|| empty_map).clone() + identity.metadata.labels.as_ref().unwrap_or_else(|| empty_map).clone() } else { - secret.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone() + identity.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone() } } None => { - println!("The secret {resource_name} does not exist."); + println!("The identity pool {resource_name} does not exist."); empty_map.clone() } } - } - ResourceType::Hoprd => { + }, + ResourceType::Hoprd => { let api_hoprd: Api = Api::namespaced(client.clone(), &resource_namespace); match api_hoprd.get_opt(&resource_name).await.unwrap() { - Some(hoprd) => { + Some(hoprd) => { if resource_kind.eq(&ResourceKind::Labels) { - hoprd.metadata.labels.as_ref().unwrap_or_else(|| empty_map).clone() + hoprd + .metadata + .labels + .as_ref() + .unwrap_or_else(|| empty_map) + .clone() } else { - hoprd.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone() + hoprd + .metadata + .annotations + .as_ref() + .unwrap_or_else(|| empty_map) + .clone() } } None => { @@ -78,14 +104,25 @@ pub async fn get_resource_kinds(client: Client, resource_type: ResourceType, res } } } - ResourceType::ClusterHoprd => { - let api_cluster_hoprd: Api = Api::namespaced(client.clone(), &resource_namespace); + ResourceType::ClusterHoprd => { + let api_cluster_hoprd: Api = + Api::namespaced(client.clone(), &resource_namespace); match api_cluster_hoprd.get_opt(&resource_name).await.unwrap() { - Some(hoprd) => { + Some(hoprd) => { if resource_kind.eq(&ResourceKind::Labels) { - hoprd.metadata.labels.as_ref().unwrap_or_else(|| empty_map).clone() + hoprd + .metadata + .labels + .as_ref() + .unwrap_or_else(|| empty_map) + .clone() } else { - hoprd.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone() + hoprd + .metadata + .annotations + .as_ref() + .unwrap_or_else(|| empty_map) + .clone() } } None => { @@ -96,81 +133,3 @@ pub async fn get_resource_kinds(client: Client, resource_type: ResourceType, res } } } - - -pub async fn update_secret_annotations(api_secret: &Api, secret_name: &str, annotation_name: &str, annotation_value: &str) -> Result { - match api_secret.get_opt(&secret_name).await.unwrap() { - Some(secret) => { - let empty_map = &mut BTreeMap::new(); - let mut hoprd_annotations: BTreeMap = secret.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone(); - if hoprd_annotations.contains_key(annotation_name) { - *hoprd_annotations.get_mut(annotation_name).unwrap() = annotation_value.to_string(); - } else { - hoprd_annotations.insert(annotation_name.to_string(), annotation_value.to_string()); - } - let secret_patch_object: Value = json!({ - "metadata": { - "annotations": hoprd_annotations - } - }); - let patch: Patch<&Value> = Patch::Merge(&secret_patch_object); - Ok(api_secret.patch(&secret_name, &PatchParams::default(), &patch).await?) - } - None => { - return Err(Error::SecretStatusError(format!("[ERROR] The secret specified {secret_name} does not exist").to_owned() - )); - } - } -} - -pub async fn delete_secret_annotations(api: &Api, secret_name: &str, annotation_name: &str) -> Result { - match api.get_opt(&secret_name).await.unwrap() { - Some(secret) => { - let empty_map = &mut BTreeMap::new(); - let mut hoprd_annotations: BTreeMap = secret.metadata.annotations.as_ref().unwrap_or_else(|| empty_map).clone(); - if hoprd_annotations.contains_key(annotation_name) { - hoprd_annotations.remove(annotation_name); - } else { - println!("[WARN] The secret {secret_name} does not contain the annotation {annotation_name}"); - } - let json_patch = json_patch::Patch(vec![PatchOperation::Replace(ReplaceOperation{ - path: "/metadata/annotations".to_owned(), - value: json!(hoprd_annotations) - })]); - let patch: Patch<&Value> = Patch::Json::<&Value>(json_patch); - api.patch(&secret_name, &PatchParams::default(), &patch).await?; - Ok(secret) - } - None => { - return Err(Error::SecretStatusError(format!("[ERROR] The secret specified {secret_name} does not exist").to_owned() - )); - } - } -} - -pub async fn update_secret_label(api_secret: &Api, secret_name: &str, label_name: &str, label_value: &String) -> Result { - match api_secret.get_opt(&secret_name).await.unwrap() { - Some(secret) => { - let empty_map = &mut BTreeMap::new(); - let mut hoprd_labels: BTreeMap = secret.metadata.labels.as_ref().unwrap_or_else(|| empty_map).clone(); - if hoprd_labels.contains_key(label_name) { - *hoprd_labels.get_mut(label_name).unwrap() = label_value.to_string(); - } else { - hoprd_labels.insert(label_name.to_string(), label_value.to_string()); - } - - let secret_patch_object: Value = json!({ - "metadata": { - "labels": hoprd_labels - } - }); - let patch: Patch<&Value> = Patch::Merge(&secret_patch_object); - Ok(api_secret.patch(&secret_name, &PatchParams::default(), &patch).await?) - } - None => { - return Err(Error::SecretStatusError(format!("[ERROR] The secret specified {secret_name} does not exist").to_owned() - )); - } - } -} - diff --git a/test-data/cluster-hoprd.yaml b/test-data/cluster-hoprd.yaml new file mode 100644 index 0000000..4b8ecb1 --- /dev/null +++ b/test-data/cluster-hoprd.yaml @@ -0,0 +1,101 @@ +--- +apiVersion: hoprnet.org/v1alpha2 +kind: ClusterHoprd +metadata: + name: pr-1234 + labels: + hoprds.hoprnet.org/pullRequest: "1234" + namespace: hoprd-operator +spec: + identityPoolName: pool-hoprd-operator + replicas: 1 + version: latest + enabled: true + config: | + hopr: + host: + address: !IPv4 0.0.0.0 + port: 9091 + db: + data: "/app/hoprd-db" + initialize: true + force_initialize: false + strategy: + on_fail_continue: true + allow_recursive: false + finalize_channel_closure: true + strategies: + - !Promiscuous + max_channels: 10 + network_quality_threshold: 0.5 + new_channel_stake: "1000000 HOPR" + minimum_node_balance: "10000000 HOPR" + min_network_size_samples: 20 + enforce_max_channels: true + - !AutoFunding + funding_amount: "1000000 HOPR" + min_stake_threshold: "100000 HOPR" + - !Aggregating + aggregation_threshold: 1000000 + unrealized_balance_ratio: 0.9 + aggregation_timeout: 60 + aggregate_on_channel_close: true + - !AutoRedeeming + redeem_only_aggregated: True + heartbeat: + variance: 2000 + interval: 20000 + threshold: 60000 + network_options: + min_delay: 1 + max_delay: 300 + quality_avg_window_size: 25 + quality_bad_threshold: 0.2 + quality_offline_threshold: 0.5 + quality_step: 0.1 + ignore_timeframe: 600 + backoff_exponent: 1.5 + backoff_min: 2 + backoff_max: 300 + healthcheck: + enable: true + host: 0.0.0.0 + port: 8080 + transport: + announce_local_addresses: false + prefer_local_addresses: false + protocol: + ack: + timeout: 15 + heartbeat: + timeout: 15 + msg: + timeout: 15 + ticket_aggregation: + timeout: 15 + chain: + network: rotsee + announce: true + provider: + check_unrealized_balance: true + safe_module: + safe_transaction_service_provider: https://safe-transaction.prod.hoprtech.net/ + safe_address: "0x0000000000000000000000000000000000000000" + module_address: "0x0000000000000000000000000000000000000000" + identity: + file: "/app/hoprd-db/.hoprd.id" + password: "" + private_key: + inbox: + capacity: 512 + max_age: 900 + excluded_tags: + - 0 + api: + enable: true + auth: !Token "" + host: + address: !IPv4 0.0.0.0 + port: 3001 + test: + use_weak_crypto: false \ No newline at end of file diff --git a/test-data/hoprd-node-core.yaml b/test-data/hoprd-node-core.yaml new file mode 100644 index 0000000..9a5d7d5 --- /dev/null +++ b/test-data/hoprd-node-core.yaml @@ -0,0 +1,99 @@ +--- +apiVersion: hoprnet.org/v1alpha2 +kind: Hoprd +metadata: + name: core-rotsee-1 + namespace: rotsee +spec: + version: latest + identityPoolName: core-rotsee + identityName: core-rotsee-1 + enabled: true + config: | + hopr: + host: + address: !IPv4 0.0.0.0 + port: 9091 + db: + data: "/app/hoprd-db" + initialize: true + force_initialize: false + strategy: + on_fail_continue: true + allow_recursive: false + finalize_channel_closure: true + strategies: + - !Promiscuous + max_channels: 10 + network_quality_threshold: 0.5 + new_channel_stake: "1000000 HOPR" + minimum_node_balance: "10000000 HOPR" + min_network_size_samples: 20 + enforce_max_channels: true + - !AutoFunding + funding_amount: "1000000 HOPR" + min_stake_threshold: "100000 HOPR" + - !Aggregating + aggregation_threshold: 1000000 + unrealized_balance_ratio: 0.9 + aggregation_timeout: 60 + aggregate_on_channel_close: true + - !AutoRedeeming + redeem_only_aggregated: True + heartbeat: + variance: 2000 + interval: 20000 + threshold: 60000 + network_options: + min_delay: 1 + max_delay: 300 + quality_avg_window_size: 25 + quality_bad_threshold: 0.2 + quality_offline_threshold: 0.5 + quality_step: 0.1 + ignore_timeframe: 600 + backoff_exponent: 1.5 + backoff_min: 2 + backoff_max: 300 + healthcheck: + enable: true + host: 0.0.0.0 + port: 8080 + transport: + announce_local_addresses: false + prefer_local_addresses: false + protocol: + ack: + timeout: 15 + heartbeat: + timeout: 15 + msg: + timeout: 15 + ticket_aggregation: + timeout: 15 + chain: + network: rotsee + announce: true + provider: + check_unrealized_balance: true + safe_module: + safe_transaction_service_provider: https://safe-transaction.prod.hoprtech.net/ + safe_address: "0x0000000000000000000000000000000000000000" + module_address: "0x0000000000000000000000000000000000000000" + identity: + file: "/app/hoprd-db/.hoprd.id" + password: "" + private_key: + inbox: + capacity: 512 + max_age: 900 + excluded_tags: + - 0 + api: + enable: true + auth: !Token "" + host: + address: !IPv4 0.0.0.0 + port: 3001 + test: + use_weak_crypto: false diff --git a/test-data/hoprd-node-operator.yaml b/test-data/hoprd-node-operator.yaml new file mode 100644 index 0000000..1cc845f --- /dev/null +++ b/test-data/hoprd-node-operator.yaml @@ -0,0 +1,100 @@ +--- +apiVersion: hoprnet.org/v1alpha2 +kind: Hoprd +metadata: + name: hoprd-node-1 + namespace: hoprd-operator +spec: + version: latest + identityPoolName: pool-hoprd-operator + identityName: pool-hoprd-operator-2 + enabled: true + config: | + hopr: + host: + address: !IPv4 0.0.0.0 + port: 9091 + db: + data: "/app/hoprd-db" + initialize: true + force_initialize: false + strategy: + on_fail_continue: true + allow_recursive: false + finalize_channel_closure: true + strategies: + - !Promiscuous + max_channels: 10 + network_quality_threshold: 0.5 + new_channel_stake: "1000000 HOPR" + minimum_node_balance: "10000000 HOPR" + min_network_size_samples: 20 + enforce_max_channels: true + - !AutoFunding + funding_amount: "1000000 HOPR" + min_stake_threshold: "100000 HOPR" + - !Aggregating + aggregation_threshold: 1000000 + unrealized_balance_ratio: 0.9 + aggregation_timeout: 60 + aggregate_on_channel_close: true + - !AutoRedeeming + redeem_only_aggregated: True + heartbeat: + variance: 2000 + interval: 20000 + threshold: 60000 + network_options: + min_delay: 1 + max_delay: 300 + quality_avg_window_size: 25 + quality_bad_threshold: 0.2 + quality_offline_threshold: 0.5 + quality_step: 0.1 + ignore_timeframe: 600 + backoff_exponent: 1.5 + backoff_min: 2 + backoff_max: 300 + healthcheck: + enable: true + host: 0.0.0.0 + port: 8080 + transport: + announce_local_addresses: false + prefer_local_addresses: false + protocol: + ack: + timeout: 15 + heartbeat: + timeout: 15 + msg: + timeout: 15 + ticket_aggregation: + timeout: 15 + chain: + network: rotsee + announce: true + provider: + check_unrealized_balance: true + safe_module: + safe_transaction_service_provider: https://safe-transaction.prod.hoprtech.net/ + safe_address: "0x0000000000000000000000000000000000000000" + module_address: "0x0000000000000000000000000000000000000000" + identity: + file: "/app/hoprd-db/.hoprd.id" + password: "" + private_key: + inbox: + capacity: 512 + max_age: 900 + excluded_tags: + - 0 + api: + enable: true + auth: !Token "" + host: + address: !IPv4 0.0.0.0 + port: 3001 + test: + use_weak_crypto: false + diff --git a/sample_config-prod.yaml b/test-data/sample_config-prod.yaml similarity index 95% rename from sample_config-prod.yaml rename to test-data/sample_config-prod.yaml index 04619cb..4177683 100644 --- a/sample_config-prod.yaml +++ b/test-data/sample_config-prod.yaml @@ -1,10 +1,10 @@ instance: name: "hoprd-operator" namespace: "hoprd-operator" - secret_name: "hoprd-operator" ingress: ingress_class_name: "nginx" dns_domain: "prod.hoprtech.net" + public_ip: 142.132.140.8 annotations: cert-manager.io/cluster-issuer: "linode-issuer" nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600" diff --git a/sample_config-rpch.yaml b/test-data/sample_config-rpch.yaml similarity index 100% rename from sample_config-rpch.yaml rename to test-data/sample_config-rpch.yaml diff --git a/sample_config-stage.yaml b/test-data/sample_config-stage.yaml similarity index 95% rename from sample_config-stage.yaml rename to test-data/sample_config-stage.yaml index 9783414..0c80400 100644 --- a/sample_config-stage.yaml +++ b/test-data/sample_config-stage.yaml @@ -1,10 +1,10 @@ instance: name: "hoprd-operator" namespace: "hoprd-operator" - secret_name: "hoprd-operator" ingress: ingress_class_name: "nginx" dns_domain: "stage.hoprtech.net" + public_ip: 157.90.129.92 annotations: cert-manager.io/cluster-issuer: "linode-issuer" nginx.ingress.kubernetes.io/proxy-connect-timeout: "3600"