diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..5d0eb2f1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +debug/ +target/ +**/*.rs.bk + +.idea/ +*.iws + +image.tar + +# We do NOT want to ignore .git because we use the `built` crate to gather the current git commit hash at built time +# This means we need the .git directory in our Docker image, it will be thrown away and won't be included in the final image diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..ea6646f3 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +ignore = E111,E501,E114 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..465d3a9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +--- +name: "🐛 Bug Report" +description: "If something isn't working as expected 🤔." +labels: ["type/bug"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to file a bug report! Please fill out this form as completely as possible. + + - type: input + attributes: + label: Affected version + description: Which version do you see this bug in? + + - type: textarea + attributes: + label: Current and expected behavior + description: A clear and concise description of what the operator is doing and what you would expect. + validations: + required: true + + - type: textarea + attributes: + label: Possible solution + description: "If you have suggestions on a fix for the bug." + + - type: textarea + attributes: + label: Additional context + description: "Add any other context about the problem here. Or a screenshot if applicable." + + - type: textarea + attributes: + label: Environment + description: | + What type of kubernetes cluster you are running aginst (k3s/eks/aks/gke/other) and any other information about your environment? + placeholder: | + Examples: + Output of `kubectl version --short` + + - type: dropdown + attributes: + label: Would you like to work on fixing this bug? + description: | + **NOTE**: Let us know if you would like to submit a PR for this. We are more than happy to help you through the process. + options: + - "yes" + - "no" + - "maybe" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..6f1fa288 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,12 @@ +--- +blank_issues_enabled: true +contact_links: + - name: Feature request + about: 🚀 Suggest an idea for this project + url: https://github.com/stackabletech/listener-operator/discussions/new?category=ideas + - name: 🙋🏾 Question + about: Use this to ask a question about this project + url: https://github.com/stackabletech/listener-operator/discussions/new?category=q-a + - name: Other issue + about: Open an issue that doesn't fit any other category + url: https://github.com/stackabletech/listener-operator/issues/new diff --git a/.github/ISSUE_TEMPLATE/new_version.md b/.github/ISSUE_TEMPLATE/new_version.md new file mode 100644 index 00000000..52550da8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_version.md @@ -0,0 +1,34 @@ +--- +name: New Version +about: Request support for a new product version +title: "[NEW VERSION]" +labels: '' +assignees: '' + +--- + +**Which new version of Stackable Listener Operator should we support?** + +Please specify the version, version range or version numbers to support, please also add these to the issue title + +**Additional information** + +If possible, provide a link to release notes/changelog + +**Changes required** + +Are there any upstream changes that we need to support? +e.g. new features, changed features, deprecated features etc. + + + +**Implementation checklist** + +Please don't change anything in this list. +Not all of these steps are necessary for all versions. + +- [ ] Update the Docker image +- [ ] Update documentation to include supported version(s) +- [ ] Update operator to support the new version (if needed) +- [ ] Update integration tests to test use the new versions (in addition or replacing old versions +- [ ] Update examples to use new versions diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..c2dd7295 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,18 @@ + +# Description + +*Please add a description here. This will become the commit message of the merge request later.* + + + +## Review Checklist + +- [ ] Code contains useful comments +- [ ] CRD change approved (or not applicable) +- [ ] (Integration-)Test cases added (or not applicable) +- [ ] Documentation added (or not applicable) +- [ ] Changelog updated (or not applicable) +- [ ] Cargo.toml only contains references to git tags (not specific commits or branches) +- [ ] Helm chart can be installed and deployed operator works (or not applicable) + +Once the review is done, comment `bors r+` (or `bors merge`) to merge. [Further information](https://bors.tech/documentation/getting-started/#reviewing-pull-requests) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..07c24436 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,380 @@ +--- +name: Stackable Build Pipeline + +on: + push: + branches: + - main + - staging + - trying + - "renovate/**" + tags: + - "*" + pull_request: + +env: + CARGO_TERM_COLOR: always + CARGO_INCREMENTAL: '0' + CARGO_PROFILE_DEV_DEBUG: '0' + RUSTFLAGS: "-D warnings" + RUSTDOCFLAGS: "-D warnings" + RUST_LOG: "info" + OPERATOR_NAME: "listener-operator" + PRODUCT_NAME: "listener-operator" + DEV_REPO_HELM_URL: https://repo.stackable.tech/repository/helm-dev + TEST_REPO_HELM_URL: https://repo.stackable.tech/repository/helm-test + STABLE_REPO_HELM_URL: https://repo.stackable.tech/repository/helm-stable + +jobs: + # Identify unused dependencies + run_udeps: + name: Run Cargo Udeps + runs-on: ubuntu-latest + env: + RUSTC_BOOTSTRAP: 1 + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + override: true + - uses: Swatinem/rust-cache@6720f05bc48b77f96918929a9019fb2203ff71f8 # tag=v2.0.0 + with: + key: udeps + - uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # renovate: tag=v1.0.3 + with: + command: install + args: cargo-udeps --locked + - uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # renovate: tag=v1.0.3 + with: + command: udeps + + prerelease_job: + name: Returns the pre-release string from the version of the cargo workspace. + runs-on: ubuntu-latest + outputs: + str: ${{ steps.prerelease.outputs.str }} + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # tag=v4 + - name: Install requirements for version tool + run: pip install -r python/requirements.txt + - id: prerelease + name: Extract the pre-release string. Might be empty. + run: | + PRERELEASE=$(python/cargo_version.py -o | sed "s/^[0-9]\+\.[0-9]\+\.[0-9]\+-\?//g") + echo "::set-output name=str::$PRERELEASE" + + # This job evaluates the github environment to determine why this action is running and selects the appropriate + # target repository for published Helm charts based on this. + # + # The following scenarios are identified: + # - pull request: + # condition: github.event_name == "pull_request" + # repository: test + # + # - release (aka a tag was created): + # condition: github.event_name == 'create' & github.ref.startswith('refs/tags/') + # repository: stable + # + # - merge of pr to main branch and pre release is nightly: + # condition: github.event_name == 'push' & github.ref == 'refs/heads/main' & needs.prerelease_job.outputs.str == 'nightly' + # repository: dev + # + # Any other scenarios will cause the publish step to be skipped, most commonly this is expected to happen for the + # branches that bors uses internally (staging, trying) for which the checks need to run, but we do not want artifacts + # to be published. + select_repo: + name: Select target repository based on action trigger + runs-on: ubuntu-latest + outputs: + repository: ${{ steps.selectrepo.outputs.repo }} + needs: prerelease_job + steps: + - id: selectrepo + env: + TRIGGER: ${{ github.event_name }} + GITHUB_REF: ${{ github.ref }} + PRERELEASE: ${{ needs.prerelease_job.outputs.str }} + run: | + if [[ $TRIGGER == "pull_request" ]]; then + echo "exporting test as target repo: ${{ env.TEST_REPO_HELM_URL }}" + echo "::set-output name=repo::${{ env.TEST_REPO_HELM_URL }}" + elif [[ $TRIGGER == "push" && $GITHUB_REF == "refs/heads/main" && $PRERELEASE == 'nightly' ]]; then + echo "exporting dev as target repo: ${{ env.DEV_REPO_HELM_URL }}" + echo "::set-output name=repo::${{ env.DEV_REPO_HELM_URL }}" + elif [[ ( $TRIGGER == "create" || $TRIGGER == "push" ) && $GITHUB_REF == refs/tags/* ]]; then + echo "exporting stable as target repo: ${{ env.STABLE_REPO_HELM_URL }}" + echo "::set-output name=repo::${{ env.STABLE_REPO_HELM_URL }}" + else + echo "Unknown trigger and ref combination encountered, skipping publish step: $TRIGGER $GITHUB_REF" + echo "::set-output name=repo::skip" + fi + + run_cargodeny: + name: Run Cargo Deny + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - advisories + - bans licenses sources + + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: ${{ matrix.checks == 'advisories' }} + + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: EmbarkStudios/cargo-deny-action@7257a18a9c2fe3f92b85d41ae473520dff953c97 # tag=v1.3.2 + with: + command: check ${{ matrix.checks }} + + run_rustfmt: + name: Run Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + components: rustfmt + override: true + - uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # renovate: tag=v1.0.3 + with: + command: fmt + args: --all -- --check + + run_clippy: + name: Run Clippy + runs-on: ubuntu-latest + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + - uses: Swatinem/rust-cache@6720f05bc48b77f96918929a9019fb2203ff71f8 # tag=v2.0.0 + with: + key: clippy + - name: Run clippy action to produce annotations + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: actions-rs/clippy-check@b5b5f21f4797c02da247df37026fcd0a5024aa4d # renovate: tag=v1.0.7 + if: env.GITHUB_TOKEN != null + with: + args: --all-targets -- -D warnings + token: ${{ secrets.GITHUB_TOKEN }} + - name: Run clippy manually without annotations + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: env.GITHUB_TOKEN == null + run: cargo clippy --all-targets -- -D warnings + + run_rustdoc: + name: Run RustDoc + runs-on: ubuntu-latest + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + components: rustfmt + override: true + - uses: Swatinem/rust-cache@6720f05bc48b77f96918929a9019fb2203ff71f8 # tag=v2.0.0 + with: + key: doc + - uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # renovate: tag=v1.0.3 + with: + command: doc + args: --document-private-items + + run_tests: + name: Run Cargo Tests + needs: + - run_cargodeny + - run_clippy + - run_rustfmt + - run_rustdoc + runs-on: ubuntu-latest + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + override: true + - uses: Swatinem/rust-cache@6720f05bc48b77f96918929a9019fb2203ff71f8 # tag=v2.0.0 + with: + key: test + - uses: actions-rs/cargo@844f36862e911db73fe0815f00a4a2602c279505 # renovate: tag=v1.0.3 + with: + command: test + + # This job cleans up the CRDs, Helm charts and Kustomize manifests, followed by rebuilding them + # It then runs a `git diff` and fails the entire workflow, if any difference is encountered. + # + # Since CRD files are generated during the 'cargo build' process we need to run this once after + # removing the CRD files to ensure that the checked in versions match what the code expects. + # + # The reason for this step is, that developers are expected to check in up-to-date versions of charts + # and manifests, as we'd otherwise have to build these in CI and commit them back to the PR, which + # creates all kinds of problems. + # Therefor this failsafe simply aborts anything that has not had charts and manifests rebuilt before pushing. + check_charts: + name: Check if committed Helm & Kustomize Charts were up to date + needs: + - run_cargodeny + - run_clippy + - run_rustfmt + - run_rustdoc + runs-on: ubuntu-latest + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - name: Set up Helm + uses: azure/setup-helm@b5b231a831f96336bbfeccc1329990f0005c5bb1 # tag=v3.3 + with: + version: v3.6.2 + - name: Set up cargo + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + override: true + - name: Set up rust-cache + uses: Swatinem/rust-cache@6720f05bc48b77f96918929a9019fb2203ff71f8 # tag=v2.0.0 + with: + key: charts + - name: Regenerate charts + run: make regenerate-charts + - name: Check if committed charts were up to date + run: git diff --exit-code + - name: Git Diff showed uncommitted changes + if: ${{ failure() }} + uses: actions/github-script@d50f485531ba88479582bc2da03ff424389af5c1 # tag=v6 + with: + script: | + core.setFailed('Committed charts were not up to date, please regenerate and re-commit!') + + test_charts: + name: Run Chart Tests + needs: + - check_charts + - run_tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - name: placeholder + run: echo Tests will go here + + tests_passed: + name: All tests passed + needs: + - test_charts + - run_udeps + runs-on: ubuntu-latest + steps: + - name: log + run: echo All tests have passed! + + package_and_publish: + name: Package Charts, Build Docker Image and publish them + needs: + - tests_passed + - select_repo + runs-on: ubuntu-latest + env: + NEXUS_PASSWORD: ${{ secrets.NEXUS_PASSWORD }} + REPO: ${{ needs.select_repo.outputs.repository }} + if: needs.select_repo.outputs.repository != 'skip' + steps: + - name: Install protoc + run: | + sudo apt-get update + sudo apt-get install protobuf-compiler + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + with: + submodules: recursive + - uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # tag=v4 + if: ${{ github.event_name == 'pull_request' }} + - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af # renovate: tag=v1.0.7 + with: + profile: minimal + toolchain: stable + components: rustfmt + override: true + - name: Install requirements for version tool + if: ${{ github.event_name == 'pull_request' }} + run: pip install -r python/requirements.txt + + # This step checks if the current run was triggered by a push to a pr (or a pr being created). + # If this is the case it changes the version of this project in all Cargo.toml files to include the suffix + # "-pr" so that the published artifacts can be linked to this PR. + - name: Update version if PR + if: ${{ github.event_name == 'pull_request' }} + run: python/cargo_version.py -m pr${{ github.event.pull_request.number }} + + # Recreate charts with changed version if needed + - name: Clean charts + if: ${{ github.event_name == 'pull_request' }} + run: make chart-clean clean-manifests compile-chart generate-manifests + + # Package and publish charts + - name: Package Chart + run: mkdir -p target/helm && helm package --destination target/helm deploy/helm/${{ env.OPERATOR_NAME }} + - name: Build Docker image + if: env.NEXUS_PASSWORD != null # pragma: allowlist secret + run: make docker + - name: Publish Chart + if: env.NEXUS_PASSWORD != null # pragma: allowlist secret + run: >- + /usr/bin/curl + --fail + -u 'github:${{ secrets.NEXUS_PASSWORD }}' + --upload-file "./$(find target/helm/ -name '*.tgz')" + "${{ env.REPO }}/" diff --git a/.github/workflows/daily_security.yml b/.github/workflows/daily_security.yml new file mode 100644 index 00000000..b609d366 --- /dev/null +++ b/.github/workflows/daily_security.yml @@ -0,0 +1,20 @@ +# ============= +# This file is automatically generated from the templates in stackabletech/operator-templating +# DON'T MANUALLY EDIT THIS FILE +# ============= +--- +name: Security audit + +on: + schedule: + - cron: '15 4 * * *' + workflow_dispatch: + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: actions-rs/audit-check@35b7b53b1e25b55642157ac01b4adceb5b9ebef3 # renovate: tag=v1.2.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/reviewdog.yaml b/.github/workflows/reviewdog.yaml new file mode 100644 index 00000000..67a11e40 --- /dev/null +++ b/.github/workflows/reviewdog.yaml @@ -0,0 +1,62 @@ +--- +name: reviewdog +on: + pull_request + +permissions: + contents: read + checks: write + pull-requests: write + issues: write + +jobs: + actionlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: reviewdog/action-actionlint@12f228ecba8c567a103efafb3fb0bf5b60dc16b7 # tag=v1.27.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + flake8: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # tag=v4 + with: + python-version: "3.9" + - uses: reviewdog/action-flake8@b6435e67f0cfda225b9e0c9283cfb7ea7c551bdb # tag=v3.6.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + hadolint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: reviewdog/action-hadolint@55be5d2c4b0b80d439247b128a9ded3747f92a29 # tag=v1.33.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + markdownlint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: reviewdog/action-markdownlint@b8f945b8bee2a2967214f35956209bc31d3c4d26 # tag=v0.7.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: reviewdog/action-shellcheck@66c9a47bf02255b250284a82251cb4cadf5043f5 # tag=v1.15.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + yamllint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + - uses: reviewdog/action-yamllint@8c429dfe4fc47b1ce1fa99a64e94693880d5dc30 # tag=v1.6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e89cb372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +tests/ansible/roles/ +tests/_work/ +debug/ +target/ +**/*.rs.bk + +.idea/ +*.iws +*.iml + +*.tgz + +Cargo.nix +crate-hashes.json +result +image.tar \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..b2e03c66 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "rust/csi-grpc/vendor/csi"] + path = rust/csi-grpc/vendor/csi + url = https://github.com/container-storage-interface/spec diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..dbfa4558 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,20 @@ +--- +# All defaults or options can be checked here: +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 9999 + # Number of characters for headings + heading_line_length: 9999 + # Number of characters for code blocks + code_block_line_length: 9999 + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + siblings_only: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7153b386 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,35 @@ +--- +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: detect-aws-credentials + args: ["--allow-missing-credentials"] + - id: detect-private-key + + - repo: https://github.com/doublify/pre-commit-rust + rev: v1.0 + hooks: + - id: fmt + args: ["--all", "--", "--check"] + - id: clippy + args: ["--all-targets", "--", "-D", "warnings"] + + - repo: https://github.com/adrienverge/yamllint + rev: v1.26.3 + hooks: + - id: yamllint + + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.31.1 + hooks: + - id: markdownlint + + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 00000000..5df27e82 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,9 @@ +[MESSAGES CONTROL] + +# These rules are for missing docstrings which doesn't matter much for most of our simple scripts +disable=C0114,C0115,C0116 + +[FORMAT] + +max-line-length=999 +indent-string=' ' diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 00000000..769cda16 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,14 @@ +--- +extends: default + +ignore: | + deploy/helm/**/templates + +rules: + line-length: disable + truthy: + check-keys: false + comments: + min-spaces-from-content: 1 # Needed due to https://github.com/adrienverge/yamllint/issues/443 + indentation: + indent-sequences: consistent diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..1cc66094 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2685 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" + +[[package]] +name = "async-stream" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +dependencies = [ + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa 1.0.3", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom", + "instant", + "rand", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "built" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f346b6890a0dfa7266974910e7df2d5088120dd54721b9b0e5aae1ae5e05715" +dependencies = [ + "cargo-lock", + "chrono", + "git2", +] + +[[package]] +name = "bumpalo" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cargo-lock" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c408da54db4c50d4693f7e649c299bc9de9c23ead86249e5368830bb32a734b" +dependencies = [ + "semver", + "serde", + "toml", + "url", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "3.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "cmake" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +dependencies = [ + "cc", +] + +[[package]] +name = "const_format" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "csi-grpc" +version = "0.1.0-nightly" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tonic-build", +] + +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "encoding" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" +dependencies = [ + "encoding-index-japanese", + "encoding-index-korean", + "encoding-index-simpchinese", + "encoding-index-singlebyte", + "encoding-index-tradchinese", +] + +[[package]] +name = "encoding-index-japanese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-korean" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-simpchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-singlebyte" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding-index-tradchinese" +version = "1.20141219.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" +dependencies = [ + "encoding_index_tests", +] + +[[package]] +name = "encoding_index_tests" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" + +[[package]] +name = "fancy-regex" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95b4efe5be9104a4a18a9916e86654319895138be727b229820c39257c30dda" +dependencies = [ + "bit-set", + "regex", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" + +[[package]] +name = "futures-executor" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" + +[[package]] +name = "futures-macro" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" + +[[package]] +name = "futures-task" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" + +[[package]] +name = "futures-util" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "git2" +version = "0.13.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +dependencies = [ + "bitflags", + "libc", + "libgit2-sys", + "log", + "url", +] + +[[package]] +name = "h2" +version = "0.3.7" +source = "git+https://github.com/stackabletech/h2.git?branch=feature/grpc-uds#b7554e1b8730af5fbb7fc1841d92f9247c4e477c" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.6.10", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.3", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436ec0091e4f20e655156a30a0df3770fe2900aa301e548e08446ec794b6953c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 0.4.8", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-openssl" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ee5d7a8f718585d1c3c61dfde28ef5b0bb14734b4db13f5ada856cdc6c612b" +dependencies = [ + "http", + "hyper", + "linked_hash_set", + "once_cell", + "openssl", + "openssl-sys", + "parking_lot", + "tokio", + "tokio-openssl", + "tower-layer", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "237a0714f28b1ee39ccec0770ccb544eb02c9ef2c82bb096230eefcffa6468b0" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "js-sys", + "once_cell", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "integer-encoding" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02" + +[[package]] +name = "itertools" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf247779e67a9082a4790b45e71ac7cfd1321331a5c856a74a9faebdab78d0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "java-properties" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1904d8654a1ef51034d02d5a9411b50bf91bea15b0ab644ae179d1325976263" +dependencies = [ + "encoding", + "lazy_static", + "regex", +] + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f995a3c8f2bc3dd52a18a583e90f9ec109c047fa1603a853e46bcda14d2e279d" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + +[[package]] +name = "jsonpath_lib" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaa63191d68230cccb81c5aa23abd53ed64d83337cacbb25a7b8c7979523774f" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "k8s-openapi" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ae2c04fcee6b01b04e3aadd56bb418932c8e0a9d8a93f48bc68c6bdcdb559d" +dependencies = [ + "base64", + "bytes", + "chrono", + "schemars", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a527a8001a61d8d470dab27ac650889938760c243903e7cd90faaf7c60a34bdd" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0d48f42df4e8342e9f488c4b97e3759d0042c4e7ab1a853cc285adb44409480" +dependencies = [ + "base64", + "bytes", + "chrono", + "dirs-next", + "either", + "futures", + "http", + "http-body", + "hyper", + "hyper-openssl", + "hyper-timeout", + "hyper-tls", + "jsonpath_lib", + "k8s-openapi", + "kube-core", + "openssl", + "pem", + "pin-project", + "secrecy", + "serde", + "serde_json", + "serde_yaml 0.8.26", + "thiserror", + "tokio", + "tokio-native-tls", + "tokio-util 0.7.4", + "tower", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f56027f862fdcad265d2e9616af416a355e28a1c620bb709083494753e070d" +dependencies = [ + "chrono", + "form_urlencoded", + "http", + "json-patch", + "k8s-openapi", + "once_cell", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d74121eb41af4480052901f31142d8d9bbdf1b7c6b856da43bcb02f5b1b177" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "kube-runtime" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fdcf5a20f968768e342ef1a457491bb5661fccd81119666d626c57500b16d99" +dependencies = [ + "ahash", + "backoff", + "derivative", + "futures", + "json-patch", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "smallvec", + "thiserror", + "tokio", + "tokio-util 0.7.4", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" + +[[package]] +name = "libgit2-sys" +version = "0.12.26+1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libz-sys" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[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", +] + +[[package]] +name = "lock_api" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "mio" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0" + +[[package]] +name = "openssl" +version = "0.10.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "opentelemetry" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "js-sys", + "lazy_static", + "percent-encoding", + "pin-project", + "rand", + "thiserror", + "tokio", + "tokio-stream", +] + +[[package]] +name = "opentelemetry-jaeger" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c0b12cd9e3f9b35b52f6e0dac66866c519b26f424f4bbf96e3fe8bfbdc5229" +dependencies = [ + "async-trait", + "lazy_static", + "opentelemetry", + "opentelemetry-semantic-conventions", + "thiserror", + "thrift", + "tokio", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985cc35d832d412224b2cffe2f9194b1b89b6aa5d0bef76d080dce09d90e62bd" +dependencies = [ + "opentelemetry", +] + +[[package]] +name = "ordered-float" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3305af35278dd29f46fcdd139e0b1fbfae2153f0e5928b39b035542dd31e37b7" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "os_str_bytes" +version = "6.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "pem" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +dependencies = [ + "base64", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "prettyplease" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49e86d2c26a24059894a3afa13fd17d063419b05dfb83f06d9c3566060c3f5a" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "product-config" +version = "0.4.0" +source = "git+https://github.com/stackabletech/product-config.git?tag=0.4.0#e1e5938b4f6120f85a088194e86d22433fdba731" +dependencies = [ + "fancy-regex", + "java-properties", + "schemars", + "semver", + "serde", + "serde_json", + "serde_yaml 0.8.26", + "thiserror", + "xml-rs", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" +dependencies = [ + "bytes", + "cfg-if", + "cmake", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "schemars" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float 2.10.0", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +dependencies = [ + "indexmap", + "itoa 1.0.3", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "serde_yaml" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8613d593412a0deb7bbd8de9d908efff5a0cb9ccd8f62c641e7b2ed2f57291d1" +dependencies = [ + "indexmap", + "itoa 1.0.3", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "snafu" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "stackable-listener-operator" +version = "0.1.0-nightly" +dependencies = [ + "anyhow", + "built", + "clap", + "csi-grpc", + "futures", + "h2", + "libc", + "pin-project", + "prost", + "serde", + "snafu", + "socket2", + "stackable-operator", + "strum", + "tokio", + "tokio-stream", + "tonic", + "tonic-reflection", +] + +[[package]] +name = "stackable-operator" +version = "0.25.2" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.25.2#ef41dc96071665ce99b35e8090c8df3ef89e9616" +dependencies = [ + "chrono", + "clap", + "const_format", + "derivative", + "either", + "futures", + "json-patch", + "k8s-openapi", + "kube", + "lazy_static", + "opentelemetry", + "opentelemetry-jaeger", + "product-config", + "rand", + "regex", + "schemars", + "serde", + "serde_json", + "serde_yaml 0.9.13", + "stackable-operator-derive", + "strum", + "thiserror", + "tokio", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[package]] +name = "stackable-operator-derive" +version = "0.25.2" +source = "git+https://github.com/stackabletech/operator-rs.git?tag=0.25.2#ef41dc96071665ce99b35e8090c8df3ef89e9616" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52205623b1b0f064a4e71182c3b18ae902267282930c6d5462c91b859668426e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + +[[package]] +name = "thiserror" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c53f98874615aea268107765aa1ed8f6116782501d18e53d08b471733bea6c85" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b463991b4eab2d801e724172285ec4195c650e8ec79b149e6c2a8e6dd3f783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "thrift" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b82ca8f46f95b3ce96081fe3dd89160fdea970c254bb72925255d1b62aae692e" +dependencies = [ + "byteorder", + "integer-encoding", + "log", + "ordered-float 1.1.1", + "threadpool", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0020c875007ad96677dcc890298f4b942882c5d4eb7cc8f439fc3bf813dc9c95" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-openssl" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08f9ffb7809f1b20c1b398d92acf4cc719874b3b2b2d9ea2f09b4a80350878a" +dependencies = [ + "futures-util", + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6edf2d6bc038a43d31353570e27270603f4648d18f5ed10c0e179abe43255af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tonic" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9d60db39854b30b835107500cf0aca0b0d14d6e1c3de124217c23a29c2ddb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util 0.7.4", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9263bf4c9bfaae7317c1c2faf7f18491d2fe476f70c414b73bf5d445b00ffa1" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tonic-reflection" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1d786fcf313b48f1aac280142eae249f3c03495355c7906aa49872a41955015" +dependencies = [ + "bytes", + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util 0.7.4", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +dependencies = [ + "base64", + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.17.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f" +dependencies = [ + "once_cell", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" +dependencies = [ + "ansi_term", + "matchers", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "xml-rs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..63156300 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[workspace] +members = ["rust/operator-binary", "rust/csi-grpc"] + +[patch.crates-io] +# Workaround for https://github.com/hyperium/tonic/issues/243 +h2 = { git = "https://github.com/stackabletech/h2.git", branch = "feature/grpc-uds" } + +[patch."https://github.com/stackabletech/operator-rs.git"] +# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "feature/listener-implied-labels" } +# stackable-operator = { path = "../operator-rs" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..1b9535c9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,43 @@ +Licensed under the Open Software License version 3.0 + +1) Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + +a) to reproduce the Original Work in copies, either alone or as part of a collective work; + +b) to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + +c) to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + +d) to perform the Original Work publicly; and + +e) to display the Original Work publicly. + +2) Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + +3) Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + +4) Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + +5) External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + +6) Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + +7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + +8) Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + +9) Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + +10) Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + +11) Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + +12) Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + +13) Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + +14) Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +15) Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + +16) Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4980f44d --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +# ============= +# This file is automatically generated from the templates in stackabletech/operator-templating +# DO NOT MANUALLY EDIT THIS FILE +# ============= + +# This script requires https://github.com/mikefarah/yq (not to be confused with https://github.com/kislyuk/yq) +# It is available from Nixpkgs as `yq-go` (`nix shell nixpkgs#yq-go`) + +.PHONY: docker chart-lint compile-chart + +TAG := $(shell git rev-parse --short HEAD) + +VERSION := $(shell cargo metadata --format-version 1 | jq -r '.packages[] | select(.name=="stackable-listener-operator") | .version') +IS_NIGHTLY := $(shell echo "${VERSION}" | grep -- '-nightly$$') +# When rendering docs we want to simplify the version number slightly, only rendering "nightly" for nightly branches +# (since we only render nightlies for the active development trunk anyway) and chopping off the semver patch version otherwise +DOCS_VERSION := $(if ${IS_NIGHTLY},nightly,$(shell echo "${VERSION}" | sed 's/^\([0-9]\+\.[0-9]\+\)\..*$$/\1/')) +export VERSION IS_NIGHTLY DOCS_VERSION + +SHELL=/usr/bin/env bash -euo pipefail + +## Docker related targets +docker-build: + docker build --force-rm --build-arg VERSION=${VERSION} -t "docker.stackable.tech/stackable/listener-operator:${VERSION}" -f docker/Dockerfile . + +docker-build-latest: docker-build + docker tag "docker.stackable.tech/stackable/listener-operator:${VERSION}" \ + "docker.stackable.tech/stackable/listener-operator:latest" + +docker-publish: + echo "${NEXUS_PASSWORD}" | docker login --username github --password-stdin docker.stackable.tech + docker push --all-tags docker.stackable.tech/stackable/listener-operator + +docker: docker-build docker-publish + +docker-release: docker-build-latest docker-publish + +## Chart related targets +compile-chart: version crds config + +chart-clean: + rm -rf deploy/helm/listener-operator/configs + rm -rf deploy/helm/listener-operator/crds + +version: + yq eval -i '.version = strenv(VERSION) | .appVersion = strenv(VERSION)' /dev/stdin < deploy/helm/listener-operator/Chart.yaml + yq eval -i '.version = strenv(DOCS_VERSION) | .prerelease = strenv(IS_NIGHTLY) != ""' /dev/stdin < docs/antora.yml + +config: + if [ -d "deploy/config-spec/" ]; then\ + mkdir -p deploy/helm/listener-operator/configs;\ + cp -r deploy/config-spec/* deploy/helm/listener-operator/configs;\ + fi + +crds: + mkdir -p deploy/helm/listener-operator/crds + cargo run --bin stackable-listener-operator -- crd | yq eval '.metadata.annotations["helm.sh/resource-policy"]="keep"' - > deploy/helm/listener-operator/crds/crds.yaml + +chart-lint: compile-chart + docker run -it -v $(shell pwd):/build/helm-charts -w /build/helm-charts quay.io/helmpack/chart-testing:v3.5.0 ct lint --config deploy/helm/ct.yaml + +## Manifest related targets +clean-manifests: + mkdir -p deploy/manifests + rm -rf $$(find deploy/manifests -maxdepth 1 -mindepth 1 -not -name Kustomization) + +generate-manifests: clean-manifests compile-chart + ./scripts/generate-manifests.sh + +regenerate-charts: chart-clean clean-manifests compile-chart generate-manifests diff --git a/README.md b/README.md new file mode 100644 index 00000000..4265c41e --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# Stackable Listener Operator + +[![Maintenance](https://img.shields.io/badge/Maintained%3F-yes-green.svg)](https://GitHub.com/stackabletech/secret-operator/graphs/commit-activity) + +A CSI provider intended to provide an abstract way to expose a single Pod to the outside network, while hiding details about the cluster from the application developer. + +It is written by [Stackable](https://www.stackable.tech) in Rust. + +The documentation for this operator can be found at https://docs.stackable.tech/listener-operator/stable/index.html. +The documentation for all Stackable products can be found at https://docs.stackable.tech. diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 00000000..65793a21 --- /dev/null +++ b/Tiltfile @@ -0,0 +1,33 @@ +default_registry("docker.stackable.tech/sandbox") + +custom_build( + 'docker.stackable.tech/sandbox/listener-operator', + 'nix run -f . crate2nix generate && nix-build . -A docker --argstr dockerName "${EXPECTED_REGISTRY}/listener-operator" && ./result/load-image | docker load', + deps=['rust', 'Cargo.toml', 'Cargo.lock', 'default.nix', "nix", 'build.rs', 'vendor'], + # ignore=['result*', 'Cargo.nix', 'target', *.yaml], + outputs_image_ref_to='result/ref', +) + +# Load the latest CRDs from Nix +watch_file('result') +if os.path.exists('result'): + k8s_yaml('result/crds.yaml') + +# Exclude stale CRDs from Helm chart, and apply the rest +helm_crds, helm_non_crds = filter_yaml( + helm( + 'deploy/helm/listener-operator', + name='listener-operator', + set=[ + 'image.repository=docker.stackable.tech/sandbox/listener-operator', + ], + ), + api_version = "^apiextensions\\.k8s\\.io/.*$", + kind = "^CustomResourceDefinition$", +) +k8s_yaml(helm_non_crds) + +# Load examples +k8s_yaml('examples/nginx-nodeport.yaml') +k8s_yaml('examples/nginx-lb.yaml') +k8s_yaml('examples/nginx-preprovisioned-lb.yaml') diff --git a/bors.toml b/bors.toml new file mode 100644 index 00000000..420d30c8 --- /dev/null +++ b/bors.toml @@ -0,0 +1,9 @@ +status = [ + 'All tests passed' +] +delete_merged_branches = true +use_squash_merge = true +pr_status = [ 'license/cla' ] +timeout_sec = 7200 +cut_body_after = "" +required_approvals = 1 diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..c4ef02a0 --- /dev/null +++ b/default.nix @@ -0,0 +1,59 @@ +{ sources ? import ./nix/sources.nix # managed by https://github.com/nmattia/niv +, nixpkgs ? sources.nixpkgs +, pkgs ? import nixpkgs {} +, cargo ? import ./Cargo.nix { + inherit nixpkgs pkgs; release = false; + defaultCrateOverrides = pkgs.defaultCrateOverrides // { + prost-build = attrs: { + buildInputs = [ pkgs.protobuf ]; + }; + tonic-reflection = attrs: { + buildInputs = [ pkgs.rustfmt ]; + }; + }; + } +, dockerName ? "docker.stackable.tech/sandbox/listener-operator" +, dockerTag ? null +}: +rec { + build = cargo.workspaceMembers.stackable-listener-operator.build; + crds = pkgs.runCommand "listener-operator-crds.yaml" {} + '' + ${build}/bin/stackable-listener-operator crd > $out + ''; + + dockerImage = pkgs.dockerTools.streamLayeredImage { + name = dockerName; + tag = dockerTag; + contents = [ pkgs.bashInteractive pkgs.coreutils pkgs.util-linuxMinimal ]; + config = { + Entrypoint = [ (build+"/bin/stackable-listener-operator") ]; + Cmd = [ "run" ]; + }; + }; + docker = pkgs.linkFarm "listener-operator-docker" [ + { + name = "load-image"; + path = dockerImage; + } + { + name = "ref"; + path = pkgs.writeText "${dockerImage.name}-image-tag" "${dockerImage.imageName}:${dockerImage.imageTag}"; + } + { + name = "image-repo"; + path = pkgs.writeText "${dockerImage.name}-repo" dockerImage.imageName; + } + { + name = "image-tag"; + path = pkgs.writeText "${dockerImage.name}-tag" dockerImage.imageTag; + } + { + name = "crds.yaml"; + path = crds; + } + ]; + + crate2nix = pkgs.crate2nix; + tilt = pkgs.tilt; +} diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..e40902da --- /dev/null +++ b/deny.toml @@ -0,0 +1,62 @@ +targets = [ + { triple = "x86_64-unknown-linux-gnu" }, + { triple = "aarch64-unknown-linux-gnu" }, + { triple = "x86_64-unknown-linux-musl" }, + { triple = "aarch64-apple-darwin" }, + { triple = "x86_64-apple-darwin" }, +] + +[advisories] +vulnerability = "warn" +unmaintained = "allow" +unsound = "warn" +yanked = "warn" +notice = "warn" + +[bans] +multiple-versions = "allow" + +[licenses] +unlicensed = "deny" +copyleft = "deny" +allow-osi-fsf-free = "neither" +default = "deny" +confidence-threshold = 1.0 +allow = [ + "Apache-2.0", + "BSD-3-Clause", + "CC0-1.0", + "ISC", + "LicenseRef-ring", + "LicenseRef-webpki", + "MIT", + "Unicode-DFS-2016", + "Zlib" +] + +exceptions = [ + { name = "stackable-listener-operator-crd", allow = ["OSL-3.0"] }, + { name = "stackable-listener-operator", allow = ["OSL-3.0"] }, + { name = "stackable-listener-operator-binary", allow = ["OSL-3.0"] }, + ] + +[[licenses.clarify]] +name = "ring" +expression = "LicenseRef-ring" +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 }, +] + +[[licenses.clarify]] +name = "webpki" +expression = "LicenseRef-webpki" +license-files = [ + { path = "LICENSE", hash = 0x001c7e6c }, +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" + +[sources.allow-org] +github = ["stackabletech"] diff --git a/deploy/DO_NOT_EDIT.md b/deploy/DO_NOT_EDIT.md new file mode 100644 index 00000000..1b26fd50 --- /dev/null +++ b/deploy/DO_NOT_EDIT.md @@ -0,0 +1,11 @@ +These Helm charts and manifests are automatically generated. +Please do not edit anything except for files explicitly mentioned below in this +directory manually. + +The following files are ok to edit: + +- helm/listener-operator/templates/roles.yaml +- helm/listener-operator/values.yaml + +The details are in-motion but check this repository for a few details: + diff --git a/deploy/helm/ct.yaml b/deploy/helm/ct.yaml new file mode 100644 index 00000000..fbef6924 --- /dev/null +++ b/deploy/helm/ct.yaml @@ -0,0 +1,8 @@ +# This file is used for chart-testing (https://github.com/helm/chart-testing) +# The name "ct.yaml" is not very self-descriptive but it is the default that chart-testing is looking for +--- +remote: origin +target-branch: main +chart-dirs: + - deploy/helm +all: true diff --git a/deploy/helm/listener-operator/.helmignore b/deploy/helm/listener-operator/.helmignore new file mode 100644 index 00000000..fef44b7e --- /dev/null +++ b/deploy/helm/listener-operator/.helmignore @@ -0,0 +1,28 @@ +# ============= +# This file is automatically generated from the templates in stackabletech/operator-templating +# DON'T MANUALLY EDIT THIS FILE +# ============= + +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/helm/listener-operator/Chart.yaml b/deploy/helm/listener-operator/Chart.yaml new file mode 100644 index 00000000..e04ea22b --- /dev/null +++ b/deploy/helm/listener-operator/Chart.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v2 +name: listener-operator +version: "0.1.0-nightly" +appVersion: "0.1.0-nightly" +description: The Stackable Operator for Stackable Listener Operator +home: https://github.com/stackabletech/listener-operator +maintainers: + - name: Stackable + url: https://www.stackable.tech diff --git a/deploy/helm/listener-operator/README.md b/deploy/helm/listener-operator/README.md new file mode 100644 index 00000000..90de9394 --- /dev/null +++ b/deploy/helm/listener-operator/README.md @@ -0,0 +1,27 @@ +# Helm Chart for Stackable Operator for Stackable Listener Operator + +This Helm Chart can be used to install Custom Resource Definitions and the Operator for Stackable Listener Operator provided by Stackable. + +## Requirements + +- Create a [Kubernetes Cluster](../Readme.md) +- Install [Helm](https://helm.sh/docs/intro/install/) + +## Install the Stackable Operator for Stackable Listener Operator + +```bash +# From the root of the operator repository +make compile-chart + +helm install listener-operator deploy/helm/listener-operator +``` + +## Usage of the CRDs + +The usage of this operator and its CRDs is described in the [documentation](https://docs.stackable.tech/listener-operator/index.html) + +The operator has example requests included in the [`/examples`](https://github.com/stackabletech/listener-operator/tree/main/examples) directory. + +## Links + +https://github.com/stackabletech/listener-operator diff --git a/deploy/helm/listener-operator/crds/crds.yaml b/deploy/helm/listener-operator/crds/crds.yaml new file mode 100644 index 00000000..d6e132e5 --- /dev/null +++ b/deploy/helm/listener-operator/crds/crds.yaml @@ -0,0 +1,164 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listenerclasses.listeners.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: ListenerClass + plural: listenerclasses + shortNames: [] + singular: listenerclass + scope: Cluster + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerClassSpec via `CustomResource` + properties: + spec: + description: Defines a policy for how [`Listener`]s should be exposed. + properties: + serviceAnnotations: + additionalProperties: + type: string + default: {} + description: Annotations that should be added to the [`Service`] object. + type: object + serviceType: + description: The method used to access the services. + enum: + - NodePort + - LoadBalancer + type: string + required: + - serviceType + type: object + required: + - spec + title: ListenerClass + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listeners.listeners.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: Listener + plural: listeners + shortNames: [] + singular: listener + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerSpec via `CustomResource` + properties: + spec: + description: |- + Exposes a set of pods to the outside world. + + Essentially a Stackable extension of a Kubernetes [`Service`]. Compared to [`Service`], [`Listener`] changes two things: 1. It uses a cluster-level policy object ([`ListenerClass`]) to define how exactly the exposure works 2. It has a consistent API for reading back the exposed address(es) of the service 3. The [`Pod`] must mount a [`Volume`] referring to the `Listener`, which also allows us to control stickiness + properties: + className: + description: The name of the [`ListenerClass`]. + nullable: true + type: string + extraPodSelectorLabels: + additionalProperties: + type: string + default: {} + description: Extra labels that the [`Pod`]s must match in order to be exposed. They must _also_ still have a `Volume` referring to the listener. + type: object + ports: + description: Ports that should be exposed. + items: + properties: + name: + description: |- + The name of the port. + + The name of each port *must* be unique within a single [`Listener`]. + type: string + port: + description: The port number. + format: int32 + type: integer + protocol: + description: The layer-4 protocol (`TCP` or `UDP`). + nullable: true + type: string + required: + - name + - port + type: object + nullable: true + type: array + publishNotReadyAddresses: + default: true + description: Whether incoming traffic should also be directed to `Pod`s that are not `Ready`. + nullable: true + type: boolean + type: object + status: + description: Informs users about how to reach the [`Listener`]. + nullable: true + properties: + ingressAddresses: + description: All addresses that the [`Listener`] is currently reachable from. + items: + description: One address that a [`Listener`] is accessible from. + properties: + address: + description: The hostname or IP address to the [`Listener`]. + type: string + ports: + additionalProperties: + format: int32 + type: integer + description: Port mapping table. + type: object + required: + - address + - ports + type: object + nullable: true + type: array + nodePorts: + additionalProperties: + format: int32 + type: integer + description: |- + Port mappings for accessing the [`Listener`] on each [`Node`] that the [`Pod`]s are currently running on. + + This is only intended for internal use by listener-operator itself. This will be left unset if using a [`ListenerClass`] that does not require [`Node`]-local access. + nullable: true + type: object + serviceName: + description: The backing Kubernetes [`Service`]. + nullable: true + type: string + type: object + required: + - spec + title: Listener + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/helm/listener-operator/templates/_helpers.tpl b/deploy/helm/listener-operator/templates/_helpers.tpl new file mode 100644 index 00000000..a2258b27 --- /dev/null +++ b/deploy/helm/listener-operator/templates/_helpers.tpl @@ -0,0 +1,76 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-operator" }} +{{- end }} + +{{/* +Expand the name of the chart. +*/}} +{{- define "operator.appname" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "operator.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "operator.labels" -}} +helm.sh/chart: {{ include "operator.chart" . }} +{{ include "operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "operator.appname" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Labels for Kubernetes objects created by helm test +*/}} +{{- define "operator.testLabels" -}} +helm.sh/test: {{ include "operator.chart" . }} +{{- end }} diff --git a/deploy/helm/listener-operator/templates/configmap.yaml b/deploy/helm/listener-operator/templates/configmap.yaml new file mode 100644 index 00000000..e75acc25 --- /dev/null +++ b/deploy/helm/listener-operator/templates/configmap.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: v1 +data: +{{ (.Files.Glob "configs/*").AsConfig | indent 2 }} +kind: ConfigMap +metadata: + name: {{ include "operator.fullname" . }}-configmap + labels: + {{- include "operator.labels" . | nindent 4 }} diff --git a/deploy/helm/listener-operator/templates/controller-deployment.yaml b/deploy/helm/listener-operator/templates/controller-deployment.yaml new file mode 100644 index 00000000..772925eb --- /dev/null +++ b/deploy/helm/listener-operator/templates/controller-deployment.yaml @@ -0,0 +1,70 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }}-controller-deployment + labels: + {{- include "operator.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/role: controller + {{- include "operator.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + app.kubernetes.io/role: controller + {{- include "operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ .Release.Name }}-serviceaccount + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "operator.appname" . }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + args: + - run + - controller + env: + - name: CSI_ENDPOINT + value: /csi/csi.sock + volumeMounts: + - name: csi + mountPath: /csi + - name: external-provisioner + image: k8s.gcr.io/sig-storage/csi-provisioner:v3.1.0 + args: + - --csi-address=/csi/csi.sock + - --feature-gates=Topology=true + - --extra-create-metadata + volumeMounts: + - name: csi + mountPath: /csi + volumes: + - name: csi + emptyDir: {} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/helm/listener-operator/templates/csidriver.yaml b/deploy/helm/listener-operator/templates/csidriver.yaml new file mode 100644 index 00000000..c819bc84 --- /dev/null +++ b/deploy/helm/listener-operator/templates/csidriver.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: CSIDriver +metadata: + name: listeners.stackable.tech +spec: + attachRequired: false + podInfoOnMount: true + fsGroupPolicy: File + volumeLifecycleModes: + - Ephemeral + - Persistent diff --git a/deploy/helm/listener-operator/templates/listener-classes.yaml b/deploy/helm/listener-operator/templates/listener-classes.yaml new file mode 100644 index 00000000..0c404940 --- /dev/null +++ b/deploy/helm/listener-operator/templates/listener-classes.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: nodeport +spec: + serviceType: NodePort +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: lb-external +spec: + serviceType: LoadBalancer diff --git a/deploy/helm/listener-operator/templates/node-daemonset.yaml b/deploy/helm/listener-operator/templates/node-daemonset.yaml new file mode 100644 index 00000000..f5f632ae --- /dev/null +++ b/deploy/helm/listener-operator/templates/node-daemonset.yaml @@ -0,0 +1,86 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: {{ .Release.Name }}-node-daemonset + labels: + {{- include "operator.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/role: node + {{- include "operator.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + app.kubernetes.io/role: node + {{- include "operator.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ .Release.Name }}-serviceaccount + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "operator.appname" . }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + args: + - run + - node + env: + - name: CSI_ENDPOINT + value: /csi/csi.sock + - name: NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + volumeMounts: + - name: csi + mountPath: /csi + - name: mountpoint + mountPath: /var/lib/kubelet/pods + mountPropagation: Bidirectional + - name: node-driver-registrar + image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.4.0 + args: + - --csi-address=/csi/csi.sock + - --kubelet-registration-path=/var/lib/kubelet/plugins/listeners.stackable.tech/csi.sock + volumeMounts: + - name: registration-sock + mountPath: /registration + - name: csi + mountPath: /csi + volumes: + - name: registration-sock + hostPath: + path: /var/lib/kubelet/plugins_registry/listeners.stackable.tech-reg.sock + - name: csi + hostPath: + path: /var/lib/kubelet/plugins/listeners.stackable.tech/ + - name: mountpoint + hostPath: + path: /var/lib/kubelet/pods/ + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/helm/listener-operator/templates/roles.yaml b/deploy/helm/listener-operator/templates/roles.yaml new file mode 100644 index 00000000..631764af --- /dev/null +++ b/deploy/helm/listener-operator/templates/roles.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ .Release.Name }}-clusterrole +rules: + - apiGroups: + - "" + resources: + - events + - services + verbs: + - get + - list + - watch + - create + - patch + - apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - watch + - create + - delete + - apiGroups: + - "" + resources: + - nodes + - persistentvolumeclaims + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - csinodes + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - patch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - create + - apiGroups: + - listeners.stackable.tech + resources: + - listenerclasses + - listeners + verbs: + - get + - list + - watch + - apiGroups: + - listeners.stackable.tech + resources: + - listeners + - listeners/status + verbs: + - patch + - create diff --git a/deploy/helm/listener-operator/templates/serviceaccount.yaml b/deploy/helm/listener-operator/templates/serviceaccount.yaml new file mode 100644 index 00000000..8ba0d58c --- /dev/null +++ b/deploy/helm/listener-operator/templates/serviceaccount.yaml @@ -0,0 +1,29 @@ +--- +{{ if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "operator.fullname" . }}-serviceaccount + labels: + {{- include "operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace. +kind: ClusterRoleBinding +metadata: + name: {{ include "operator.fullname" . }}-clusterrolebinding + labels: + {{- include "operator.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ include "operator.fullname" . }}-serviceaccount + namespace: {{ .Release.Namespace }} +roleRef: + kind: ClusterRole + name: {{ include "operator.fullname" . }}-clusterrole + apiGroup: rbac.authorization.k8s.io +{{- end }} diff --git a/deploy/helm/listener-operator/templates/storageclass.yaml b/deploy/helm/listener-operator/templates/storageclass.yaml new file mode 100644 index 00000000..59f2c5d5 --- /dev/null +++ b/deploy/helm/listener-operator/templates/storageclass.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: listeners.stackable.tech +provisioner: listeners.stackable.tech +volumeBindingMode: WaitForFirstConsumer diff --git a/deploy/helm/listener-operator/values.yaml b/deploy/helm/listener-operator/values.yaml new file mode 100644 index 00000000..3448a385 --- /dev/null +++ b/deploy/helm/listener-operator/values.yaml @@ -0,0 +1,52 @@ +# Default values for listener-operator. +--- +image: + repository: docker.stackable.tech/stackable/listener-operator + pullPolicy: IfNotPresent + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: + # listener-operator requires root permissions + runAsUser: 0 + privileged: true + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/deploy/manifests/Kustomization b/deploy/manifests/Kustomization new file mode 100644 index 00000000..0e1ce898 --- /dev/null +++ b/deploy/manifests/Kustomization @@ -0,0 +1,8 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - configmap.yaml + - deployment.yaml + - roles.yaml + - serviceaccount.yaml diff --git a/deploy/manifests/configmap.yaml b/deploy/manifests/configmap.yaml new file mode 100644 index 00000000..010a21fe --- /dev/null +++ b/deploy/manifests/configmap.yaml @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +data: {} +kind: ConfigMap +metadata: + name: listener-operator-configmap + labels: + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + app.kubernetes.io/version: "0.1.0-nightly" diff --git a/deploy/manifests/controller-deployment.yaml b/deploy/manifests/controller-deployment.yaml new file mode 100644 index 00000000..a9f55676 --- /dev/null +++ b/deploy/manifests/controller-deployment.yaml @@ -0,0 +1,53 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: listener-operator-controller-deployment + labels: + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + app.kubernetes.io/version: "0.1.0-nightly" +spec: + selector: + matchLabels: + app.kubernetes.io/role: controller + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + template: + metadata: + labels: + app.kubernetes.io/role: controller + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + spec: + serviceAccountName: listener-operator-serviceaccount + securityContext: {} + containers: + - name: listener-operator + securityContext: + privileged: true + runAsUser: 0 + image: "docker.stackable.tech/stackable/listener-operator:0.1.0-nightly" + imagePullPolicy: IfNotPresent + resources: {} + args: + - run + - controller + env: + - name: CSI_ENDPOINT + value: /csi/csi.sock + volumeMounts: + - name: csi + mountPath: /csi + - name: external-provisioner + image: k8s.gcr.io/sig-storage/csi-provisioner:v3.1.0 + args: + - --csi-address=/csi/csi.sock + - --feature-gates=Topology=true + - --extra-create-metadata + volumeMounts: + - name: csi + mountPath: /csi + volumes: + - name: csi + emptyDir: {} diff --git a/deploy/manifests/crds.yaml b/deploy/manifests/crds.yaml new file mode 100644 index 00000000..49a0890f --- /dev/null +++ b/deploy/manifests/crds.yaml @@ -0,0 +1,165 @@ +--- +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listenerclasses.listeners.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: ListenerClass + plural: listenerclasses + shortNames: [] + singular: listenerclass + scope: Cluster + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerClassSpec via `CustomResource` + properties: + spec: + description: Defines a policy for how [`Listener`]s should be exposed. + properties: + serviceAnnotations: + additionalProperties: + type: string + default: {} + description: Annotations that should be added to the [`Service`] object. + type: object + serviceType: + description: The method used to access the services. + enum: + - NodePort + - LoadBalancer + type: string + required: + - serviceType + type: object + required: + - spec + title: ListenerClass + type: object + served: true + storage: true + subresources: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: listeners.listeners.stackable.tech + annotations: + helm.sh/resource-policy: keep +spec: + group: listeners.stackable.tech + names: + categories: [] + kind: Listener + plural: listeners + shortNames: [] + singular: listener + scope: Namespaced + versions: + - additionalPrinterColumns: [] + name: v1alpha1 + schema: + openAPIV3Schema: + description: Auto-generated derived type for ListenerSpec via `CustomResource` + properties: + spec: + description: |- + Exposes a set of pods to the outside world. + + Essentially a Stackable extension of a Kubernetes [`Service`]. Compared to [`Service`], [`Listener`] changes two things: 1. It uses a cluster-level policy object ([`ListenerClass`]) to define how exactly the exposure works 2. It has a consistent API for reading back the exposed address(es) of the service 3. The [`Pod`] must mount a [`Volume`] referring to the `Listener`, which also allows us to control stickiness + properties: + className: + description: The name of the [`ListenerClass`]. + nullable: true + type: string + extraPodSelectorLabels: + additionalProperties: + type: string + default: {} + description: Extra labels that the [`Pod`]s must match in order to be exposed. They must _also_ still have a `Volume` referring to the listener. + type: object + ports: + description: Ports that should be exposed. + items: + properties: + name: + description: |- + The name of the port. + + The name of each port *must* be unique within a single [`Listener`]. + type: string + port: + description: The port number. + format: int32 + type: integer + protocol: + description: The layer-4 protocol (`TCP` or `UDP`). + nullable: true + type: string + required: + - name + - port + type: object + nullable: true + type: array + publishNotReadyAddresses: + default: true + description: Whether incoming traffic should also be directed to `Pod`s that are not `Ready`. + nullable: true + type: boolean + type: object + status: + description: Informs users about how to reach the [`Listener`]. + nullable: true + properties: + ingressAddresses: + description: All addresses that the [`Listener`] is currently reachable from. + items: + description: One address that a [`Listener`] is accessible from. + properties: + address: + description: The hostname or IP address to the [`Listener`]. + type: string + ports: + additionalProperties: + format: int32 + type: integer + description: Port mapping table. + type: object + required: + - address + - ports + type: object + nullable: true + type: array + nodePorts: + additionalProperties: + format: int32 + type: integer + description: |- + Port mappings for accessing the [`Listener`] on each [`Node`] that the [`Pod`]s are currently running on. + + This is only intended for internal use by listener-operator itself. This will be left unset if using a [`ListenerClass`] that does not require [`Node`]-local access. + nullable: true + type: object + serviceName: + description: The backing Kubernetes [`Service`]. + nullable: true + type: string + type: object + required: + - spec + title: Listener + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/manifests/csidriver.yaml b/deploy/manifests/csidriver.yaml new file mode 100644 index 00000000..c819bc84 --- /dev/null +++ b/deploy/manifests/csidriver.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: CSIDriver +metadata: + name: listeners.stackable.tech +spec: + attachRequired: false + podInfoOnMount: true + fsGroupPolicy: File + volumeLifecycleModes: + - Ephemeral + - Persistent diff --git a/deploy/manifests/listener-classes.yaml b/deploy/manifests/listener-classes.yaml new file mode 100644 index 00000000..0c404940 --- /dev/null +++ b/deploy/manifests/listener-classes.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: nodeport +spec: + serviceType: NodePort +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: lb-external +spec: + serviceType: LoadBalancer diff --git a/deploy/manifests/node-daemonset.yaml b/deploy/manifests/node-daemonset.yaml new file mode 100644 index 00000000..9b08a38a --- /dev/null +++ b/deploy/manifests/node-daemonset.yaml @@ -0,0 +1,69 @@ +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: listener-operator-node-daemonset + labels: + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + app.kubernetes.io/version: "0.1.0-nightly" +spec: + selector: + matchLabels: + app.kubernetes.io/role: node + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + template: + metadata: + labels: + app.kubernetes.io/role: node + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + spec: + serviceAccountName: listener-operator-serviceaccount + securityContext: {} + containers: + - name: listener-operator + securityContext: + privileged: true + runAsUser: 0 + image: "docker.stackable.tech/stackable/listener-operator:0.1.0-nightly" + imagePullPolicy: IfNotPresent + resources: {} + args: + - run + - node + env: + - name: CSI_ENDPOINT + value: /csi/csi.sock + - name: NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + volumeMounts: + - name: csi + mountPath: /csi + - name: mountpoint + mountPath: /var/lib/kubelet/pods + mountPropagation: Bidirectional + - name: node-driver-registrar + image: k8s.gcr.io/sig-storage/csi-node-driver-registrar:v2.4.0 + args: + - --csi-address=/csi/csi.sock + - --kubelet-registration-path=/var/lib/kubelet/plugins/listeners.stackable.tech/csi.sock + volumeMounts: + - name: registration-sock + mountPath: /registration + - name: csi + mountPath: /csi + volumes: + - name: registration-sock + hostPath: + path: /var/lib/kubelet/plugins_registry/listeners.stackable.tech-reg.sock + - name: csi + hostPath: + path: /var/lib/kubelet/plugins/listeners.stackable.tech/ + - name: mountpoint + hostPath: + path: /var/lib/kubelet/pods/ diff --git a/deploy/manifests/roles.yaml b/deploy/manifests/roles.yaml new file mode 100644 index 00000000..a5896c90 --- /dev/null +++ b/deploy/manifests/roles.yaml @@ -0,0 +1,76 @@ +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: listener-operator-clusterrole +rules: + - apiGroups: + - "" + resources: + - events + - services + verbs: + - get + - list + - watch + - create + - patch + - apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - watch + - create + - delete + - apiGroups: + - "" + resources: + - nodes + - persistentvolumeclaims + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - storage.k8s.io + resources: + - csinodes + - storageclasses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - patch + - apiGroups: + - events.k8s.io + resources: + - events + verbs: + - create + - apiGroups: + - listeners.stackable.tech + resources: + - listenerclasses + - listeners + verbs: + - get + - list + - watch + - apiGroups: + - listeners.stackable.tech + resources: + - listeners + - listeners/status + verbs: + - patch + - create diff --git a/deploy/manifests/serviceaccount.yaml b/deploy/manifests/serviceaccount.yaml new file mode 100644 index 00000000..5cb7fa4a --- /dev/null +++ b/deploy/manifests/serviceaccount.yaml @@ -0,0 +1,27 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: listener-operator-serviceaccount + labels: + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + app.kubernetes.io/version: "0.1.0-nightly" +--- +apiVersion: rbac.authorization.k8s.io/v1 +# This cluster role binding allows anyone in the "manager" group to read secrets in any namespace. +kind: ClusterRoleBinding +metadata: + name: listener-operator-clusterrolebinding + labels: + app.kubernetes.io/name: listener-operator + app.kubernetes.io/instance: listener-operator + app.kubernetes.io/version: "0.1.0-nightly" +subjects: + - kind: ServiceAccount + name: listener-operator-serviceaccount + namespace: default +roleRef: + kind: ClusterRole + name: listener-operator-clusterrole + apiGroup: rbac.authorization.k8s.io diff --git a/deploy/manifests/storageclass.yaml b/deploy/manifests/storageclass.yaml new file mode 100644 index 00000000..59f2c5d5 --- /dev/null +++ b/deploy/manifests/storageclass.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: listeners.stackable.tech +provisioner: listeners.stackable.tech +volumeBindingMode: WaitForFirstConsumer diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..80434596 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,35 @@ +# ============= +# This file is automatically generated from the templates in stackabletech/operator-templating +# DON'T MANUALLY EDIT THIS FILE +# ============= +FROM docker.stackable.tech/stackable/ubi8-rust-builder AS builder + +FROM registry.access.redhat.com/ubi8/ubi-minimal AS operator + +ARG VERSION +ARG RELEASE="1" + +LABEL name="Stackable Operator for Stackable Listener Operator" \ + maintainer="info@stackable.de" \ + vendor="Stackable GmbH" \ + version="${VERSION}" \ + release="${RELEASE}" \ + summary="Deploy and manage Stackable Listener Operator clusters." \ + description="Deploy and manage Stackable Listener Operator clusters." + +# Update image +RUN microdnf install -y yum \ + && yum -y update-minimal --security --sec-severity=Important --sec-severity=Critical \ + && yum clean all \ + && microdnf clean all + +COPY LICENSE /licenses/LICENSE + +COPY --from=builder /app/stackable-listener-operator / + +RUN groupadd -g 1000 stackable && adduser -u 1000 -g stackable -c 'Stackable Operator' stackable + +USER stackable:stackable + +ENTRYPOINT ["/stackable-listener-operator"] +CMD ["run"] diff --git a/docs/antora.yml b/docs/antora.yml new file mode 100644 index 00000000..8307319c --- /dev/null +++ b/docs/antora.yml @@ -0,0 +1,7 @@ +--- +name: listener-operator +version: "nightly" +title: Stackable Listener Operator +nav: + - modules/ROOT/nav.adoc +prerelease: true diff --git a/docs/modules/ROOT/examples/listenerclass-internal-gke.yaml b/docs/modules/ROOT/examples/listenerclass-internal-gke.yaml new file mode 100644 index 00000000..04fafba8 --- /dev/null +++ b/docs/modules/ROOT/examples/listenerclass-internal-gke.yaml @@ -0,0 +1,9 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: internal +spec: + serviceType: LoadBalancer + serviceAnnotations: + networking.gke.io/load-balancer-type: Internal diff --git a/docs/modules/ROOT/examples/listenerclass-public-gke.yaml b/docs/modules/ROOT/examples/listenerclass-public-gke.yaml new file mode 100644 index 00000000..7709281b --- /dev/null +++ b/docs/modules/ROOT/examples/listenerclass-public-gke.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: public +spec: + serviceType: LoadBalancer diff --git a/docs/modules/ROOT/examples/listenerclass-public-onprem.yaml b/docs/modules/ROOT/examples/listenerclass-public-onprem.yaml new file mode 100644 index 00000000..ed27bace --- /dev/null +++ b/docs/modules/ROOT/examples/listenerclass-public-onprem.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: public +spec: + serviceType: NodePort diff --git a/docs/modules/ROOT/examples/usage-pod.yaml b/docs/modules/ROOT/examples/usage-pod.yaml new file mode 100644 index 00000000..0c252a7a --- /dev/null +++ b/docs/modules/ROOT/examples/usage-pod.yaml @@ -0,0 +1,29 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: example-public-pod +spec: + volumes: + - name: listener + ephemeral: # <1> + volumeClaimTemplate: + metadata: + annotations: + listeners.stackable.tech/listener-class: public # <2> + spec: + storageClassName: listeners.stackable.tech + accessModes: + - ReadWriteMany + resources: + requests: + storage: "1" + containers: + - name: nginx + image: nginx + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: listener + mountPath: /listener # <3> diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc new file mode 100644 index 00000000..35262ec2 --- /dev/null +++ b/docs/modules/ROOT/nav.adoc @@ -0,0 +1,8 @@ +* xref:building.adoc[] +* xref:installation.adoc[] +* xref:usage.adoc[] +* Concepts +** xref:listener.adoc[] +** xref:listenerclass.adoc[] +** xref:volume.adoc[] + diff --git a/docs/modules/ROOT/pages/building.adoc b/docs/modules/ROOT/pages/building.adoc new file mode 100644 index 00000000..f91c7c90 --- /dev/null +++ b/docs/modules/ROOT/pages/building.adoc @@ -0,0 +1,95 @@ += Building the Operator + +This operator is written in Rust. + +It is developed against the latest stable Rust release, and we currently don't support any older versions. + +The Listener Operator is a https://github.com/container-storage-interface/spec/blob/master/spec.md[Container Storage Interface (CSI)] provider plugin +for the local Kubelet, which means that it should only be executed inside a Kubernetes `Pod`. Currently, the following ways are supported to build the Listener Operator: + +* `docker build` +* https://nixos.org/[Nix] + +The `docker build` command is currently the primary deployment target, and the official images are built +using it. However, Nix has much faster incremental build and deployment times, making it ideal for local development. + +== Docker + +To build and deploy to the active Kind cluster, run: + +[source,console] +---- +$ echo Building with Docker +# Ensure that all submodules are up-to-date +$ git submodule update --recursive --init +# Create a unique image ID +$ REPO=listener-operator +$ TAG="$(uuidgen)" +# Build the image +$ docker build . -f docker/Dockerfile -t "$REPO:$TAG" +# Load the image onto the Kind nodes +$ kind load docker-image "$REPO:$TAG" +# Deploy latest CRD +$ docker run --rm "$REPO:$TAG" crd | kubectl apply -f- +# Deploy +$ helm upgrade listener-operator deploy/helm/listener-operator \ + --install \ + --set-string "image.repository=$REPO,image.tag=$TAG" +---- + +== Nix + +To build and deploy to the active Kind cluster, run the following Bash commands: + +[source,console] +---- +$ echo Building with Nix +# Ensure that all submodules are up-to-date +$ git submodule update --recursive --init +# Use crate2nix (https://github.com/kolloch/crate2nix) to convert +# Cargo.lock into a Nix derivation +$ nix run -f . crate2nix generate +# Build the Docker images +# A custom Docker repository can be specified by appending +# `--argstr dockerName my-custom-registry/listener-operator` +$ nix build -f . docker +# Load the images onto the Kind nodes +# Nix does not use the Docker daemon, instead it builds individual +# layers, as well as a script (`result/load-image`) that combines +# them into a Docker image archive. +# `load-image` can also be piped into `docker load` to prepare for +# pushing to a registry. +$ ./result/load-image > image.tar && kind load image-archive image.tar +# On single-node kind clusters it is slightly more efficient to run +# `./result/load-image | kind load image-archive /dev/stdin` instead. +# Deploy +$ kubectl apply -f result/crds.yaml +$ helm upgrade listener-operator deploy/helm/listener-operator \ + --install \ + --set-string "image.repository=$(cat result/image-repo),image.tag=$(cat result/image-tag)" +---- + +You may need to add `extra-experimental-features = nix-command` to `/etc/nix/nix.conf`, or add `--experimental-features nix-command` to the Nix commands. + +You can also use https://tilt.dev/[Tilt] to automatically rebuild and redeploy when files are changed: + +[source,console] +---- +$ nix run -f . tilt up +---- + +== K3d + +The Listener Operator, as with most CSI providers, requires the Kubernetes node's root folder to be mounted as `rshared`. K3d does not do this by default, +but can be prodded into doing this by running `mount --make-rshared /` in each node container. + +To do this for each running K3d node, run the following script: + +[source,console] +---- +for i in $(k3d node list -o json | jq -r .[].name); do + docker exec -it $i mount --make-rshared / +done +---- + +NOTE: This is _not_ persistent, and must be re-executed every time the cluster (or a node in it) is restarted. diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/modules/ROOT/pages/index.adoc new file mode 100644 index 00000000..f8568992 --- /dev/null +++ b/docs/modules/ROOT/pages/index.adoc @@ -0,0 +1,3 @@ += Stackable Listener Operator + +This is an operator for Kubernetes that provisions network listeners according to the cluster policy, and injects connection parameters into Pods. diff --git a/docs/modules/ROOT/pages/installation.adoc b/docs/modules/ROOT/pages/installation.adoc new file mode 100644 index 00000000..35a19313 --- /dev/null +++ b/docs/modules/ROOT/pages/installation.adoc @@ -0,0 +1,29 @@ += Installation + +There are two ways to run the Stackable Listener Operator: + +1. Helm managed Docker container deployment on Kubernetes + +2. Build from source + + +== Helm +Helm allows you to download and deploy Stackable operators on Kubernetes and is by far the easiest installation method. First ensure that you have installed the Stackable Operators Helm repository: + +[source,console] +---- +$ helm repo add stackable https://repo.stackable.tech/repository/helm-stable/ +---- + +Then install the Stackable Listener Operator + +[source,console] +---- +$ helm install listener-operator stackable/listener-operator +---- + +Helm will deploy the operator in Kubernetes containers and apply the CRDs. You're now ready to expose services! + +== Building the operator from source + +See xref:building.adoc[] for more information. diff --git a/docs/modules/ROOT/pages/listener.adoc b/docs/modules/ROOT/pages/listener.adoc new file mode 100644 index 00000000..2ef6aedd --- /dev/null +++ b/docs/modules/ROOT/pages/listener.adoc @@ -0,0 +1,32 @@ += `Listener` + +A `Listener` object exposes a set of ``Pod``s according to the rules of a xref:listenerclass.adoc[], but it also adds a couple of other +features that are useful for the Stackable platform at large. + +== `ListenerClass` + +The exact rules of pod exposure are dictated by the specified xref:listenerclass.adoc[], which allow a single `Listener` definition to be reused in different clusters, regardless of the Kubernetes distribution or cloud provider. + +== Address API + +A `Listener` writes back all addresses that it can be reached on to `Listener.status.ingress_addresses`, which can then be used to generate discovery information. Contrary to Kubernetes' `Service`, this is done regardless of the type of service, and transparently also contains information about remapped ports. + +== Address volume projection + +`Listener` objects can be mounted into a `Pod` as a `PersistentVolumeClaim`, which contains information about how the `Pod` should request that external clients refer to it. + +For example, if the volume is mounted to `/stackable/listener`, the primary address can be read from `/stackable/listener/default-address/address`, and the public `http` port number can be read from `/stackable/listener/default-address/ports/http`. + +== Per-replica listeners + +A `Listener` PVC can also specify a xref:listenerclass.adoc[] rather than a `Listener`, in which case a `Listener` object is created +automatically. These PVCs can automatically be created for each replica using either ``StatefulSet``'s `volumeClaimTemplates` (for long-lived listeners that will +be kept across replica restarts and upgrades) or ``Pod``'s `volumes[].ephemeral` (for temporary listeners that are deleted when their corresponding `Pod` is deleted). + +== Sticky scheduling + +When mounting a `Listener` PVC, it will be made "sticky" to that node if the xref:listenerclass.adoc[] uses a strategy that depends on the node +that the workload is running on. + +Keep in mind that this will only work correctly when using long-lived PVCs (such as via ``StatefulSet``'s `volumeClaimTemplates`). Ephemeral PVCs +will be "reset" for every pod that is created, even if they refer to a long-lived `Listener` object. diff --git a/docs/modules/ROOT/pages/listenerclass.adoc b/docs/modules/ROOT/pages/listenerclass.adoc new file mode 100644 index 00000000..260065f3 --- /dev/null +++ b/docs/modules/ROOT/pages/listenerclass.adoc @@ -0,0 +1,26 @@ += `ListenerClass` + +A `ListenerClass` defines a category of listeners. For example, this could be "VPC-internal service", "internet-accessible service", or "K8s-internal service". +The `ListenerClass` then defines how this intent is realized in a given cluster. + +For example, a Google Kubernetes Engine (GKE) cluster might want to expose all internet-facing services using a managed load balancer, since GKE nodes are +relatively short-lived and don't have stable addresses: + +[source,yaml] +---- +include::example$listenerclass-public-gke.yaml[] +---- + +On the other hand, an on-premise cluster might not have dedicated load balancer infrastructure at all, but instead use "pet" Nodes which may be expected to live for years. This might lead administrators of such systems to prefer exposing node ports directly instead: + +[source,yaml] +---- +include::example$listenerclass-public-onprem.yaml[] +---- + +Finally, it can be desirable to add additional annotations to a `Service`. For example, a user might want to only expose some services inside a given cloud vendor VPC. How exactly this is accomplished depends on the cloud provider in question, but for GKE this requires the annotation `networking.gke.io/load-balancer-type`: + +[source,yaml] +---- +include::example$listenerclass-internal-gke.yaml[] +---- diff --git a/docs/modules/ROOT/pages/usage.adoc b/docs/modules/ROOT/pages/usage.adoc new file mode 100644 index 00000000..d9dd623c --- /dev/null +++ b/docs/modules/ROOT/pages/usage.adoc @@ -0,0 +1,27 @@ += Usage + +The operator creates a xref:listener.adoc[] for each mounted CSI volume with `storageClassName: listeners.stackable.tech`. + +A minimal exposed `Pod` looks like this: + +[source,yaml] +---- +include::example$usage-pod.yaml[] +---- +<1> Defines an _ephemeral_ listener, meaning that it will automatically be deleted when the `Pod` is. +<2> Defines that we want to expose this pod by automatically creating a service according to the xref:listenerclass.adoc[] `public`. +<3> Mounts metadata about the `Listener` (such as the port mapping and IP address) into `/listener`. The volume *must* be mounted, even if this data is never used by the `Pod` itself. + +The exact xref:listenerclass.adoc[] is going to depend on the Kubernetes environment, but should often look like this for public clouds: + +[source,yaml] +---- +include::example$listenerclass-public-gke.yaml[] +---- + +Or like this for on-premise environments: + +[source,yaml] +---- +include::example$listenerclass-public-onprem.yaml[] +---- diff --git a/docs/modules/ROOT/pages/volume.adoc b/docs/modules/ROOT/pages/volume.adoc new file mode 100644 index 00000000..13d74c5a --- /dev/null +++ b/docs/modules/ROOT/pages/volume.adoc @@ -0,0 +1,58 @@ += Volume + + +The Listener Operator acts as a CSI `PersistentVolume`, which helps it to stabilize network addresses, inject pod metadata and expose individual pods. + +== Stable addresses + +Some xref:listenerclass.adoc[] strategies, such as `NodePort`, tie the public address to the Kubernetes node that the `Pod` is running on. When this address must be configured statically in clients +(such as for HDFS NameNodes), then Kubernetes' default "floating" scheduling either requires all clients to be reconfigured every time something moves, or for all clients to proxy their traffic through +a single static node, which then becomes a single point of failure (along with the node that the workload is running on). + +Mounting listeners into Pods as `PersistentVolume` allows the Listener Operator to pin these workloads to one node. Note that this only happens for xref:listenerclass.adoc[]es that actually benefit +from pinning. + +== Pod metadata injection + +Some services (such as Kafka) need to know their external address, so that they can advertise it to their own replica discovery mechanism. xref:listener.adoc[] volumes contain a file tree that exposes +this information: + +[square] +* `default-address/`- A symlink to `addresses/{primary address}` +* `addresses/` - Contains information about all addresses associated with this xref:listener.adoc[] +[square] +** `\{address\}/` - A folder is created for each address +[square] +*** `address` - Contains the Pod's address (IP address or hostname) +*** `ports/` - Contains port numbers for each named port +[square] +**** `{port name}` - Contains the public port number for this named port + +== Individual pod exposure + +Sometimes each replica must be exposed individually, for example because clients need to access data on a specific shard. `PersistentVolumeClaim` templates can be used to provision this automatically. + +=== `StatefulSet` `volumeClaimTemplates` + +The `volumeClaimTemplates` allow volumes to be provisioned for each `StatefulSet` replica. These volumes are _persistent_, and will not be deleted when the `Pod` or `StatefulSet` is. This makes them useful for provisioning addresses that must be hard-coded into client configuration. + +=== Pod-scoped ephemeral volumes + +`Pod.spec.volumes[].ephemeral` allows volumes to be provisioned for each `Pod`. These volumes are tied to the lifetime of the `Pod` and will be deleted along with it. This makes them useful for provisioning temporary addresses that will be discovered out of band (such as for HDFS DataNodes). + +== Reference + +All configuration must be specified as `annotations` on the `PersistentVolumeClaim`. The following attributes are currently supported: + +=== `listeners.stackable.tech/listener-name` + +*Required*: If `listeners.stackable.tech/listener-class` is not specified + +Provisions metadata about an existing xref:listener.adoc[] that was created manually. + +=== `listeners.stackable.tech/listener-class` + +*Required*: If `listeners.stackable.tech/listener-name` is not specified + +Provisions a new xref:listener.adoc[] using the specified xref:listenerclass.adoc[]. The created xref:listener.adoc[] will expose +all of the ``Pod``'s ports. diff --git a/examples/nginx-lb.yaml b/examples/nginx-lb.yaml new file mode 100644 index 00000000..715ddbf4 --- /dev/null +++ b/examples/nginx-lb.yaml @@ -0,0 +1,64 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: exposed-nginx-lb +spec: + replicas: 1 + serviceName: "" + selector: + matchLabels: + app: exposed-nginx-lb + template: + metadata: + labels: + app: exposed-nginx-lb + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - name: http + containerPort: 9999 + volumeMounts: + - name: listener + mountPath: /usr/share/nginx/html/listener + - name: config + mountPath: /etc/nginx/templates + volumes: + - name: config + configMap: + name: exposed-nginx-lb-config + volumeClaimTemplates: + - metadata: + name: listener + annotations: + listeners.stackable.tech/listener-class: lb-external + spec: + accessModes: + - ReadWriteMany + storageClassName: listeners.stackable.tech + resources: + requests: + storage: 1 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: exposed-nginx-lb-config +data: + default.conf.template: | + server { + listen 9999; + listen [::]:9999; + + location / { + root /usr/share/nginx/html; + index index.html index.htm; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } diff --git a/examples/nginx-nodeport.yaml b/examples/nginx-nodeport.yaml new file mode 100644 index 00000000..e6b302bb --- /dev/null +++ b/examples/nginx-nodeport.yaml @@ -0,0 +1,37 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: exposed-nginx-nodeport +spec: + replicas: 1 + serviceName: "" + selector: + matchLabels: + app: exposed-nginx-nodeport + template: + metadata: + labels: + app: exposed-nginx-nodeport + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: listener + mountPath: /usr/share/nginx/html/listener + volumeClaimTemplates: + - metadata: + name: listener + annotations: + listeners.stackable.tech/listener-class: nodeport + spec: + accessModes: + - ReadWriteMany + storageClassName: listeners.stackable.tech + resources: + requests: + storage: 1 diff --git a/examples/nginx-preprovisioned-lb.yaml b/examples/nginx-preprovisioned-lb.yaml new file mode 100644 index 00000000..7d7d9464 --- /dev/null +++ b/examples/nginx-preprovisioned-lb.yaml @@ -0,0 +1,47 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: exposed-nginx-preprovisioned-lb +spec: + replicas: 1 + serviceName: "" + selector: + matchLabels: + app: exposed-nginx-preprovisioned-lb + template: + metadata: + labels: + app: exposed-nginx-preprovisioned-lb + spec: + containers: + - name: nginx + image: nginx:latest + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: listener + mountPath: /usr/share/nginx/html/listener + volumeClaimTemplates: + - metadata: + name: listener + annotations: + listeners.stackable.tech/listener-name: exposed-nginx-preprovisioned-lb + spec: + accessModes: + - ReadWriteMany + storageClassName: listeners.stackable.tech + resources: + requests: + storage: 1 +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: Listener +metadata: + name: exposed-nginx-preprovisioned-lb +spec: + className: lb-external + ports: + - name: http + port: 9998 diff --git a/nix/sources.json b/nix/sources.json new file mode 100644 index 00000000..681cc2d3 --- /dev/null +++ b/nix/sources.json @@ -0,0 +1,14 @@ +{ + "nixpkgs": { + "branch": "nixpkgs-unstable", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3bbb296d9a0088c314ce83038b896753bbe33acb", + "sha256": "0dlq1zjcki30k2afg749a7c34vyb86sx1irab48l2g214nhj83hw", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/3bbb296d9a0088c314ce83038b896753bbe33acb.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/nix/sources.nix b/nix/sources.nix new file mode 100644 index 00000000..9a01c8ac --- /dev/null +++ b/nix/sources.nix @@ -0,0 +1,194 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + submodules = if spec ? submodules then spec.submodules else false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules == true + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + {} + else {}; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; + in + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/python/cargo_version.py b/python/cargo_version.py new file mode 100755 index 00000000..245e5e1c --- /dev/null +++ b/python/cargo_version.py @@ -0,0 +1,197 @@ +#!/usr/bin/env python3 +# +# Utility for viewing and managing versions of cargo workspaces and crates. +# For workspaces, it assumes that all crate members use a single shared version. +# +# usage: cargo-version.py [-h] [-p PROJECT] [-r] [-n {major,minor,patch}] [-s SET] [-o] [-m PRERELEASE] +# +import argparse + +import toml +import semver + + +class Crate: + def __init__(self, path, name, version, dependencies): + self.path = path + self.name = name + self.version = version + self.dependencies = dependencies + + def with_dependencies(self, names): + deps = {k: v for k, v in self.dependencies.items() if k in names} + return Crate(self.path, self.name, self.version, deps) + + @classmethod + def finalize(cls, version): + return str(semver.VersionInfo.parse(version).finalize_version()) + + @classmethod + def bump_level(cls, version, level): + ver = semver.VersionInfo.parse(version) + if level == "major": + return str(ver.bump_major()) + if level == "minor": + return str(ver.bump_minor()) + if level == "patch": + return str(ver.bump_patch()) + + return str(ver.bump_prerelease("nightly"))[ + :-2 + ] # remove the .1 suffix that semver always adds to the prererelease. + + @classmethod + def prerelease(cls, version, prerelease): + ver = semver.VersionInfo.parse(version) + return str(semver.VersionInfo(ver.major, ver.minor, ver.patch, prerelease)) + + def finalize_version(self): + return Crate( + self.path, self.name, Crate.finalize(self.version), self.dependencies.copy() + ) + + def bump_version(self, level): + return Crate( + self.path, + self.name, + Crate.bump_level(self.version, level), + self.dependencies.copy(), + ) + + def set_version(self, version): + return Crate(self.path, self.name, version, self.dependencies.copy()) + + def set_prerelease(self, prerelease): + return Crate( + self.path, + self.name, + Crate.prerelease(self.version, prerelease), + self.dependencies.copy(), + ) + + def next_version(self): + return Crate( + self.path, + self.name, + str(semver.VersionInfo.parse(self.version).next_version("patch")), + self.dependencies.copy(), + ) + + def show_version(self): + return self.version + + def save(self, previous): + contents = [] + cargo_file = f"{self.path}/Cargo.toml" + with open(cargo_file, "r", encoding="utf8") as ctl: + for line in ctl.readlines(): + if line.startswith("version"): + line = line.replace(previous.version, self.version) + else: + for dname, dversion in self.dependencies.items(): + if line.startswith(dname): + line = line.replace(previous.dependencies[dname], dversion) + contents.append(line) + with open(cargo_file, "w", encoding="utf8") as ctl: + ctl.write("".join(contents)) + + def __str__(self): + return f"Crate({self.path}, {self.name}, {self.version}, {self.dependencies})" + + +class Workspace: + def __init__(self, crates): + names = {c.name for c in crates} + self.crates = {c.name: c.with_dependencies(names) for c in crates} + + def finalize_version(self): + crates = {c.name: c.finalize_version() for c in self.crates.values()} + return Workspace(Workspace.update_dependencies(crates).values()) + + def bump_version(self, level): + crates = {c.name: c.bump_version(level) for c in self.crates.values()} + return Workspace(Workspace.update_dependencies(crates).values()) + + def set_version(self, version): + crates = {c.name: c.set_version(version) for c in self.crates.values()} + return Workspace(Workspace.update_dependencies(crates).values()) + + def set_prerelease(self, prerelease): + crates = {c.name: c.set_prerelease(prerelease) for c in self.crates.values()} + return Workspace(Workspace.update_dependencies(crates).values()) + + def next_version(self): + crates = {c.name: c.next_version() for c in self.crates.values()} + return Workspace(Workspace.update_dependencies(crates).values()) + + def show_version(self): + for cts in self.crates.values(): + return cts.show_version() + return "0.0.0" + + @classmethod + def update_dependencies(cls, crate_dict): + for crate in crate_dict.values(): + for dep in crate.dependencies.keys(): + crate.dependencies[dep] = crate_dict[dep].version + return crate_dict + + def __str__(self): + return f"Workspace({[str(c) for c in self.crates.values()]})" + + def save(self, previous): + for crn in self.crates.keys(): + self.crates[crn].save(previous.crates[crn]) + + +def load(root): + ctl = toml.load(f"{root}/Cargo.toml") + if "workspace" in ctl: + return Workspace( + [load(f"{root}/{path}") for path in ctl["workspace"]["members"]] + ) + + return Crate( + path=root, + name=ctl["package"]["name"], + version=ctl["package"]["version"], + dependencies={ + dn: ctl["dependencies"][dn]["version"] + for dn in ctl["dependencies"] + if "version" in ctl["dependencies"][dn] + }, + ) + + +def parse_args(): + parser = argparse.ArgumentParser(description="Change versions of cargo projects.") + parser.add_argument("-p", "--project", help="Project folder", default=".") + parser.add_argument("-r", "--release", help="Version", action="store_true") + parser.add_argument( + "-n", "--next", help="Version", choices=["major", "minor", "patch"] + ) + parser.add_argument("-s", "--set", help="Version") + parser.add_argument("-o", "--show", help="Version", action="store_true") + parser.add_argument("-m", "--prerelease", help="Set pre-prelease string.") + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + old = load(args.project.rstrip("/")) + if args.release: + new = old.finalize_version() + new.save(old) + elif args.next: + new = old.bump_version(args.next).bump_version("prerelease") + new.save(old) + elif args.set: + # sanity check + semver.VersionInfo.parse(args.set) + new = old.set_version(args.set) + new.save(old) + elif args.prerelease: + new = old.set_prerelease(args.prerelease) + new.save(old) + elif args.show: + print(old.show_version()) diff --git a/python/requirements.txt b/python/requirements.txt new file mode 100644 index 00000000..143c5421 --- /dev/null +++ b/python/requirements.txt @@ -0,0 +1,2 @@ +semver==2.13.0 +toml==0.10.2 diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..df34602d --- /dev/null +++ b/renovate.json @@ -0,0 +1,32 @@ +{ + "extends": [ + "helpers:pinGitHubActionDigests" + ], + "labels": [ + "dependencies" + ], + "prCreation": "not-pending", + "reviewers": [ + "team:developers" + ], + "rollbackPrs": true, + "schedule": [ + "after 5:00 and before 6:00 every weekday" + ], + "timezone": "Europe/Berlin", + "packageRules": [ + { + "matchUpdateTypes": [ + "patch" + ], + "groupName": "All dependencies (patch only)" + } + ], + "lockFileMaintenance": { + "enabled": true, + "schedule": [ + "after 5:00 and before 6:00 every weekday" + ] + }, + "ignorePaths": [".github/workflows/build.yml", ".github/workflows/daily_security.yml", ".github/workflows/reviewdog.yaml"] +} diff --git a/rust/csi-grpc/Cargo.toml b/rust/csi-grpc/Cargo.toml new file mode 100644 index 00000000..341a3507 --- /dev/null +++ b/rust/csi-grpc/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "csi-grpc" +authors = ["Stackable GmbH "] +description = "Rust bindings for the CSI GRPC API" +license = "Apache-2.0" +version = "0.1.0-nightly" +edition = "2021" +repository = "https://github.com/stackabletech/listener-operator" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +prost = "0.10.3" +prost-types = "0.10.1" +tonic = "0.7.2" + +[build-dependencies] +tonic-build = "0.7.2" \ No newline at end of file diff --git a/rust/csi-grpc/build.rs b/rust/csi-grpc/build.rs new file mode 100644 index 00000000..155e936c --- /dev/null +++ b/rust/csi-grpc/build.rs @@ -0,0 +1,11 @@ +//! Compile Rust code from gRPC definition files stored in the vendor/csi directory. + +use std::path::PathBuf; + +fn main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is required")); + tonic_build::configure() + .file_descriptor_set_path(out_dir.join("file_descriptor_set.bin")) + .compile(&["csi.proto"], &["vendor/csi"]) + .unwrap(); +} diff --git a/rust/csi-grpc/src/lib.rs b/rust/csi-grpc/src/lib.rs new file mode 100644 index 00000000..d6adb506 --- /dev/null +++ b/rust/csi-grpc/src/lib.rs @@ -0,0 +1,10 @@ +//! Include gRPC definition files that have been generated by `build.rs` + +pub static FILE_DESCRIPTOR_SET_BYTES: &[u8] = + include_bytes!(concat!(env!("OUT_DIR"), "/file_descriptor_set.bin")); + +// Trivial warnings that come from prost-generated code +#[allow(clippy::derive_partial_eq_without_eq)] +pub mod v1 { + tonic::include_proto!("csi.v1"); +} diff --git a/rust/csi-grpc/vendor/csi b/rust/csi-grpc/vendor/csi new file mode 160000 index 00000000..ad238e5c --- /dev/null +++ b/rust/csi-grpc/vendor/csi @@ -0,0 +1 @@ +Subproject commit ad238e5cad5a27188fcdae1ff3ace3f612638ca5 diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml new file mode 100644 index 00000000..cddd132f --- /dev/null +++ b/rust/operator-binary/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "stackable-listener-operator" +authors = ["Stackable GmbH "] +description = "Stackable Operator for network listeners" +license = "OSL-3.0" +version = "0.1.0-nightly" +edition = "2021" +repository = "https://github.com/stackabletech/listener-operator" + +[dependencies] +# Need to keep this in sync with our patched h2 build +h2 = "=0.3.7" + +clap = { version = "3.1.18", features = ["derive"] } +futures = "0.3.21" +libc = "0.2.125" +pin-project = "1.0.10" +prost = "0.10.3" +socket2 = { version = "0.4.4", features = ["all"] } +stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", tag = "0.25.2" } +tokio = { version = "1.18.2", features = ["full"] } +tokio-stream = { version = "0.1.8", features = ["net"] } +tonic = "0.7.2" +tonic-reflection = "0.4.0" +anyhow = "1.0.57" +serde = "1.0.137" +snafu = "0.7.1" +strum = "0.24.1" +csi-grpc = { path = "../csi-grpc" } + +[build-dependencies] +built = { version = "0.5.1", features = ["chrono", "git2"] } diff --git a/rust/operator-binary/build.rs b/rust/operator-binary/build.rs new file mode 100644 index 00000000..ed36fcf0 --- /dev/null +++ b/rust/operator-binary/build.rs @@ -0,0 +1,13 @@ +use std::path::PathBuf; + +fn main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR is required")); + built::write_built_file_with_opts( + // built's env module depends on a whole bunch of variables that crate2nix doesn't provide + // so we grab the specific env variables that we care about out ourselves instead. + built::Options::default().set_env(false), + "Cargo.toml".as_ref(), + &out_dir.join("built.rs"), + ) + .unwrap(); +} diff --git a/rust/operator-binary/src/csi_server/controller.rs b/rust/operator-binary/src/csi_server/controller.rs new file mode 100644 index 00000000..1b03dfe9 --- /dev/null +++ b/rust/operator-binary/src/csi_server/controller.rs @@ -0,0 +1,228 @@ +use csi_grpc as csi; +use serde::{de::IntoDeserializer, Deserialize}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + commons::listener::{Listener, ListenerClass, ServiceType}, + k8s_openapi::api::core::v1::PersistentVolumeClaim, + kube::{core::DynamicObject, runtime::reflector::ObjectRef}, +}; +use tonic::{Request, Response, Status}; + +use crate::utils::error_full_message; + +use super::{tonic_unimplemented, ListenerSelector, ListenerVolumeContext}; + +pub struct ListenerOperatorController { + pub client: stackable_operator::client::Client, +} + +#[derive(Deserialize)] +struct ControllerVolumeParams { + #[serde(rename = "csi.storage.k8s.io/pvc/name")] + pvc_name: String, + #[serde(rename = "csi.storage.k8s.io/pvc/namespace")] + pvc_namespace: String, +} + +#[derive(Snafu, Debug)] +#[snafu(module)] +enum CreateVolumeError { + #[snafu(display("failed to decode request parameters"))] + DecodeRequestParams { source: serde::de::value::Error }, + #[snafu(display("failed to get {obj}"))] + GetObject { + source: stackable_operator::error::Error, + obj: Box>, + }, + #[snafu(display("failed to decode volume context"))] + DecodeVolumeContext { source: serde::de::value::Error }, + #[snafu(display("{listener} does not specify a listener class"))] + NoListenerClass { listener: ObjectRef }, +} + +impl From for Status { + fn from(err: CreateVolumeError) -> Self { + let full_msg = error_full_message(&err); + // Convert to an appropriate tonic::Status representation and include full error message + match err { + CreateVolumeError::DecodeRequestParams { .. } => Status::invalid_argument(full_msg), + CreateVolumeError::DecodeVolumeContext { .. } => Status::invalid_argument(full_msg), + CreateVolumeError::NoListenerClass { .. } => Status::invalid_argument(full_msg), + CreateVolumeError::GetObject { .. } => Status::unavailable(full_msg), + } + } +} + +#[tonic::async_trait] +impl csi::v1::controller_server::Controller for ListenerOperatorController { + async fn controller_get_capabilities( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::ControllerGetCapabilitiesResponse { + capabilities: vec![csi::v1::ControllerServiceCapability { + r#type: Some(csi::v1::controller_service_capability::Type::Rpc( + csi::v1::controller_service_capability::Rpc { + r#type: + csi::v1::controller_service_capability::rpc::Type::CreateDeleteVolume + .into(), + }, + )), + }], + })) + } + + async fn create_volume( + &self, + request: Request, + ) -> Result, Status> { + use create_volume_error::*; + let request = request.into_inner(); + let ControllerVolumeParams { + pvc_name, + pvc_namespace: ns, + } = ControllerVolumeParams::deserialize(request.parameters.into_deserializer()) + .context(create_volume_error::DecodeRequestParamsSnafu)?; + let pvc = self + .client + .get::(&pvc_name, Some(&ns)) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(&pvc_name) + .within(&ns) + .erase(), + })?; + let raw_volume_context = pvc.metadata.annotations.unwrap_or_default(); + let ListenerVolumeContext { listener_selector } = + ListenerVolumeContext::deserialize(raw_volume_context.clone().into_deserializer()) + .context(create_volume_error::DecodeVolumeContextSnafu)?; + let listener_class_name = match listener_selector { + ListenerSelector::Listener(listener_name) => { + let listener = self + .client + .get::(&listener_name, Some(&ns)) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(&listener_name) + .within(&ns) + .erase(), + })?; + listener + .spec + .class_name + .clone() + .with_context(|| NoListenerClassSnafu { + listener: ObjectRef::from_obj(&listener), + })? + } + ListenerSelector::ListenerClass(listener_class) => listener_class, + }; + let listener_class = self + .client + .get::(&listener_class_name, None) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(&listener_class_name) + .within(&ns) + .erase(), + })?; + Ok(Response::new(csi::v1::CreateVolumeResponse { + volume: Some(csi::v1::Volume { + capacity_bytes: 0, + volume_id: request.name, + volume_context: raw_volume_context.into_iter().collect(), + content_source: None, + accessible_topology: match listener_class.spec.service_type { + // Pick the top node (as selected by the CSI client) and "stick" to that + // Since we want clients to have a stable address to connect to + ServiceType::NodePort => request + .accessibility_requirements + .unwrap_or_default() + .preferred + .into_iter() + .take(1) + .collect(), + // Load balancers have no relationship to any particular node, so don't try to be sticky + ServiceType::LoadBalancer => Vec::new(), + }, + }), + })) + } + + async fn delete_volume( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::DeleteVolumeResponse {})) + } + + async fn controller_publish_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn controller_unpublish_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn validate_volume_capabilities( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn list_volumes( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn get_capacity( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn create_snapshot( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn delete_snapshot( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn list_snapshots( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn controller_expand_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn controller_get_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } +} diff --git a/rust/operator-binary/src/csi_server/identity.rs b/rust/operator-binary/src/csi_server/identity.rs new file mode 100644 index 00000000..6d9ba72e --- /dev/null +++ b/rust/operator-binary/src/csi_server/identity.rs @@ -0,0 +1,56 @@ +use std::collections::HashMap; + +use clap::crate_version; +use csi_grpc as csi; +use tonic::{Request, Response, Status}; + +use crate::OPERATOR_KEY; + +pub struct ListenerOperatorIdentity; + +#[tonic::async_trait] +impl csi::v1::identity_server::Identity for ListenerOperatorIdentity { + async fn get_plugin_info( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::GetPluginInfoResponse { + name: OPERATOR_KEY.to_string(), + vendor_version: crate_version!().to_string(), + manifest: HashMap::new(), + })) + } + + async fn get_plugin_capabilities( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::GetPluginCapabilitiesResponse { + capabilities: vec![ + csi::v1::PluginCapability { + r#type: Some(csi::v1::plugin_capability::Type::Service( + csi::v1::plugin_capability::Service { + r#type: + csi::v1::plugin_capability::service::Type::VolumeAccessibilityConstraints + .into(), + }, + )), + }, + csi::v1::PluginCapability { + r#type: Some(csi::v1::plugin_capability::Type::Service( + csi::v1::plugin_capability::Service { + r#type: csi::v1::plugin_capability::service::Type::ControllerService.into(), + }, + )), + }, + ], + })) + } + + async fn probe( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::ProbeResponse { ready: Some(true) })) + } +} diff --git a/rust/operator-binary/src/csi_server/mod.rs b/rust/operator-binary/src/csi_server/mod.rs new file mode 100644 index 00000000..952670ac --- /dev/null +++ b/rust/operator-binary/src/csi_server/mod.rs @@ -0,0 +1,25 @@ +use serde::Deserialize; + +pub mod controller; +pub mod identity; +pub mod node; + +#[derive(Deserialize)] +enum ListenerSelector { + #[serde(rename = "listeners.stackable.tech/listener-class")] + ListenerClass(String), + #[serde(rename = "listeners.stackable.tech/listener-name")] + Listener(String), +} + +#[derive(Deserialize)] +struct ListenerVolumeContext { + #[serde(flatten)] + listener_selector: ListenerSelector, +} + +fn tonic_unimplemented() -> Result { + Err(tonic::Status::unimplemented( + "this endpoint is not implemented", + )) +} diff --git a/rust/operator-binary/src/csi_server/node.rs b/rust/operator-binary/src/csi_server/node.rs new file mode 100644 index 00000000..b728d8c4 --- /dev/null +++ b/rust/operator-binary/src/csi_server/node.rs @@ -0,0 +1,373 @@ +use std::{fmt::Debug, path::PathBuf}; + +use csi_grpc::{self as csi, v1::Topology}; +use serde::{ + de::{DeserializeOwned, IntoDeserializer}, + Deserialize, +}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + builder::OwnerReferenceBuilder, + commons::listener::{Listener, ListenerIngress, ListenerPort, ListenerSpec}, + k8s_openapi::api::core::v1::{Node, PersistentVolume, Pod}, + kube::{ + core::{DynamicObject, ObjectMeta}, + runtime::reflector::ObjectRef, + Resource, + }, +}; +use tonic::{Request, Response, Status}; + +use crate::{ + listener_controller::listener_mounted_pod_label, + utils::{error_full_message, node_primary_address}, +}; + +use super::{tonic_unimplemented, ListenerSelector, ListenerVolumeContext}; + +const FIELD_MANAGER_SCOPE: &str = "volume"; + +pub struct ListenerOperatorNode { + pub client: stackable_operator::client::Client, + pub node_name: String, +} + +#[derive(Deserialize)] +struct ListenerNodeVolumeContext { + #[serde(rename = "csi.storage.k8s.io/pod.namespace")] + pod_namespace: String, + #[serde(rename = "csi.storage.k8s.io/pod.name")] + pod_name: String, + #[serde(flatten)] + common: ListenerVolumeContext, +} + +#[derive(Snafu, Debug)] +#[snafu(module)] +enum PublishVolumeError { + #[snafu(display("failed to decode volume context"))] + DecodeVolumeContext { source: serde::de::value::Error }, + #[snafu(display("failed to get {obj}"))] + GetObject { + source: stackable_operator::error::Error, + obj: ObjectRef, + }, + #[snafu(display("{pod} has not been scheduled to a node yet"))] + PodHasNoNode { pod: ObjectRef }, + #[snafu(display("failed to build Listener's owner reference"))] + BuildListenerOwnerRef { + source: stackable_operator::error::Error, + }, + #[snafu(display("failed to apply {listener}"))] + ApplyListener { + source: stackable_operator::error::Error, + listener: ObjectRef, + }, + #[snafu(display("failed to add listener label to {pod}"))] + AddListenerLabelToPod { + source: stackable_operator::error::Error, + pod: ObjectRef, + }, + #[snafu(display("failed to prepare pod dir at {target_path:?}"))] + PreparePodDir { + source: pod_dir::Error, + target_path: PathBuf, + }, +} + +impl From for Status { + fn from(err: PublishVolumeError) -> Self { + let full_msg = error_full_message(&err); + // Convert to an appropriate tonic::Status representation and include full error message + match err { + PublishVolumeError::DecodeVolumeContext { .. } => Status::invalid_argument(full_msg), + PublishVolumeError::GetObject { .. } => Status::unavailable(full_msg), + PublishVolumeError::PodHasNoNode { .. } => Status::unavailable(full_msg), + PublishVolumeError::BuildListenerOwnerRef { .. } => Status::unavailable(full_msg), + PublishVolumeError::ApplyListener { .. } => Status::unavailable(full_msg), + PublishVolumeError::AddListenerLabelToPod { .. } => Status::unavailable(full_msg), + PublishVolumeError::PreparePodDir { .. } => Status::internal(full_msg), + } + } +} + +#[derive(Snafu, Debug)] +#[snafu(module)] +enum UnpublishVolumeError { + #[snafu(display("failed to clean up volume data at {path:?}"))] + CleanupData { + source: std::io::Error, + path: PathBuf, + }, +} + +impl From for Status { + fn from(err: UnpublishVolumeError) -> Self { + let full_msg = error_full_message(&err); + // Convert to an appropriate tonic::Status representation and include full error message + match err { + UnpublishVolumeError::CleanupData { .. } => Status::internal(full_msg), + } + } +} + +#[tonic::async_trait] +impl csi::v1::node_server::Node for ListenerOperatorNode { + async fn node_get_info( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::NodeGetInfoResponse { + node_id: self.node_name.clone(), + max_volumes_per_node: i64::MAX, + accessible_topology: Some(Topology { + segments: [( + "listeners.stackable.tech/hostname".to_string(), + self.node_name.clone(), + )] + .into(), + }), + })) + } + + async fn node_get_capabilities( + &self, + _request: Request, + ) -> Result, Status> { + Ok(Response::new(csi::v1::NodeGetCapabilitiesResponse { + capabilities: Vec::new(), + })) + } + + async fn node_publish_volume( + &self, + request: Request, + ) -> Result, Status> { + use publish_volume_error::*; + async fn get_obj + DeserializeOwned + Clone + Debug>( + client: &stackable_operator::client::Client, + name: &str, + ns: Option<&str>, + ) -> Result { + client.get(name, ns).await.with_context(|_| GetObjectSnafu { + obj: { + let mut obj = ObjectRef::::new(name); + if let Some(ns) = ns { + obj = obj.within(ns); + } + obj.erase() + }, + }) + } + + let request = request.into_inner(); + let ListenerNodeVolumeContext { + pod_namespace: ns, + pod_name, + common: ListenerVolumeContext { listener_selector }, + } = ListenerNodeVolumeContext::deserialize(request.volume_context.into_deserializer()) + .context(DecodeVolumeContextSnafu)?; + let pv_name = &request.volume_id; + let pv = get_obj::(&self.client, pv_name, None).await?; + let pod = get_obj::(&self.client, &pod_name, Some(&ns)).await?; + + let listener = match listener_selector { + ListenerSelector::Listener(listener_name) => { + get_obj::(&self.client, &listener_name, Some(&ns)).await? + } + ListenerSelector::ListenerClass(listener_class_name) => { + let listener = Listener { + metadata: ObjectMeta { + namespace: Some(ns.clone()), + name: pv + .spec + .as_ref() + .and_then(|pv_spec| pv_spec.claim_ref.as_ref()?.name.clone()), + owner_references: Some(vec![OwnerReferenceBuilder::new() + .initialize_from_resource(&pv) + .build() + .context(BuildListenerOwnerRefSnafu)?]), + ..Default::default() + }, + spec: ListenerSpec { + class_name: Some(listener_class_name), + ports: Some( + pod.spec + .iter() + .flat_map(|ps| &ps.containers) + .flat_map(|ctr| &ctr.ports) + .flatten() + .map(|port| ListenerPort { + name: port + .name + .clone() + .unwrap_or_else(|| format!("port-{}", port.container_port)), + protocol: port.protocol.clone(), + port: port.container_port, + }) + .collect(), + ), + publish_not_ready_addresses: Some(true), + ..Default::default() + }, + status: None, + }; + self.client + .apply_patch(FIELD_MANAGER_SCOPE, &listener, &listener) + .await + .with_context(|_| ApplyListenerSnafu { + listener: ObjectRef::from_obj(&listener), + })? + } + }; + + // Add listener label to pod so that traffic can be directed to it + self.client + // IMPORTANT + // Use a merge patch rather than an apply so that we don't delete labels added by other listener volumes. + // Volumes aren't hot-swappable anyway, and all labels will be removed when the pod is deleted. + .merge_patch( + &pod, + &Pod { + metadata: ObjectMeta { + labels: Some([listener_mounted_pod_label(&listener)].into()), + ..Default::default() + }, + ..Default::default() + }, + ) + .await + .with_context(|_| AddListenerLabelToPodSnafu { + pod: ObjectRef::from_obj(&pod), + })?; + + // Prefer calculating a per-node address where possible, to ensure that the address at least tries to + // connect to the pod in question. + // We also can't rely on `ingress_addresses` being set yet, since the pod won't have an IP address yet + // (and so can't be found in `Endpoints`) + let listener_addrs = if let Some(node_ports) = listener + .status + .as_ref() + .and_then(|status| status.node_ports.clone()) + { + let node_name = pod + .spec + .as_ref() + .and_then(|s| s.node_name.as_deref()) + .with_context(|| PodHasNoNodeSnafu { + pod: ObjectRef::from_obj(&pod), + })?; + let node = get_obj::(&self.client, node_name, None).await?; + node_primary_address(&node) + .map(|address| ListenerIngress { + address: address.to_string(), + ports: node_ports, + }) + .into_iter() + .collect() + } else { + listener + .status + .as_ref() + .and_then(|s| s.ingress_addresses.as_ref()) + .cloned() + .unwrap_or_default() + }; + + let target_path = PathBuf::from(request.target_path); + pod_dir::write_listener_info_to_pod_dir(&target_path, &listener_addrs) + .await + .context(PreparePodDirSnafu { target_path })?; + + Ok(Response::new(csi::v1::NodePublishVolumeResponse {})) + } + + async fn node_unpublish_volume( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let path = PathBuf::from(request.target_path); + match tokio::fs::remove_dir_all(&path).await { + Ok(()) => {} + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + // already deleted => nothing to do + } + Err(err) => Err(err).context(unpublish_volume_error::CleanupDataSnafu { path })?, + } + Ok(Response::new(csi::v1::NodeUnpublishVolumeResponse {})) + } + + async fn node_stage_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn node_unstage_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn node_get_volume_stats( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } + + async fn node_expand_volume( + &self, + _request: Request, + ) -> Result, Status> { + tonic_unimplemented() + } +} + +mod pod_dir { + use std::path::Path; + + use snafu::{OptionExt, ResultExt, Snafu}; + use stackable_operator::commons::listener::ListenerIngress; + + #[derive(Snafu, Debug)] + pub enum Error { + #[snafu(display("failed to write content"), context(false))] + WriteContent { source: std::io::Error }, + #[snafu(display("listener has no address yet"))] + NoDefaultAddress, + #[snafu(display("default address folder is outside of the volume root"))] + DefaultAddrIsOutsideRoot { source: std::path::StripPrefixError }, + } + + pub async fn write_listener_info_to_pod_dir( + target_path: &Path, + listener_addrs: &[ListenerIngress], + ) -> Result<(), Error> { + let addrs_path = target_path.join("addresses"); + tokio::fs::create_dir_all(&addrs_path).await?; + let mut default_addr_dir = None; + for addr in listener_addrs { + let addr_dir = addrs_path.join(&addr.address); + let ports_dir = addr_dir.join("ports"); + tokio::fs::create_dir_all(&ports_dir).await?; + tokio::fs::write(addr_dir.join("address"), addr.address.as_bytes()).await?; + for (port_name, port) in &addr.ports { + tokio::fs::write(ports_dir.join(port_name), port.to_string().as_bytes()).await?; + } + default_addr_dir.get_or_insert(addr_dir); + } + tokio::fs::symlink( + default_addr_dir + .context(NoDefaultAddressSnafu)? + .strip_prefix(target_path) + .context(DefaultAddrIsOutsideRootSnafu)?, + target_path.join("default-address"), + ) + .await?; + Ok(()) + } +} diff --git a/rust/operator-binary/src/listener_controller.rs b/rust/operator-binary/src/listener_controller.rs new file mode 100644 index 00000000..62c7f5af --- /dev/null +++ b/rust/operator-binary/src/listener_controller.rs @@ -0,0 +1,300 @@ +use crate::utils::node_primary_address; +use futures::{future::try_join_all, StreamExt}; +use snafu::{OptionExt, ResultExt, Snafu}; +use stackable_operator::{ + builder::OwnerReferenceBuilder, + commons::listener::{ + Listener, ListenerClass, ListenerIngress, ListenerPort, ListenerSpec, ListenerStatus, + ServiceType, + }, + k8s_openapi::api::core::v1::{Endpoints, Node, Service, ServicePort, ServiceSpec}, + kube::{ + api::{DynamicObject, ListParams, ObjectMeta}, + runtime::{controller, reflector::ObjectRef}, + }, + logging::controller::{report_controller_reconciled, ReconcilerError}, +}; +use std::{collections::BTreeMap, sync::Arc, time::Duration}; +use strum::IntoStaticStr; + +#[cfg(doc)] +use stackable_operator::k8s_openapi::api::core::v1::Pod; + +const FIELD_MANAGER_SCOPE: &str = "listener"; + +pub async fn run(client: stackable_operator::client::Client) { + let controller = + controller::Controller::new(client.get_all_api::(), ListParams::default()); + let listener_store = controller.store(); + controller + .owns(client.get_all_api::(), ListParams::default()) + .watches( + client.get_all_api::(), + ListParams::default(), + move |endpoints| { + listener_store + .state() + .into_iter() + .filter(move |listener| { + listener + .status + .as_ref() + .and_then(|s| s.service_name.as_deref()) + == endpoints.metadata.name.as_deref() + }) + .map(|l| ObjectRef::from_obj(&*l)) + }, + ) + .shutdown_on_signal() + .run( + reconcile, + error_policy, + Arc::new(Ctx { + client: client.clone(), + }), + ) + .map(|res| { + report_controller_reconciled(&client, "listener.listeners.stackable.tech", &res); + }) + .collect::<()>() + .await; +} + +pub struct Ctx { + pub client: stackable_operator::client::Client, +} + +#[derive(Debug, Snafu, IntoStaticStr)] +pub enum Error { + #[snafu(display("object has no namespace"))] + NoNs, + #[snafu(display("object has no name"))] + NoName, + #[snafu(display("object has no ListenerClass (.spec.class_name)"))] + NoListenerClass, + #[snafu(display("failed to get {obj}"))] + GetObject { + source: stackable_operator::error::Error, + obj: ObjectRef, + }, + #[snafu(display("failed to build owner reference to Listener"))] + BuildListenerOwnerRef { + source: stackable_operator::error::Error, + }, + #[snafu(display("failed to apply {svc}"))] + ApplyService { + source: stackable_operator::error::Error, + svc: ObjectRef, + }, + #[snafu(display("failed to apply status for Listener"))] + ApplyStatus { + source: stackable_operator::error::Error, + }, +} +impl ReconcilerError for Error { + fn category(&self) -> &'static str { + self.into() + } + + fn secondary_object(&self) -> Option> { + match self { + Self::NoNs => None, + Self::NoName => None, + Self::NoListenerClass => None, + Self::GetObject { source: _, obj } => Some(obj.clone()), + Self::BuildListenerOwnerRef { .. } => None, + Self::ApplyService { source: _, svc } => Some(svc.clone().erase()), + Self::ApplyStatus { source: _ } => None, + } + } +} + +pub async fn reconcile( + listener: Arc, + ctx: Arc, +) -> Result { + let ns = listener.metadata.namespace.clone().context(NoNsSnafu)?; + let listener_class_name = listener + .spec + .class_name + .as_deref() + .context(NoListenerClassSnafu)?; + let listener_class = ctx + .client + .get::(listener_class_name, None) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(listener_class_name).erase(), + })?; + let pod_ports = listener + .spec + .ports + .iter() + .flatten() + .map( + |ListenerPort { + name, + port, + protocol, + }| { + ( + (protocol, name), + ServicePort { + name: Some(name.clone()), + protocol: protocol.clone(), + port: *port, + ..Default::default() + }, + ) + }, + ) + // Deduplicate ports by (protocol, name) + .collect::>(); + let svc_name = listener.metadata.name.clone().context(NoNameSnafu)?; + let mut pod_selector = listener.spec.extra_pod_selector_labels.clone(); + pod_selector.extend([listener_mounted_pod_label(&listener)]); + let svc = Service { + metadata: ObjectMeta { + namespace: Some(ns.clone()), + name: Some(svc_name.clone()), + owner_references: Some(vec![OwnerReferenceBuilder::new() + .initialize_from_resource(&*listener) + .build() + .context(BuildListenerOwnerRefSnafu)?]), + ..Default::default() + }, + spec: Some(ServiceSpec { + type_: Some(match listener_class.spec.service_type { + ServiceType::NodePort => "NodePort".to_string(), + ServiceType::LoadBalancer => "LoadBalancer".to_string(), + }), + ports: Some(pod_ports.into_values().collect()), + external_traffic_policy: Some("Local".to_string()), + selector: Some(pod_selector), + publish_not_ready_addresses: Some( + listener + .spec + .publish_not_ready_addresses + .unwrap_or_default(), + ), + ..Default::default() + }), + ..Default::default() + }; + let svc = ctx + .client + .apply_patch(FIELD_MANAGER_SCOPE, &svc, &svc) + .await + .with_context(|_| ApplyServiceSnafu { + svc: ObjectRef::from_obj(&svc), + })?; + + let addresses: Vec; + let ports: BTreeMap; + match listener_class.spec.service_type { + ServiceType::NodePort => { + let endpoints = ctx + .client + .get_opt::(&svc_name, Some(&ns)) + .await + .with_context(|_| GetObjectSnafu { + obj: ObjectRef::::new(&svc_name).within(&ns).erase(), + })? + // Endpoints object may not yet be created by its respective controller + .unwrap_or_default(); + let node_names = endpoints + .subsets + .into_iter() + .flatten() + .flat_map(|subset| subset.addresses) + .flatten() + .flat_map(|addr| addr.node_name) + .collect::>(); + let nodes = try_join_all(node_names.iter().map(|node_name| async { + ctx.client + .get::(node_name, None) + .await + .context(GetObjectSnafu { + obj: ObjectRef::::new(node_name).erase(), + }) + })) + .await?; + addresses = nodes + .into_iter() + .flat_map(|node| node_primary_address(&node).map(str::to_string)) + .collect(); + ports = svc + .spec + .as_ref() + .and_then(|s| s.ports.as_ref()) + .into_iter() + .flatten() + .filter_map(|port| Some((port.name.clone()?, port.node_port?))) + .collect(); + } + ServiceType::LoadBalancer => { + addresses = svc + .status + .iter() + .flat_map(|ss| ss.load_balancer.as_ref()?.ingress.as_ref()) + .flatten() + .flat_map(|ingress| ingress.hostname.clone().or_else(|| ingress.ip.clone())) + .collect(); + ports = svc + .spec + .as_ref() + .and_then(|s| s.ports.as_ref()) + .into_iter() + .flatten() + .filter_map(|port| Some((port.name.clone()?, port.port))) + .collect(); + } + }; + + let listener_status_meta = Listener { + metadata: ObjectMeta { + name: listener.metadata.name.clone(), + namespace: listener.metadata.namespace.clone(), + uid: listener.metadata.uid.clone(), + ..Default::default() + }, + spec: ListenerSpec::default(), + status: None, + }; + let listener_status = ListenerStatus { + service_name: svc.metadata.name, + ingress_addresses: Some( + addresses + .into_iter() + .map(|addr| ListenerIngress { + address: addr, + ports: ports.clone(), + }) + .collect(), + ), + node_ports: (listener_class.spec.service_type == ServiceType::NodePort).then_some(ports), + }; + ctx.client + .apply_patch_status(FIELD_MANAGER_SCOPE, &listener_status_meta, &listener_status) + .await + .context(ApplyStatusSnafu)?; + + Ok(controller::Action::await_change()) +} + +pub fn error_policy(_err: &Error, _ctx: Arc) -> controller::Action { + controller::Action::requeue(Duration::from_secs(5)) +} + +/// A label that identifies [`Pod`]s that have mounted `listener` +/// +/// Listener-Op's CSI Node driver is responsible for adding this to the relevant [`Pod`]s. +pub fn listener_mounted_pod_label(listener: &Listener) -> (String, String) { + ( + format!( + "listeners.stackable.tech/mounted-listener.{}", + listener.metadata.name.as_deref().unwrap_or_default() + ), + "true".to_string(), + ) +} diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs new file mode 100644 index 00000000..eaf1633f --- /dev/null +++ b/rust/operator-binary/src/main.rs @@ -0,0 +1,132 @@ +use std::{os::unix::prelude::FileTypeExt, path::PathBuf}; + +use clap::{crate_description, crate_version, Parser}; +use csi_grpc::v1::{ + controller_server::ControllerServer, identity_server::IdentityServer, node_server::NodeServer, +}; +use csi_server::{ + controller::ListenerOperatorController, identity::ListenerOperatorIdentity, + node::ListenerOperatorNode, +}; +use futures::{pin_mut, FutureExt, TryStreamExt}; +use stackable_operator::{ + commons::listener::{Listener, ListenerClass}, + logging::TracingTarget, + CustomResourceExt, +}; +use tokio::signal::unix::{signal, SignalKind}; +use tokio_stream::wrappers::UnixListenerStream; +use tonic::transport::Server; +use utils::{uds_bind_private, TonicUnixStream}; + +mod csi_server; +mod listener_controller; +mod utils; + +const OPERATOR_KEY: &str = "listeners.stackable.tech"; + +#[derive(clap::Parser)] +#[clap(author, version)] +struct Opts { + #[clap(subcommand)] + cmd: stackable_operator::cli::Command, +} + +#[derive(clap::Parser)] +struct ListenerOperatorRun { + #[clap(long, env, default_value_t, arg_enum)] + tracing_target: TracingTarget, + #[clap(long, env)] + csi_endpoint: PathBuf, + + #[clap(subcommand)] + mode: RunMode, +} + +#[derive(clap::Parser, strum::AsRefStr)] +enum RunMode { + Controller, + Node { + #[clap(long, env)] + node_name: String, + }, +} + +mod built_info { + include!(concat!(env!("OUT_DIR"), "/built.rs")); + pub const TARGET: Option<&str> = option_env!("TARGET"); +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let opts = Opts::parse(); + match opts.cmd { + stackable_operator::cli::Command::Crd => { + ListenerClass::print_yaml_schema()?; + Listener::print_yaml_schema()?; + } + stackable_operator::cli::Command::Run(ListenerOperatorRun { + tracing_target, + csi_endpoint, + mode, + }) => { + stackable_operator::logging::initialize_logging( + "LISTENER_OPERATOR_LOG", + "listener-operator", + tracing_target, + ); + stackable_operator::utils::print_startup_string( + &format!("{} ({})", crate_description!(), mode.as_ref()), + crate_version!(), + built_info::GIT_VERSION, + built_info::TARGET.unwrap_or("unknown target"), + built_info::BUILT_TIME_UTC, + built_info::RUSTC_VERSION, + ); + let client = + stackable_operator::client::create_client(Some(OPERATOR_KEY.to_string())).await?; + if csi_endpoint + .symlink_metadata() + .map_or(false, |meta| meta.file_type().is_socket()) + { + let _ = std::fs::remove_file(&csi_endpoint); + } + let mut sigterm = signal(SignalKind::terminate())?; + let csi_listener = + UnixListenerStream::new(uds_bind_private(csi_endpoint)?).map_ok(TonicUnixStream); + let csi_server = Server::builder() + .add_service( + tonic_reflection::server::Builder::configure() + .include_reflection_service(true) + .register_encoded_file_descriptor_set(csi_grpc::FILE_DESCRIPTOR_SET_BYTES) + .build()?, + ) + .add_service(IdentityServer::new(ListenerOperatorIdentity)); + + match mode { + RunMode::Controller => { + let csi_server = csi_server + .add_service(ControllerServer::new(ListenerOperatorController { + client: client.clone(), + })) + .serve_with_incoming_shutdown(csi_listener, sigterm.recv().map(|_| ())); + let controller = listener_controller::run(client).map(Ok); + pin_mut!(csi_server, controller); + futures::future::try_select(csi_server, controller) + .await + .map_err(|err| err.factor_first().0)?; + } + RunMode::Node { node_name } => { + csi_server + .add_service(NodeServer::new(ListenerOperatorNode { + client: client.clone(), + node_name, + })) + .serve_with_incoming_shutdown(csi_listener, sigterm.recv().map(|_| ())) + .await?; + } + } + } + } + Ok(()) +} diff --git a/rust/operator-binary/src/utils.rs b/rust/operator-binary/src/utils.rs new file mode 100644 index 00000000..81b4a377 --- /dev/null +++ b/rust/operator-binary/src/utils.rs @@ -0,0 +1,140 @@ +use std::{os::unix::prelude::AsRawFd, path::Path}; + +use pin_project::pin_project; +use socket2::Socket; +use stackable_operator::k8s_openapi::api::core::v1::Node; +use tokio::{ + io::{AsyncRead, AsyncWrite}, + net::{UnixListener, UnixStream}, +}; +use tonic::transport::server::Connected; + +/// Adapter for using [`UnixStream`] as a [`tonic`] connection +/// Tonic usually communicates via TCP sockets, but the Kubernetes CSI interface expects +/// plugins to use Unix sockets instead. +/// This provides a wrapper implementation which delegates to tokio's [`UnixStream`] in order +/// to enable tonic to communicate via Unix sockets. +#[pin_project] +pub struct TonicUnixStream(#[pin] pub UnixStream); + +impl AsyncRead for TonicUnixStream { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + self.project().0.poll_read(cx, buf) + } +} + +impl AsyncWrite for TonicUnixStream { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + self.project().0.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().0.poll_flush(cx) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + self.project().0.poll_shutdown(cx) + } + + fn poll_write_vectored( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> std::task::Poll> { + self.project().0.poll_write_vectored(cx, bufs) + } + + fn is_write_vectored(&self) -> bool { + self.0.is_write_vectored() + } +} + +impl Connected for TonicUnixStream { + type ConnectInfo = (); + + fn connect_info(&self) -> Self::ConnectInfo {} +} + +/// Bind a Unix Domain Socket listener that is only accessible to the current user +pub fn uds_bind_private(path: impl AsRef) -> Result { + // Workaround for https://github.com/tokio-rs/tokio/issues/4422 + let socket = Socket::new(socket2::Domain::UNIX, socket2::Type::STREAM, None)?; + unsafe { + // Socket-level chmod is propagated to the file created by Socket::bind. + // We need to chmod /before/ creating the file, because otherwise there is a brief window where + // the file is world-accessible (unless restricted by the global umask). + if libc::fchmod(socket.as_raw_fd(), 0o600) == -1 { + return Err(std::io::Error::last_os_error()); + } + } + socket.bind(&socket2::SockAddr::unix(path)?)?; + socket.listen(1024)?; + socket.set_nonblocking(true)?; + UnixListener::from_std(socket.into()) +} + +/// Combines the messages of an error and its sources into a [`String`] of the form `"error: source 1: source 2: root error"` +pub fn error_full_message(err: &dyn std::error::Error) -> String { + use std::fmt::Write; + // Build the full hierarchy of error messages by walking up the stack until an error + // without `source` set is encountered and concatenating all encountered error strings. + let mut full_msg = format!("{}", err); + let mut curr_err = err.source(); + while let Some(curr_source) = curr_err { + write!(full_msg, ": {curr_source}").expect("string formatting should be infallible"); + curr_err = curr_source.source(); + } + full_msg +} + +/// Try to guess the primary address of a Node, which it is expected that external clients should be able to reach it on +pub fn node_primary_address(node: &Node) -> Option<&str> { + let addrs = node + .status + .as_ref() + .and_then(|s| s.addresses.as_deref()) + .unwrap_or_default(); + // IP addresses are currently preferred over hostnames since nodes don't always have resolvable hostnames + addrs + .iter() + .find(|addr| addr.type_ == "ExternalIP") + .or_else(|| addrs.iter().find(|addr| addr.type_ == "InternalIP")) + .or_else(|| addrs.iter().find(|addr| addr.type_ == "Hostname")) + .map(|addr| addr.address.as_str()) +} + +#[cfg(test)] +mod tests { + use crate::utils::error_full_message; + + #[test] + fn error_messages() { + assert_eq!( + error_full_message(anyhow::anyhow!("standalone error").as_ref()), + "standalone error" + ); + assert_eq!( + error_full_message( + anyhow::anyhow!("root error") + .context("middleware") + .context("leaf") + .as_ref() + ), + "leaf: middleware: root error" + ); + } +} diff --git a/scripts/docs_templating.sh b/scripts/docs_templating.sh new file mode 100755 index 00000000..ed5b9b01 --- /dev/null +++ b/scripts/docs_templating.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Reads a file with variables to insert into templates, and templates all .*.j2 files +# in the 'docs' directory. +# +# dependencies +# pip install jinja2-cli + +docs_dir="$(dirname "$0")/../docs" +templating_vars_file="$docs_dir/templating_vars.yaml" + +# Check if files need templating +if [[ -z $(find "$docs_dir" -name '*.j2') ]]; +then + echo "No files need templating, exiting." + exit +fi + +# Check if jinja2 is there +if ! command -v jinja2 &> /dev/null +then + echo "jinja2 could not be found. Use 'pip install jinja2-cli' to install it." + exit +fi + +# Check if templating vars file exists +if [[ ! -f "$templating_vars_file" ]]; +then + echo "$templating_vars_file does not exist, cannot start templating." +fi + +find "$docs_dir" -name '*.j2' | +while read -r file +do + new_file_name=${file%.j2} # Remove .j2 suffix + echo "templating $new_file_name" + jinja2 "$file" "$templating_vars_file" -o "$new_file_name" +done + +echo "done" diff --git a/scripts/generate-manifests.sh b/scripts/generate-manifests.sh new file mode 100755 index 00000000..8edcae8e --- /dev/null +++ b/scripts/generate-manifests.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# This script reads a Helm chart from deploy/helm/listener-operator and +# generates manifest files into deploy/manifestss +set -e + +tmp=$(mktemp -d ./manifests-XXXXX) + +helm template --output-dir "$tmp" \ + --include-crds \ + --name-template listener-operator \ + deploy/helm/listener-operator + +for file in "$tmp"/listener-operator/*/*; do + yq eval -i 'del(.. | select(has("app.kubernetes.io/managed-by")) | ."app.kubernetes.io/managed-by")' /dev/stdin < "$file" + yq eval -i 'del(.. | select(has("helm.sh/chart")) | ."helm.sh/chart")' /dev/stdin < "$file" + sed -i '/# Source: .*/d' "$file" +done + +cp -r "$tmp"/listener-operator/*/* deploy/manifests/ + +rm -rf "$tmp" diff --git a/scripts/run_tests.sh b/scripts/run_tests.sh new file mode 100755 index 00000000..8fccb47f --- /dev/null +++ b/scripts/run_tests.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -e + +# Register absolute paths to pass to Ansible so the location of the role is irrelevant +# for the run +TESTDIR="$(pwd)/tests" +WORKDIR="$(pwd)/tests/_work" + +# Create dirs +mkdir -p tests/ansible/roles +mkdir -p "$WORKDIR" + +# Install Ansible role if needed +pushd tests/ansible +ansible-galaxy role install -r requirements.yaml -p ./roles + +# TODO: create pipenv in files for script thingy + +# Funnel via JSON to ensure that values are escaped properly +echo '{}' | jq '{work_dir: $WORKDIR, test_dir: $TESTDIR}' --arg WORKDIR "$WORKDIR" --arg TESTDIR "$TESTDIR" > "${WORKDIR}"/vars.json + +# Run playbook to generate test scenarios +ansible-playbook playbook.yaml --extra-vars "@${WORKDIR}/vars.json" +popd + +# Run tests +pushd tests/_work +kubectl kuttl test "$@" +popd diff --git a/tests/README-templating.md b/tests/README-templating.md new file mode 100644 index 00000000..97ca94e4 --- /dev/null +++ b/tests/README-templating.md @@ -0,0 +1,83 @@ +# Test Scenario Templating + +## Introduction + +The tests in this directory are designed to be expanded into multiple test scenarios based on test dimensions that can be defined in a dimensions file. + +## Defining Test Dimensions + +The dimensions file currently has to be named `test-definition.yaml` and reside in the same directory as the `kuttl-test.yaml.jinja2` file. + +An example of a minimal folder structure will be given further down in this file. + +An example of the content for the test definition file is shown here: + +````yaml +dimensions: + - name: spark + values: + - 3.2.1 + - 3.2.2 + - 3.2.3 + - name: hadoop + values: + - 3.1.0 + - 3.2.0 + - name: aws + - abc + - xyz +tests: + - name: spark-pi-public-s3 + dimensions: + - spark + - hadoop +```` + +This file defines three dimensions for this test to be considered. +It also defines one test case named _spark-pi-public-s3_ and the dimensions that this test case should use. +In this example the test case uses only two of the three dimensions defined, so a run of this test case would be expanded into the following test structure: + +````text +└── spark-pi-public-s3 + ├── spark-3.2.1_hadoop-3.1.0 + ├── spark-3.2.1_hadoop-3.2.0 + ├── spark-3.2.2_hadoop-3.1.0 + ├── spark-3.2.2_hadoop-3.2.0 + ├── spark-3.2.3_hadoop-3.1.0 + └── spark-3.2.3_hadoop-3.2.0 +```` + +The name of a test case defined under `tests` in this file has to refer back to a directory in the `templates/kuttl` directory, which will be used to create the test scenarios. + +Given the example of a test-definition.yaml shown above, the following folder structure would create the test scenarios shown above. + +````text +tests +├── kuttl-test.yaml.j2 +├── templates +│ └── kuttl +│ └── spark-pi-public-s3 +└── test-definition.yaml +```` + +The `kuttl-test.yaml.jinja2` cannot currently be edited, as it comes from the operator templating and any changes would be overwritten again. +This should be fairly easy to solve and we can look at this as soon as it becomes necessary. + +## Using + +### Requirements + +To run tests locally you need the following things installed: + +- python3 (version >= 3.9) + - pyyaml library installed +- ansible (tested with `2.10.8` and `2.12.5`) +- jq + +### Running + +To run tests please execute the following command from the gitroot of the operator repository: + +`scripts/run_tests.sh --parallel 2` + +This will install the necessary ansible role into `tests/ansible/roles`, expand the test templates into all defined test scenarios and execute kuttl to test these scenarios. Any arguments are passed on to `kuttl`. diff --git a/tests/ansible/playbook.yaml b/tests/ansible/playbook.yaml new file mode 100644 index 00000000..f580831f --- /dev/null +++ b/tests/ansible/playbook.yaml @@ -0,0 +1,6 @@ +--- +- name: Expand test templates into test scenarios by applying input dimensions + hosts: localhost + connection: local + roles: + - expand-tests diff --git a/tests/ansible/requirements.yaml b/tests/ansible/requirements.yaml new file mode 100644 index 00000000..d7aef5c1 --- /dev/null +++ b/tests/ansible/requirements.yaml @@ -0,0 +1,4 @@ +--- +- name: expand-tests + src: https://github.com/stackabletech/expand-tests.git + scm: git diff --git a/tests/kuttl-test.yaml.jinja2 b/tests/kuttl-test.yaml.jinja2 new file mode 100644 index 00000000..76b502aa --- /dev/null +++ b/tests/kuttl-test.yaml.jinja2 @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestSuite +testDirs: + {% for testcase in testinput.tests %} + - ./tests/{{ testcase.name }} + {% endfor %} + +startKIND: false +suppress: ["events"] +parallel: 2 diff --git a/tests/templates/.gitkeep b/tests/templates/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tests/templates/kuttl/smoke-nodeport/00-create-listenerclass.yaml b/tests/templates/kuttl/smoke-nodeport/00-create-listenerclass.yaml new file mode 100644 index 00000000..d501f211 --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/00-create-listenerclass.yaml @@ -0,0 +1,7 @@ +--- +apiVersion: listeners.stackable.tech/v1alpha1 +kind: ListenerClass +metadata: + name: listener-operator-test-smoke-nodeport +spec: + serviceType: NodePort diff --git a/tests/templates/kuttl/smoke-nodeport/01-assert.yaml b/tests/templates/kuttl/smoke-nodeport/01-assert.yaml new file mode 100644 index 00000000..f90121fe --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/01-assert.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: nginx +status: + readyReplicas: 2 + replicas: 2 diff --git a/tests/templates/kuttl/smoke-nodeport/01-create-nginx-statefulset.yaml b/tests/templates/kuttl/smoke-nodeport/01-create-nginx-statefulset.yaml new file mode 100644 index 00000000..a346fcc2 --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/01-create-nginx-statefulset.yaml @@ -0,0 +1,46 @@ +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: nginx +spec: + serviceName: nginx + selector: + matchLabels: + app: nginx + replicas: 2 + template: + metadata: + labels: + app: nginx + spec: + containers: + - name: nginx + image: nginx:1.23.1 + ports: + - name: http + containerPort: 80 + volumeMounts: + - name: listener + mountPath: /listener + - name: metadata + mountPath: /usr/share/nginx/html/ + volumes: + - name: metadata + downwardAPI: + items: + - path: pod-name + fieldRef: + fieldPath: metadata.name + volumeClaimTemplates: + - metadata: + name: listener + annotations: + listeners.stackable.tech/listener-class: listener-operator-test-smoke-nodeport + spec: + accessModes: + - ReadWriteMany + storageClassName: listeners.stackable.tech + resources: + requests: + storage: 1 diff --git a/tests/templates/kuttl/smoke-nodeport/02-assert.yaml b/tests/templates/kuttl/smoke-nodeport/02-assert.yaml new file mode 100644 index 00000000..2887667c --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/02-assert.yaml @@ -0,0 +1,6 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +commands: + - script: curl $(kubectl -n $NAMESPACE get listener/listener-nginx-0 -o jsonpath='http://{.status.ingressAddresses[0].address}:{.status.ingressAddresses[0].ports.http}/pod-name') | grep nginx-0 + - script: curl $(kubectl -n $NAMESPACE get listener/listener-nginx-1 -o jsonpath='http://{.status.ingressAddresses[0].address}:{.status.ingressAddresses[0].ports.http}/pod-name') | grep nginx-1 diff --git a/tests/templates/kuttl/smoke-nodeport/02-validate-all-ingresses-are-reachable.yaml b/tests/templates/kuttl/smoke-nodeport/02-validate-all-ingresses-are-reachable.yaml new file mode 100644 index 00000000..3fde425f --- /dev/null +++ b/tests/templates/kuttl/smoke-nodeport/02-validate-all-ingresses-are-reachable.yaml @@ -0,0 +1 @@ +# Empty, see 02-assert.yaml diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml new file mode 100644 index 00000000..b7671b2c --- /dev/null +++ b/tests/test-definition.yaml @@ -0,0 +1,5 @@ +--- +dimensions: [] +tests: + - name: smoke-nodeport + dimensions: []