diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c71356a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,290 @@ +name: Build and Push Images + +on: + pull_request: + branches: [master] + push: + branches: [master] + +env: + REGISTRY: ghcr.io + DOCKER_BUILDKIT: 1 + BUILDKIT_PROGRESS: plain + +jobs: + # ============================================================================ + # Pre-checks: Validate commits and version + # ============================================================================ + pre-checks: + runs-on: ubuntu-latest + outputs: + skip_build: ${{ steps.check-commits.outputs.skip_build }} + version: ${{ steps.version.outputs.version }} + image_tag: ${{ steps.image-tag.outputs.tag }} + latest_tag: ${{ steps.image-tag.outputs.latest_tag }} + is_release: ${{ steps.check-event.outputs.is_release }} + image_prefix: ${{ steps.lowercase.outputs.image_prefix }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Convert repository owner to lowercase + id: lowercase + run: | + echo "image_prefix=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}" >> $GITHUB_OUTPUT + + - name: Check event type + id: check-event + run: | + if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/master" ]]; then + echo "is_release=true" >> $GITHUB_OUTPUT + else + echo "is_release=false" >> $GITHUB_OUTPUT + fi + + - name: Validate commit messages (commitlint style) + id: commitlint + run: | + # Get commits to check + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + COMMITS=$(git log --format="%s" origin/master..HEAD) + else + # On push, check the pushed commit + COMMITS=$(git log --format="%s" -1) + fi + + # Conventional commit regex + PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?!?: .+" + + echo "Checking commits..." + FAILED=false + while IFS= read -r msg; do + if [[ -z "$msg" ]]; then + continue + fi + # Skip merge commits + if [[ "$msg" =~ ^Merge ]]; then + echo "Skipping merge commit: $msg" + continue + fi + if [[ ! "$msg" =~ $PATTERN ]]; then + echo "Invalid commit message: $msg" + echo "Expected format: type(scope)?: description" + echo "Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert" + FAILED=true + else + echo "Valid: $msg" + fi + done <<< "$COMMITS" + + if [[ "$FAILED" == "true" ]]; then + echo "Commit message validation failed" + exit 1 + fi + + - name: Check if docs-only commits + id: check-commits + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + COMMITS=$(git log --format="%s" origin/master..HEAD) + else + COMMITS=$(git log --format="%s" -1) + fi + + # Check if ALL commits are docs-prefixed + ALL_DOCS=true + while IFS= read -r msg; do + if [[ -z "$msg" ]]; then + continue + fi + if [[ ! "$msg" =~ ^docs ]]; then + ALL_DOCS=false + break + fi + done <<< "$COMMITS" + + if [[ "$ALL_DOCS" == "true" ]]; then + echo "All commits are docs-only, skipping build" + echo "skip_build=true" >> $GITHUB_OUTPUT + else + echo "skip_build=false" >> $GITHUB_OUTPUT + fi + + - name: Get version from Cargo.toml + id: version + run: | + VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Version: $VERSION" + + - name: Generate image tag + id: image-tag + run: | + VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + IS_RELEASE="${{ steps.check-event.outputs.is_release }}" + + if [[ "$IS_RELEASE" == "true" ]]; then + # Push to master: use version tag and latest + echo "tag=${VERSION}" >> $GITHUB_OUTPUT + echo "latest_tag=latest" >> $GITHUB_OUTPUT + else + # PR: use version with timestamp suffix and latest-dev + TIMESTAMP=$(date +"%Y%m%d%H%M%S") + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + echo "tag=${VERSION}-${TIMESTAMP}-${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "latest_tag=latest-dev" >> $GITHUB_OUTPUT + fi + + - name: Check version differs from master (PR only) + if: github.event_name == 'pull_request' + run: | + PR_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + + git fetch origin master + git checkout origin/master -- Cargo.toml + MASTER_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + git checkout HEAD -- Cargo.toml + + echo "PR version: $PR_VERSION" + echo "Master version: $MASTER_VERSION" + + if [[ "$PR_VERSION" == "$MASTER_VERSION" ]]; then + echo "Version must be bumped from master version ($MASTER_VERSION)" + exit 1 + fi + + - name: Check breaking change requires major bump (PR only) + if: github.event_name == 'pull_request' + run: | + COMMITS=$(git log --format="%s" origin/master..HEAD) + + HAS_BREAKING=false + while IFS= read -r msg; do + # Check for breaking change indicator (! before :) or BREAKING CHANGE in body + if [[ "$msg" =~ !: ]] || [[ "$msg" =~ BREAKING\ CHANGE ]]; then + HAS_BREAKING=true + break + fi + done <<< "$COMMITS" + + if [[ "$HAS_BREAKING" == "true" ]]; then + echo "Breaking change detected in commits" + + PR_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + + git fetch origin master + git checkout origin/master -- Cargo.toml + MASTER_VERSION=$(grep -E '^version' Cargo.toml | head -1 | sed -E 's/version = "(.*)"/\1/') + git checkout HEAD -- Cargo.toml + + # Extract major versions + PR_MAJOR=$(echo "$PR_VERSION" | cut -d. -f1) + MASTER_MAJOR=$(echo "$MASTER_VERSION" | cut -d. -f1) + + echo "PR major: $PR_MAJOR, Master major: $MASTER_MAJOR" + + if [[ "$PR_MAJOR" -le "$MASTER_MAJOR" ]]; then + echo "Breaking change requires major version bump (current: $MASTER_VERSION, PR: $PR_VERSION)" + exit 1 + fi + fi + + # ============================================================================ + # Build images + # ============================================================================ + build: + needs: pre-checks + if: needs.pre-checks.outputs.skip_build != 'true' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + strategy: + matrix: + service: [agent, hive, hive-hq] + include: + - service: agent + image_name: hive-agent + dockerfile: agent/Dockerfile + - service: hive + image_name: hive-server + dockerfile: hive/Dockerfile + - service: hive-hq + image_name: hive-hq + dockerfile: hive-hq/Dockerfile + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image tags + id: tags + run: | + IMAGE_TAG="${{ needs.pre-checks.outputs.image_tag }}" + LATEST_TAG="${{ needs.pre-checks.outputs.latest_tag }}" + IMAGE="${{ needs.pre-checks.outputs.image_prefix }}/${{ matrix.image_name }}" + + echo "tags=${IMAGE}:${IMAGE_TAG},${IMAGE}:${LATEST_TAG}" >> $GITHUB_OUTPUT + echo "version=${IMAGE_TAG}" >> $GITHUB_OUTPUT + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + file: ${{ matrix.dockerfile }} + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.tags.outputs.tags }} + build-args: | + BUILD_VERSION=${{ steps.tags.outputs.version }} + IMAGE_SOURCE=https://github.com/${{ github.repository }} + labels: | + org.opencontainers.image.source=https://github.com/${{ github.repository }} + org.opencontainers.image.version=${{ steps.tags.outputs.version }} + org.opencontainers.image.revision=${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # ============================================================================ + # Create git tag on release + # ============================================================================ + tag-release: + needs: [pre-checks, build] + if: needs.pre-checks.outputs.is_release == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + + - name: Create and push tag + run: | + VERSION="v${{ needs.pre-checks.outputs.version }}" + + # Check if tag already exists + if git rev-parse "$VERSION" >/dev/null 2>&1; then + echo "Tag $VERSION already exists, skipping" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag -a "$VERSION" -m "Release $VERSION" + git push origin "$VERSION" + echo "Created tag $VERSION" diff --git a/.gitignore b/.gitignore index 7510fd0..cb681be 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html -Cargo.lock +# Cargo.lock - committed for reproducible builds # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1985018 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5503 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "agent" +version = "0.0.2" +dependencies = [ + "base64 0.22.1", + "beecdiff", + "chrono", + "flate2", + "futures", + "k8s-openapi", + "kube", + "prost", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", + "tokio", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[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-to-html" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12e283a4fc285735ef99577e81a125f738429516161ac33977e466d0d8d40764" +dependencies = [ + "regex", + "thiserror 1.0.69", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "api" +version = "0.0.2" +dependencies = [ + "aes-gcm", + "ansi-to-html", + "axum 0.8.8", + "base64 0.22.1", + "bcrypt", + "beecdstorage", + "chrono", + "futures", + "getrandom 0.2.17", + "hex", + "hkdf", + "hmac", + "jsonwebtoken", + "quick-xml", + "rand 0.9.2", + "regex", + "reqwest", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "sqlx", + "tinytemplate", + "tokio", + "tower 0.5.3", + "tower-http", + "tracing", + "tracing-subscriber", + "types", + "utoipa", + "utoipa-swagger-ui", + "uuid", +] + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +dependencies = [ + "compression-codecs", + "compression-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-config" +version = "1.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96571e6996817bf3d58f6b569e4b9fd2e9d2fcf9f7424eed07b2ce9bb87535e5" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd362783681b15d136480ad555a099e82ecd8e2d10a841e14dfd0078d67fee3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959dab27ce613e6c9658eb3621064d0e2027e5f2acb65bc526a43577facea557" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.119.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d65fddc3844f902dfe1864acb8494db5f9342015ee3ab7890270d36fbd2e01c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "lru", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.92.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7d63bd2bdeeb49aa3f9b00c15e18583503b778b2e792fc06284d54e7d5b6566" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.94.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532d93574bf731f311bafb761366f9ece345a0416dbcc273d81d6d1a1205239b" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357e9a029c7524db6a0099cd77fbd5da165540339e7296cca603531bc783b56c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e523e1c4e8e7e8ff219d732988e22bfeae8a1cafdbe6d9eca1546fa080be7c" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.4.0", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee19095c7c4dda59f1697d028ce704c24b2d33c6718790c7f1d5a3015b4107c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.63.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87294a084b43d649d967efe58aa1f9e0adc260e13a6938eb904c0ae9b45824ae" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc12f8b310e38cad85cf3bef45ad236f470717393c613266ce0a89512286b650" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826141069295752372f8203c17f28e30c464d22899a43a0c9fd9c458d469c88b" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e62db736db19c488966c8d787f52e6270be565727236fd5579eaa301e7bc4a" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.8.1", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.36", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fa1213db31ac95288d981476f78d05d9cbb0353d22cdf3472cc05bb02f6551" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1fcbefc7ece1d70dcce29e490f269695dfca2d2bacdeaf9e5c3f799e4e6a42" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae5d689cf437eae90460e944a58b5668530d433b4ff85789e69d2f2a556e057d" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5b6167fcdf47399024e81ac08e795180c576a20e4d4ce67949f9a88ae37dc1" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efce7aaaf59ad53c5412f14fc19b2d5c6ab2c3ec688d272fd31f76ec12f44fb0" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65f172bcb02424eb94425db8aed1b6d583b5104d4d5ddddf22402c661a320048" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11b2f670422ff42bf7065031e72b45bc52a3508bd089f743ea90731ca2b6ea57" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d980627d2dd7bfc32a3c025685a033eeab8d365cc840c631ef59d1b8f428164" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core 0.3.4", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "itoa", + "matchit 0.7.3", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" +dependencies = [ + "axum-core 0.5.6", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "itoa", + "matchit 0.8.4", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backoff" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +dependencies = [ + "getrandom 0.2.17", + "instant", + "rand 0.8.5", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bcrypt" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abaf6da45c74385272ddf00e1ac074c7d8a6c1a1dda376902bd6a427522a8b2c" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom 0.3.4", + "subtle", + "zeroize", +] + +[[package]] +name = "beecdiff" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "serde_yaml", + "thiserror 2.0.18", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "beecdstorage" +version = "0.1.0" +dependencies = [ + "async-trait", + "aws-config", + "aws-sdk-s3", + "flate2", + "temp-env", + "tempfile", + "thiserror 2.0.18", + "tokio", + "url", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cc" +version = "1.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "compression-codecs" +version = "0.4.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00828ba6fd27b45a448e57dbfe84f1029d4c9f26b368157e9a448a5f49a2ec2a" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc-fast" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ddc2d09feefeee8bd78101665bd8645637828fa9317f9f292496dbbd8c65ff3" +dependencies = [ + "crc", + "digest", + "rand 0.9.2", + "regex", + "rustversion", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve", + "rfc6979", + "signature 1.6.4", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff", + "generic-array", + "group", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "indexmap 2.13.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core", + "http 1.4.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.4.0", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hive" +version = "0.0.2" +dependencies = [ + "aes-gcm", + "base64 0.22.1", + "bcrypt", + "beecdstorage", + "bytes", + "chrono", + "futures", + "getrandom 0.2.17", + "glob", + "hex", + "hkdf", + "http 0.2.12", + "http-body 0.4.6", + "hyper 1.8.1", + "hyper-tls", + "hyper-tungstenite", + "hyper-util", + "jsonwebtoken", + "log", + "pin-project", + "prost", + "reqwest", + "serde", + "serde_json", + "serde_yaml", + "sha2", + "sqlx", + "tempfile", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tonic-reflection", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", + "tracing-subscriber", + "tungstenite", + "uuid", + "yaml-rust", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.4.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-http-proxy" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ad4b0a1e37510028bc4ba81d0e38d239c39671b0f0ce9e02dfa93a8133f7c08" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.4.0", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http 1.4.0", + "hyper 1.8.1", + "hyper-util", + "log", + "rustls 0.23.36", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.32", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-tungstenite" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" +dependencies = [ + "http-body-util", + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", + "tokio-tungstenite", + "tungstenite", +] + +[[package]] +name = "hyper-util" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "hyper 1.8.1", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.2", + "system-configuration", + "tokio", + "tower-layer", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonpath-rust" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d8fe85bd70ff715f31ce8c739194b423d79811a19602115d611a3ec85d6200" +dependencies = [ + "lazy_static", + "once_cell", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "k8s-openapi" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8847402328d8301354c94d605481f25a6bdc1ed65471fd96af8eca71141b13" +dependencies = [ + "base64 0.22.1", + "chrono", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efffeb3df0bd4ef3e5d65044573499c0e4889b988070b08c50b25b1329289a1f" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf471ece8ff8d24735ce78dac4d091e9fcb8d74811aeb6b75de4d1c3f5de0f1" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-http-proxy", + "hyper-rustls 0.27.7", + "hyper-timeout 0.5.2", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rustls 0.23.36", + "rustls-pemfile", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42346d30bb34d1d7adc5c549b691bce7aa3a1e60254e68fab7e2d7b26fe3d77" +dependencies = [ + "chrono", + "form_urlencoded", + "http 1.4.0", + "json-patch", + "k8s-openapi", + "schemars", + "serde", + "serde-value", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "kube-derive" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9364e04cc5e0482136c6ee8b7fb7551812da25802249f35b3def7aaa31e82ad" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn", +] + +[[package]] +name = "kube-runtime" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fbf1f6ffa98e65f1d2a9a69338bb60605d46be7edf00237784b89e62c9bd44" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "async-trait", + "backoff", + "educe", + "futures", + "hashbrown 0.14.5", + "json-patch", + "jsonptr", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.7.0", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "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 = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe 0.1.6", + "openssl-sys", + "schannel", + "security-framework 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.13.0", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-rustls 0.27.7", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "tokio", + "tokio-native-tls", + "tower 0.5.3", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.103.9", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe 0.1.6", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe 0.2.1", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "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", + "serde", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.10", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap 2.13.0", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "temp-env" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96374855068f47402c3121c6eed88d29cb1de8f3ab27090e273e420bdabcf050" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd86198d9ee903fedd2f9a2e72014287c0d9167e4ae43b5853007205dda1b76" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.36", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-timeout 0.4.1", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn", +] + +[[package]] +name = "tonic-reflection" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548c227bd5c0fae5925812c4ec6c66ffcfced23ea370cb823f4d18f0fc1cb6a7" +dependencies = [ + "prost", + "prost-types", + "tokio", + "tokio-stream", + "tonic", +] + +[[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 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bitflags 2.10.0", + "bytes", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "types" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", + "serde_json", + "sqlx", + "utoipa", + "uuid", +] + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utoipa" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum 0.8.8", + "base64 0.22.1", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + +[[package]] +name = "uuid" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[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 = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71ddd76bcebeed25db614f82bf31a9f4222d3fbba300e6fb6c00afa26cbd4d9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8187381b52e32220d50b255276aa16a084ec0a9017a0ca2152a1f55c539758d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap 2.13.0", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02aae0f83f69aafc94776e879363e9771d7ecbffe2c7fbb6c14c5e00dfe88439" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 91e8410..8bc9e71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ resolver = "2" members = ["agent", "hive", "hive-hq/api", "hive-hq/types", "diff", "storage"] [workspace.package] -version = "0.0.1" +version = "0.0.2" edition = "2021" [workspace.dependencies] @@ -99,6 +99,12 @@ quick-xml = "0.37.1" htmlescape = "0.3.1" snailquote = "0.3.1" +# Cryptography +aes-gcm = "0.10" +hkdf = "0.12" +sha2 = "0.10" +getrandom = "0.2" + # Build dependencies tonic-build = "0.11.0" diff --git a/README.md b/README.md index 619044c..72624cb 100644 --- a/README.md +++ b/README.md @@ -1,210 +1,123 @@ # BeeCD -GitOps deployment with approval gates for Kubernetes. Review and approve what deploys before it goes live. - -## What It Does - -- **Approval gates** before any release reaches production -- **Manifest diffs** so you see exactly what will change -- **Multi-cluster control** from a single dashboard -- **Git-driven workflows** (push manifests, approve, done) -- **Release history** so you know what changed and who approved it - -## What You Need - -- Kubernetes 1.27+ -- Helm 3.0+ -- `kubectl` configured for your cluster -- A GitHub Personal Access Token with `repo` scope -- BeeCD images available in a registry your cluster can pull from +Hive mind for your deploys. Because YOLO deploys aren't cute in production. \ +Before anything goes live, you see a diff of what's changing. ## Getting Started -### Install - -#### 1. Create secrets - -```bash -kubectl create namespace beecd - -kubectl create secret generic hive-jwt \ - --namespace beecd \ - --from-literal=JWT_SECRET_KEY="$(openssl rand -base64 48)" - -kubectl create secret generic github-tokens \ - --namespace beecd \ - --from-literal=GHUSER="your-github-username" \ - --from-literal=GHPASS="ghp_your_token_here" - -./hack/generate-sops-gpg-secret.sh --namespace beecd --apply -``` - -#### 2. Install with Helm - -```bash -helm dependency update deploy/helm/beecd - -helm upgrade --install beecd deploy/helm/beecd \ - --namespace beecd \ - --set postgresql.enabled=true \ - --set minio.enabled=true \ - --set hiveServer.jwt.existingSecret=hive-jwt \ - --set hiveServer.github.existingSecret=github-tokens \ - --set hiveServer.sops.existingSecret=sops-gpg \ - --set hiveHq.jwt.secret="$(openssl rand -base64 48)" -``` - -> **Image registry:** The chart defaults to `registry:5000`. If your images are elsewhere, add: -> ``` -> --set image.registry="your-registry.example.com" \ -> --set image.tag="latest" -> ``` - -Wait for pods: -```bash -kubectl get pods -n beecd -w -``` - -You should see (all `1/1 Running`): -- `beecd-hive-server-*` -- `beecd-hive-hq-*` -- `beecd-postgresql-0` -- `beecd-minio-*` - -#### 3. Access the UI - -```bash -kubectl port-forward svc/beecd-hive-hq 8080:80 -n beecd -``` - -Open [http://localhost:8080](http://localhost:8080) in your browser. You should see the Hive HQ login page. +Go to [https://beecd.galleybytes.com](https://beecd.galleybytes.com) and create an account. After that, log in at your tenant URL (e.g., `https://acme.beecd.galleybytes.com`). -#### 4. Create Your Admin User +### Step 1: Configure Secrets -On first access, you need to create the initial admin user: +Before connecting clusters, configure your GitHub token so BeeCD can fetch manifests from your repositories. -1. Click **Register** on the login page -2. Enter a username and password -3. Click **Create Account** - -**Important:** Save your password somewhere secure. There is no password recovery mechanism. The first user created becomes the administrator. - -### First Deployment +1. Go to **Settings** +2. Under **GitHub Token**, enter your GitHub Personal Access Token (needs `repo` scope) +3. Click **Save** -#### Step 1: Connect a cluster (install the agent) +### Step 2: Connect a Cluster -BeeCD does **not** create Kubernetes clusters. A "cluster" in Hive HQ is an **existing** Kubernetes cluster that you connect by installing the BeeCD **agent** into it. +BeeCD deploys to existing Kubernetes clusters. Connect one by installing the BeeCD agent. -1. In Hive HQ, go to **Clusters** > **Add Cluster** +1. Go to **Clusters** -> **Add Cluster** 2. Give it a name and click **Create** -3. Hive HQ will generate an **agent manifest** - copy it -4. Save the manifest to a file (e.g., `agent.yaml`) -5. Apply it to the Kubernetes cluster you want BeeCD to deploy into: +3. Click **Generate Manifest** to get the agent installation YAML +4. Apply it to your Kubernetes cluster: ```bash kubectl apply -f agent.yaml ``` -This can be the same cluster where you installed BeeCD, or a different one. - -**About the generated manifest:** - -The agent manifest includes pre-filled defaults for connecting back to the Hive server: - -- **Hive gRPC Address**: Defaults to `beecd-hive-server.beecd.svc.cluster.local:5180` (in-cluster FQDN, plaintext). If the agent runs in a different cluster, you need to expose the Hive server via Ingress or LoadBalancer and update the address accordingly. -- **Agent Image**: Defaults to the same registry and tag configured in the Helm chart (e.g., `registry:5000/hive-agent:latest`). - -You can override these defaults in `values.yaml`: - -```yaml -hiveHq: - env: - hiveDefaultGrpcServer: "hive.example.com:443" - agentDefaultImage: "ghcr.io/yourorg/hive-agent:v1.0.0" -``` - -Once the agent connects, you will see a recent heartbeat for the cluster under **Clusters**. +Once the agent connects, you'll see a heartbeat timestamp under **Clusters**. -#### Step 2: Add a repository +### Step 3: Add a Repository -1. In Hive HQ, go to **Repositories** > **Add Repository** -2. Paste the GitHub repository URL (e.g., `https://github.com/galleybytes/manifests`) +1. Go to **Repositories** -> **Add Repository** +2. Enter the GitHub repository URL (e.g., `https://github.com/your-org/manifests`) 3. Click **Save** -#### Step 3: Add a branch and service - -A **branch** tells BeeCD which git branch to watch. A **service** defines what manifests to deploy from that branch. - -1. Click on the repository you just added - -2. Click **Add Branch** and name the branch to track (e.g., `main`) - -3. Click **Add Service** to define a deployable unit +### Step 4: Add a Branch and Service - 1. Fill in the service details: +A **branch** tells BeeCD which git branch to watch. A **service** defines what manifests to deploy. - 1. **Name**: A lowercase identifier for the service (e.g., `my-app`) - 2. **Manifest Path Template**: Where to find manifests in the repo. Use placeholders `{cluster}`, `{namespace}`, and `{service}` to organize by deployment target. +1. Click on your repository +2. Click **Add Branch** and enter the branch name (e.g., `main`) +3. Click **Add Service** and fill in: + - **Name**: A lowercase identifier (e.g., `my-app`) + - **Manifest Path Template**: Where to find manifests. Use placeholders `{cluster}`, `{namespace}`, and `{service}`. - Examples: - 1. `k8s/{cluster}/{namespace}/{service}/` - Separate directories per cluster/namespace/service - 2. `{cluster}/{namespace}/{service}.yaml` - Single file per service, organized by namespace + Examples: + - `k8s/{cluster}/{namespace}/{service}/` - Directory per cluster/namespace/service + - `{cluster}/{namespace}/{service}.yaml` - Single file per service 4. Click **Save** -#### Step 4: Create a cluster group and add your cluster to it +### Step 5: Create a Cluster Group -A **Cluster Group** is how you connect services to clusters. Services are assigned to cluster groups, and clusters in that group can deploy those services. +Cluster Groups connect services to clusters. Services are assigned to groups, and clusters in that group can deploy those services. -1. Go to **Cluster Groups** > **Add Group** -2. Give it a name (e.g., `production`) and click **Create** -3. Click on the group you just created +1. Go to **Cluster Groups** -> **Add Group** +2. Name it (e.g., `production`) and click **Create** +3. Click on the group 4. Click **Add Clusters** and select your cluster -5. Click **Add Services** and select the service you created in Step 3 +5. Click **Add Services** and select your service -#### Step 5: Label a namespace for deployment +### Step 6: Register a Namespace -In the Kubernetes cluster you connected in Step 1, label a namespace so BeeCD knows it is allowed to manage it: +Label a namespace in your Kubernetes cluster so BeeCD knows it can manage deployments there: ```bash -kubectl create namespace my-app -kubectl label namespace my-app beecd/register=true +kubectl label namespace my-namespace beecd.io/enabled=true ``` -Within 60 seconds, the namespace will appear in Hive HQ under **Clusters** > your cluster. +The namespace will appear in Hive HQ within 60 seconds under **Clusters** -> your cluster. -#### Step 6: Register the service to the namespace +### Step 7: Add Services to the Namespace -1. In Hive HQ, go to **Clusters** > your cluster -2. Find the `my-app` namespace -3. Click on the namespace to edit it -4. Select the service you want to deploy (only services from cluster groups this cluster belongs to will appear) -5. Click **Save** +1. Go to **Clusters** -> your cluster +2. Find the namespace and click the edit icon +3. Select the services you want to deploy (only services from cluster groups this cluster belongs to will appear) +4. Click **Add** -#### Step 7: Point the release at a commit +### Step 8: Create a Version -Each release needs to know which git commit to deploy. This is called a **version**. You can create one manually: +Each release needs a git commit to deploy. Create a version: -1. Navigate to **Clusters** > select your cluster > scroll down to the **Releases** section -2. Find your release in the list and click on it to open the detail page -3. Click **Versions** > **Add Manual Version** -4. Enter a version tag (e.g., `v1.0.0`) and the git commit SHA from your repo +1. Go to **Clusters** -> your cluster -> **Releases** +2. Click on a release +3. Click **Versions** -> **Add Manual Version** +4. Enter a version tag (e.g., `v1.0.0`) and the git commit SHA 5. Click **Create Version** -BeeCD will pull the manifests from that commit and prepare them for deployment. +BeeCD will fetch the manifests from that commit. -> **Tip:** For production, you can set up GitHub webhooks to create versions automatically when you push changes. See the Helm chart documentation for webhook configuration. - -#### Step 8: Review and approve +### Step 9: Review and Approve 1. Go to **Releases** 2. Click on the pending release -3. Review the **Diff** (shows what will be applied) +3. Review the **Diff** to see what will be applied 4. Click **Approve** -The manifests will deploy to your cluster. You can verify: +The manifests will deploy to your cluster. Verify with: ```bash -kubectl get all -n my-app +kubectl get all -n my-namespace ``` +## Concepts + +| Term | Description | +|------|-------------| +| **Cluster** | A Kubernetes cluster connected via the BeeCD agent | +| **Cluster Group** | A collection of clusters that share the same services | +| **Service** | A deployable unit defined by a manifest path in a git repository | +| **Release** | A service deployed to a specific namespace in a cluster | +| **Version** | A git commit SHA that a release should deploy | + +## Need Help? + +- Submit an issue on GitHub: https://github.com/galleybytes/beecd/issues +- Verify your GitHub token has access to the repository +- Ensure the agent is running: `kubectl get pods -n beecd` +- Check agent logs: `kubectl logs -n beecd -l app=beecd-agent` + diff --git a/agent/Cargo.lock b/agent/Cargo.lock new file mode 100644 index 0000000..bc81acd --- /dev/null +++ b/agent/Cargo.lock @@ -0,0 +1,2518 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "agent" +version = "0.2.0" +dependencies = [ + "base64 0.21.7", + "beecdiff", + "flate2", + "k8s-openapi", + "kube", + "prost", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tonic", + "tonic-build", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-broadcast" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.11", + "http-body 0.4.5", + "hyper 0.14.27", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower 0.4.13", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.11", + "http-body 0.4.5", + "mime", + "rustversion", + "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 = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.1", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "beecdiff" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[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.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "num-traits", + "serde", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.48", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "enum-ordinalize" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.5", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap 2.1.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.1.0", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.1.0", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http 0.2.11", + "http-body 0.4.5", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-http-proxy" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d06dbdfbacf34d996c6fb540a71a684a7aae9056c71951163af8a8a4c07b9a4" +dependencies = [ + "bytes", + "futures-util", + "headers", + "http 1.1.0", + "hyper 1.5.0", + "hyper-rustls", + "hyper-util", + "pin-project-lite", + "rustls-native-certs 0.7.3", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "log", + "rustls 0.23.16", + "rustls-native-certs 0.8.0", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.27", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonpath-rust" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d8fe85bd70ff715f31ce8c739194b423d79811a19602115d611a3ec85d6200" +dependencies = [ + "lazy_static", + "once_cell", + "pest", + "pest_derive", + "regex", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "k8s-openapi" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8847402328d8301354c94d605481f25a6bdc1ed65471fd96af8eca71141b13" +dependencies = [ + "base64 0.22.1", + "chrono", + "serde", + "serde-value", + "serde_json", +] + +[[package]] +name = "kube" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efffeb3df0bd4ef3e5d65044573499c0e4889b988070b08c50b25b1329289a1f" +dependencies = [ + "k8s-openapi", + "kube-client", + "kube-core", + "kube-derive", + "kube-runtime", +] + +[[package]] +name = "kube-client" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf471ece8ff8d24735ce78dac4d091e9fcb8d74811aeb6b75de4d1c3f5de0f1" +dependencies = [ + "base64 0.22.1", + "bytes", + "chrono", + "either", + "futures", + "home", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-http-proxy", + "hyper-rustls", + "hyper-timeout 0.5.2", + "hyper-util", + "jsonpath-rust", + "k8s-openapi", + "kube-core", + "pem", + "rustls 0.23.16", + "rustls-pemfile 2.2.0", + "secrecy", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tokio", + "tokio-util", + "tower 0.5.1", + "tower-http", + "tracing", +] + +[[package]] +name = "kube-core" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f42346d30bb34d1d7adc5c549b691bce7aa3a1e60254e68fab7e2d7b26fe3d77" +dependencies = [ + "chrono", + "form_urlencoded", + "http 1.1.0", + "json-patch", + "k8s-openapi", + "schemars", + "serde", + "serde-value", + "serde_json", + "thiserror", +] + +[[package]] +name = "kube-derive" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9364e04cc5e0482136c6ee8b7fb7551812da25802249f35b3def7aaa31e82ad" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.48", +] + +[[package]] +name = "kube-runtime" +version = "0.96.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fbf1f6ffa98e65f1d2a9a69338bb60605d46be7edf00237784b89e62c9bd44" +dependencies = [ + "ahash", + "async-broadcast", + "async-stream", + "async-trait", + "backoff", + "educe", + "futures", + "hashbrown 0.14.3", + "json-patch", + "jsonptr", + "k8s-openapi", + "kube-client", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", + "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.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pest" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f200d8d83c44a45b21764d1916299752ca035d15ecd46faca3e9a2a2bf6ad06" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcd6ab1236bbdb3a49027e920e693192ebfe8913f6d60e294de57463a493cfde" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a31940305ffc96863a735bef7c7994a00b325a7138fdbc5bda0f1a0476d3275" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pest_meta" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ff62f5259e53b78d1af898941cdcdccfae7385cf7d793a6e55de5d05bb4b7d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.48", +] + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +dependencies = [ + "bytes", + "heck", + "itertools", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.48", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9470c4bf8246c8daf25f9598dca807fb6510347b1e1cfa55749113850c79d88a" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +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", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[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 1.0.109", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +dependencies = [ + "indexmap 2.1.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tokio" +version = "1.41.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[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 = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.16", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", +] + +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2", + "http 0.2.11", + "http-body 0.4.5", + "hyper 0.14.27", + "hyper-timeout 0.4.1", + "percent-encoding", + "pin-project", + "prost", + "rustls 0.21.10", + "rustls-pemfile 1.0.4", + "tokio", + "tokio-rustls 0.24.1", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d021fc044c18582b9a2408cd0dd05b1596e3ecdb5c4df822bb0183545683889" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.48", +] + +[[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 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "base64 0.22.1", + "bitflags 2.4.1", + "bytes", + "http 1.1.0", + "http-body 1.0.1", + "mime", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[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-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.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/agent/Dockerfile b/agent/Dockerfile index 232e235..ae0c8c4 100644 --- a/agent/Dockerfile +++ b/agent/Dockerfile @@ -1,59 +1,28 @@ # syntax=docker/dockerfile:1.7 # ============================================================================= -# Optimized Agent Dockerfile - Uses BuildKit cache mounts +# Optimized Agent Dockerfile - Multi-arch support # ============================================================================= -# Key optimizations: -# 1. Uses BuildKit cache mounts for Cargo registry and target dirs -# 2. Builds natively on host arch, then cross-compiles only the final binary -# 3. Leverages pre-built base image for OpenSSL +# Supports linux/amd64 and linux/arm64 using rust-musl-cross base images # ============================================================================= -ARG RUST_VERSION=1.89.0 -ARG ALPINE_VERSION=3.19.1 +# Declare TARGETARCH at the top level +ARG TARGETARCH -# ============================================================================= -# Stage 1: Build OpenSSL for musl (cached layer) -# ============================================================================= -FROM rust:${RUST_VERSION} AS openssl-builder - -RUN apt-get update -y && apt-get install -y --no-install-recommends \ - musl-tools wget ca-certificates linux-libc-dev \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /usr/include/x86_64-linux-musl && \ - ln -sf /usr/include/x86_64-linux-gnu/asm /usr/include/x86_64-linux-musl/asm && \ - ln -sf /usr/include/asm-generic /usr/include/x86_64-linux-musl/asm-generic && \ - ln -sf /usr/include/linux /usr/include/x86_64-linux-musl/linux - -WORKDIR /musl -RUN wget -q https://github.com/openssl/openssl/archive/OpenSSL_1_1_1f.tar.gz && \ - tar xzf OpenSSL_1_1_1f.tar.gz && \ - cd openssl-OpenSSL_1_1_1f && \ - CC="musl-gcc -fPIE -pie" ./Configure no-shared no-async --prefix=/musl --openssldir=/musl/ssl linux-x86_64 && \ - make depend && \ - make -j$(nproc) && \ - make install - -# ============================================================================= -# Stage 2: Build dependencies (cached layer) -# ============================================================================= -FROM rust:${RUST_VERSION} AS deps-builder +# Use different base images based on target architecture +FROM messense/rust-musl-cross:x86_64-musl AS builder-amd64 +FROM messense/rust-musl-cross:aarch64-musl AS builder-arm64 -RUN apt-get update -y && apt-get install -y --no-install-recommends \ - musl-tools protobuf-compiler linux-libc-dev \ - && rm -rf /var/lib/apt/lists/* +# Select the appropriate builder based on TARGETARCH +FROM builder-${TARGETARCH} AS builder -# Setup musl include paths -RUN mkdir -p /usr/include/x86_64-linux-musl && \ - ln -sf /usr/include/x86_64-linux-gnu/asm /usr/include/x86_64-linux-musl/asm && \ - ln -sf /usr/include/asm-generic /usr/include/x86_64-linux-musl/asm-generic && \ - ln -sf /usr/include/linux /usr/include/x86_64-linux-musl/linux +# Redeclare ARG after FROM +ARG TARGETARCH -RUN rustup target add x86_64-unknown-linux-musl - -# Copy pre-built OpenSSL -COPY --from=openssl-builder /musl /musl -ENV OPENSSL_DIR=/musl +# Install protobuf compiler and update CA certificates +RUN apt-get update && \ + apt-get install -y protobuf-compiler ca-certificates && \ + update-ca-certificates && \ + rm -rf /var/lib/apt/lists/* WORKDIR /usr/src @@ -74,16 +43,20 @@ RUN mkdir -p agent/src && echo 'fn main() {}' > agent/src/main.rs && touch agent mkdir -p diff/src && touch diff/src/lib.rs && \ mkdir -p storage/src && touch storage/src/lib.rs -# Build dependencies with cache mount -RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ - --mount=type=cache,target=/usr/local/cargo/git,id=cargo-git \ - --mount=type=cache,target=/usr/src/target,id=agent-target \ - cargo build --release --target x86_64-unknown-linux-musl -p agent 2>/dev/null || true - -# ============================================================================= -# Stage 3: Build actual binary -# ============================================================================= -FROM deps-builder AS builder +# Set Rust target as environment variable based on architecture +ENV RUST_TARGET=${TARGETARCH:+$(test "$TARGETARCH" = "arm64" && echo "aarch64-unknown-linux-musl" || echo "x86_64-unknown-linux-musl")} +RUN echo "Building for architecture: $TARGETARCH, target: $RUST_TARGET" + +# Build dependencies with architecture-specific cache mounts +RUN --mount=type=cache,target=/root/.cargo/registry,id=cargo-registry-${TARGETARCH} \ + --mount=type=cache,target=/root/.cargo/git,id=cargo-git-${TARGETARCH} \ + --mount=type=cache,target=/usr/src/target,id=agent-target-${TARGETARCH} \ + if [ "$TARGETARCH" = "arm64" ]; then \ + RUST_TARGET="aarch64-unknown-linux-musl"; \ + else \ + RUST_TARGET="x86_64-unknown-linux-musl"; \ + fi && \ + cargo build --release --target $RUST_TARGET -p agent 2>/dev/null || true # Copy real source files COPY agent/src ./agent/src @@ -98,14 +71,22 @@ ARG BUILD_VERSION ENV BUILD_VERSION=${BUILD_VERSION} # Build final binary -RUN cargo build --release --target x86_64-unknown-linux-musl -p agent && \ - cp /usr/src/target/x86_64-unknown-linux-musl/release/agent /usr/local/bin/agent +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + RUST_TARGET="aarch64-unknown-linux-musl"; \ + else \ + RUST_TARGET="x86_64-unknown-linux-musl"; \ + fi && \ + cargo build --release --target $RUST_TARGET -p agent && \ + cp /usr/src/target/$RUST_TARGET/release/agent /usr/local/bin/agent # ============================================================================= -# Stage 4: Runtime image +# Runtime image # ============================================================================= FROM scratch +ARG IMAGE_SOURCE=https://github.com/galleybytes/beecd +LABEL org.opencontainers.image.source=${IMAGE_SOURCE} + COPY --from=builder /etc/ssl/certs /etc/ssl/certs COPY --from=builder /usr/local/bin/agent /usr/local/bin/agent diff --git a/agent/src/agent.rs b/agent/src/agent.rs index d6e8535..bad4ea6 100644 --- a/agent/src/agent.rs +++ b/agent/src/agent.rs @@ -215,6 +215,9 @@ pub struct Agent { GrpcHeaderInjector, >, >, + /// Unauthenticated gRPC client for auth operations (Login, RefreshToken, Logout) + /// Using the regular grpc_client for these would inject expired tokens, causing auth loops + pub grpc_auth_client: WorkerClient, pub k8s_client: Client, pub discovery: Discovery, pub owner: String, @@ -2652,6 +2655,7 @@ impl Agent { GrpcHeaderInjector, >, >, + grpc_auth_client: WorkerClient, k8s_client: Client, owner_name: String, cluster_name: String, @@ -2698,6 +2702,7 @@ impl Agent { drop(_enter); Ok(Self { grpc_client, + grpc_auth_client, k8s_client, discovery, owner: owner_name, @@ -2734,7 +2739,7 @@ impl Agent { debug!("Refreshing access token"); let response = self - .grpc_client + .grpc_auth_client .clone() .refresh_token(beecd::RefreshTokenRequest { refresh_token: refresh_token_value, @@ -2770,7 +2775,7 @@ impl Agent { info!("Re-authenticating with server"); let response = self - .grpc_client + .grpc_auth_client .clone() .login(beecd::LoginRequest { username: self.cluster_name.clone(), diff --git a/agent/src/main.rs b/agent/src/main.rs index 426c728..3ff939b 100644 --- a/agent/src/main.rs +++ b/agent/src/main.rs @@ -185,6 +185,7 @@ async fn main() -> Result<(), Box> { let agent = Agent::new( grpc_client, + grpc_client_for_login, k8s_client, owner, cluster_name, diff --git a/build-fast.sh b/build-fast.sh index 9ba5895..142dbe4 100755 --- a/build-fast.sh +++ b/build-fast.sh @@ -261,10 +261,10 @@ build_binary() { } ####################################### -# Build hive-hq UI and docs (requires npm and zola) +# Build hive-hq UI (requires npm) ####################################### build_hive_hq_assets() { - log_info "Building hive-hq UI and docs..." + log_info "Building hive-hq UI..." # Check for npm if ! command -v npm &>/dev/null; then @@ -272,16 +272,6 @@ build_hive_hq_assets() { exit 1 fi - # Check for zola - if ! command -v zola &>/dev/null; then - log_error "zola not found. Please install: brew install zola" - exit 1 - fi - - # Build docs with zola - log_info "Building docs with zola..." - (cd hive-hq/docs && zola build --output-dir ../dist-docs --force) - # Build UI with npm/vite (React) log_info "Building UI with npm (React/Vite)..." (cd hive-hq/ui && npm ci && npm run build) @@ -290,7 +280,7 @@ build_hive_hq_assets() { rm -rf hive-hq/dist cp -r hive-hq/ui/dist hive-hq/dist - log_success "UI and docs built" + log_success "UI built" } ####################################### @@ -327,13 +317,12 @@ create_docker_image() { local tmp_dockerfile=$(mktemp) if [[ "$service" == "hive-hq" ]]; then - # hive-hq needs UI assets and docs + # hive-hq needs UI assets cat > "$tmp_dockerfile" < -- psql -U postgres -d hive -f /path/to/init.sql + # + # Or via a Kubernetes Job before deploying the application. + # The migration grants DML privileges to hive_user/hq_user at the end. + 00-init-roles.sql: | + -- Create hive database CREATE DATABASE hive; + + -- hive_admin: owns tables and SECURITY DEFINER functions + -- BYPASSRLS allows SECURITY DEFINER functions to bypass RLS + -- Used for migrations, admin tasks, bypasses RLS + CREATE USER hive_admin WITH PASSWORD 'hive-admin-password' BYPASSRLS; + GRANT ALL PRIVILEGES ON DATABASE hive TO hive_admin; + + -- hive_user: app connection role, subject to RLS (non-owner) + -- Used by hive-server and hive-hq application connections CREATE USER hive_user WITH PASSWORD 'hive-password'; - GRANT ALL PRIVILEGES ON DATABASE hive TO hive_user; + GRANT CONNECT ON DATABASE hive TO hive_user; + + -- hq_user: same as hive_user (used by hive-hq API) + CREATE USER hq_user WITH PASSWORD 'hq-password'; + GRANT CONNECT ON DATABASE hive TO hq_user; + + -- Allow admin to act as app user for testing + GRANT hive_user TO hive_admin; + GRANT hq_user TO hive_admin; + \c hive CREATE EXTENSION IF NOT EXISTS pgcrypto; - GRANT ALL ON SCHEMA public TO hive_user; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO hive_user; - ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO hive_user; + + -- hive_admin owns the schema + GRANT ALL ON SCHEMA public TO hive_admin; + ALTER SCHEMA public OWNER TO hive_admin; + + -- App users get basic schema access (DML grants come from migration) + GRANT USAGE ON SCHEMA public TO hive_user; + GRANT USAGE ON SCHEMA public TO hq_user; # MinIO subchart configuration # Enable to deploy MinIO with the chart @@ -83,7 +118,55 @@ hiveServer: grpcTimeout: 30 grpcKeepAlive: 20 + # Hive Server configuration +hiveServer: + enabled: true + replicaCount: 1 + + image: + repository: hive-server + tag: "" # Optional: override image tag for this component + + service: + type: ClusterIP + port: 5180 + + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 250m + # memory: 256Mi + + env: + logLevel: info + storageType: s3 + grpcTimeout: 30 + grpcKeepAlive: 20 + + # Crypto configuration for multi-tenant secret encryption + crypto: + # Bootstrap key: 32-byte random key (base64-encoded) used to derive per-tenant encryption keys + # Generate with: openssl rand -base64 32 + # WARNING: This is the master key for all tenant secret decryption - protect it carefully + # RECOMMENDED: Use existingSecret to load from Kubernetes Secret instead of hardcoding + bootstrapKey: "" # Use existingSecret for production + + # Use existing Kubernetes secret to load the bootstrap key + # Secret must contain: HIVE_CRYPTO_ROOT_KEY + # Create with: kubectl create secret generic hive-crypto-bootstrap \ + # --from-literal=HIVE_CRYPTO_ROOT_KEY="$(openssl rand -base64 32)" + existingSecret: "" # Name of secret containing HIVE_CRYPTO_ROOT_KEY + # Database configuration - REQUIRED (auto-configured if postgresql.enabled=true) + # + # Two-role pattern for Row-Level Security: + # - hive_admin: owns tables, runs migrations, bypasses RLS + # - hive_user: app connection role, subject to RLS (non-owner) + # + # The application (hive-server, hive-hq) connects as hive_user. + # Migrations must be run separately as hive_admin or postgres superuser. database: # Hive database (single database for all BeeCD data) hive: @@ -92,14 +175,23 @@ hiveServer: hostRo: "" # Optional read-only replica, defaults to host if empty port: 5432 name: hive + # Application user (subject to RLS - non-owner) user: hive_user password: "hive-password" # Use existingSecret instead for production existingSecret: "" # Name of existing secret containing DATABASE_HOST, DATABASE_PASSWORD, etc. - # GitHub configuration - REQUIRED + # Admin user for running migrations (bypasses RLS - table owner) + # Only needed if running migrations separately from PostgreSQL initdb + admin: + user: hive_admin + password: "hive-admin-password" # Use existingSecret for production + existingSecret: "" # Secret containing ADMIN_DATABASE_USER, ADMIN_DATABASE_PASSWORD + + # GitHub configuration - OPTIONAL (for legacy repo access) + # In multi-tenant mode, GitHub tokens are configured per-tenant via the UI github: - user: "" - token: "" # Personal Access Token with repo scope + user: "" # Optional: for legacy single-tenant mode or backward compatibility + token: "" # Optional: Personal Access Token with repo scope apiUrl: "" # Optional: for GitHub Enterprise existingSecret: "" # Use existing secret instead @@ -121,10 +213,11 @@ hiveServer: # --from-literal=JWT_SECRET_KEY="$(openssl rand -base64 48)" existingSecret: "" - # SOPS/GPG configuration - REQUIRED for encrypted secrets + # SOPS/GPG configuration - OPTIONAL (for legacy single-tenant mode) + # In multi-tenant mode, GPG keys are configured per-tenant via the UI sops: - fingerprint: "" - gpgKey: "" # Base64-encoded GPG private key + fingerprint: "" # Optional: GPG key fingerprint + gpgKey: "" # Optional: Base64-encoded GPG private key existingSecret: "" # Use existing secret instead # Storage configuration (S3 or MinIO) - REQUIRED (auto-configured if minio.enabled=true) @@ -170,6 +263,11 @@ hiveHq: axumPort: 8080 dist: dist + # Base domain for subdomain extraction (e.g., "beecd.example.com"). + # When set, tenant subdomains like "tenant1.beecd.example.com" will extract "tenant1" as the slug. + # When unset, falls back to extracting the first label (before the first dot). + baseDomain: "" + # Preferred: explicit GitHub webhook callback URL. # This is the URL GitHub will POST to for webhook events. # Example: @@ -182,8 +280,10 @@ hiveHq: # Both have sensible defaults computed from the chart if left empty. # # hiveDefaultGrpcServer: Defaults to {{ .Release.Name }}-hive-server.{{ .Release.Namespace }}.svc.cluster.local:5180 + # hiveDefaultGrpcTls: Explicit TLS override. If not set, inferred from scheme (https:// = true, else false) # agentDefaultImage: Defaults to {{ image.registry }}/hive-agent:{{ image.tag }} hiveDefaultGrpcServer: "" + hiveDefaultGrpcTls: "" # "true" or "false" to override, empty to infer from scheme agentDefaultImage: "" # JWT configuration @@ -191,7 +291,17 @@ hiveHq: secret: "" # Auto-generated if not provided existingSecret: "" # Use existing secret instead - # Uses hiveServer.database.hive configuration + # Database configuration for hive-hq (uses hq_user, subject to RLS) + database: + # Leave empty to use embedded PostgreSQL (when postgresql.enabled=true) + host: "" # Will default to {{ .Release.Name }}-postgresql if postgresql.enabled + hostRo: "" # Optional read-only replica, defaults to host if empty + port: 5432 + name: hive + # hq_user: app connection role, subject to RLS (non-owner) + user: hq_user + password: "hq-password" # Use existingSecret instead for production + existingSecret: "" # Name of existing secret containing DATABASE_HOST, DATABASE_PASSWORD, etc. # Ingress configuration ingress: diff --git a/diff/.gitignore b/diff/.gitignore index ef2bc88..5e4a57e 100644 --- a/diff/.gitignore +++ b/diff/.gitignore @@ -1,5 +1,4 @@ /target .vscode -Cargo.lock /src/main.rs /tests/output/ diff --git a/diff/Cargo.lock b/diff/Cargo.lock new file mode 100644 index 0000000..041e6ff --- /dev/null +++ b/diff/Cargo.lock @@ -0,0 +1,317 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + +[[package]] +name = "beecdiff" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "serde_yaml", + "thiserror", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/hack/setup-dev-db.sh b/hack/setup-dev-db.sh index 201460e..bdf11f8 100755 --- a/hack/setup-dev-db.sh +++ b/hack/setup-dev-db.sh @@ -17,6 +17,8 @@ DB_SUPERPASSWORD=${DB_SUPERPASSWORD:-pass} CRUD_DB=${CRUD_DB:-crud} HIVE_DB=${HIVE_DB:-hive} +HIVE_ADMIN=${HIVE_ADMIN:-hive_admin} +HIVE_ADMIN_PASSWORD=${HIVE_ADMIN_PASSWORD:-adminpass} HIVE_USER=${HIVE_USER:-hive_user} HIVE_PASSWORD=${HIVE_PASSWORD:-pass} @@ -91,11 +93,21 @@ if ! psql_super postgres "SELECT 1 FROM pg_database WHERE datname='$AVERSION_DB' fi # create/update roles +# hive_admin: owns tables and SECURITY DEFINER functions, runs migrations +# BYPASSRLS allows SECURITY DEFINER functions to bypass RLS +if ! psql_super postgres "SELECT 1 FROM pg_roles WHERE rolname='$HIVE_ADMIN'" | grep -q 1; then + psql_super postgres "CREATE USER $HIVE_ADMIN WITH PASSWORD '$HIVE_ADMIN_PASSWORD' BYPASSRLS" +else + psql_super postgres "ALTER ROLE $HIVE_ADMIN WITH PASSWORD '$HIVE_ADMIN_PASSWORD' BYPASSRLS" +fi +# hive_user: app connection role, subject to RLS (non-owner) if ! psql_super postgres "SELECT 1 FROM pg_roles WHERE rolname='$HIVE_USER'" | grep -q 1; then psql_super postgres "CREATE USER $HIVE_USER WITH PASSWORD '$HIVE_PASSWORD'" else psql_super postgres "ALTER ROLE $HIVE_USER WITH PASSWORD '$HIVE_PASSWORD'" fi +# Grant hive_user to hive_admin so admin can test as app user +psql_super postgres "GRANT $HIVE_USER TO $HIVE_ADMIN" 2>/dev/null || true if ! psql_super postgres "SELECT 1 FROM pg_roles WHERE rolname='$HQ_USER'" | grep -q 1; then psql_super postgres "CREATE USER $HQ_USER WITH PASSWORD '$HQ_PASSWORD'" else @@ -108,16 +120,18 @@ else fi # grants for hive db -psql_super postgres "GRANT ALL PRIVILEGES ON DATABASE $HIVE_DB TO $HIVE_USER" +# hive_admin owns the database and schema +psql_super postgres "GRANT ALL PRIVILEGES ON DATABASE $HIVE_DB TO $HIVE_ADMIN" +psql_super "$HIVE_DB" "GRANT ALL ON SCHEMA public TO $HIVE_ADMIN" +psql_super "$HIVE_DB" "ALTER SCHEMA public OWNER TO $HIVE_ADMIN" + +# hive_user gets DML access (NOT ownership - this is key for RLS) +psql_super postgres "GRANT CONNECT ON DATABASE $HIVE_DB TO $HIVE_USER" +psql_super "$HIVE_DB" "GRANT USAGE ON SCHEMA public TO $HIVE_USER" + +# hq_user shares same permissions as hive_user (both are app roles) psql_super postgres "GRANT CONNECT ON DATABASE $HIVE_DB TO $HQ_USER" -psql_super "$HIVE_DB" "GRANT ALL ON SCHEMA public TO $HIVE_USER" -psql_super "$HIVE_DB" "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO $HIVE_USER" -psql_super "$HIVE_DB" "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO $HIVE_USER" -psql_super "$HIVE_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $HIVE_USER" -psql_super "$HIVE_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $HIVE_USER" -psql_super "$HIVE_DB" "GRANT ALL ON SCHEMA public TO $HQ_USER" -psql_super "$HIVE_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $HQ_USER" -psql_super "$HIVE_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $HQ_USER" +psql_super "$HIVE_DB" "GRANT USAGE ON SCHEMA public TO $HQ_USER" # grants for aversion db psql_super postgres "GRANT ALL PRIVILEGES ON DATABASE $AVERSION_DB TO $AVERSION_USER" @@ -127,7 +141,23 @@ psql_super "$AVERSION_DB" "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA publi psql_super "$AVERSION_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO $AVERSION_USER" psql_super "$AVERSION_DB" "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO $AVERSION_USER" -echo "Running initial hive migration..." -PGPASSWORD="$HIVE_PASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -U "$HIVE_USER" -d "$HIVE_DB" -p "$DB_PORT" -f hive/migrations/20241215000001_initial_schema.sql +echo "Running initial hive migration as admin..." +# Migration runs as hive_admin (or superuser) so tables are owned correctly +# The init.sql grants DML privileges to hive_user at the end +PGPASSWORD="$DB_SUPERPASSWORD" psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -U "$DB_SUPERUSER" -d "$HIVE_DB" -p "$DB_PORT" -f hive/migrations/00000000000000_init.sql echo "Done" +echo "" +echo "Role setup:" +echo " hive_admin: owns tables/functions, use for migrations (password: $HIVE_ADMIN_PASSWORD)" +echo " hive_user: app connection role, subject to RLS (password: $HIVE_PASSWORD)" +echo "" +echo "Environment variables for hive-server:" +echo " export DATABASE_HOST=$DB_HOST" +echo " export DATABASE_HOST_RO=$DB_HOST" +echo " export DATABASE_PORT=$DB_PORT" +echo " export DATABASE_NAME=$HIVE_DB" +echo " export DATABASE_USER=$HIVE_USER" +echo " export DATABASE_PASSWORD=$HIVE_PASSWORD" +echo " export DATABASE_ADMIN_USER=$HIVE_ADMIN" +echo " export DATABASE_ADMIN_PASSWORD=$HIVE_ADMIN_PASSWORD" diff --git a/hive-hq/Cargo.lock b/hive-hq/Cargo.lock new file mode 100644 index 0000000..be78c5d --- /dev/null +++ b/hive-hq/Cargo.lock @@ -0,0 +1,5418 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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-to-html" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d73c455ae09fa2223a75114789f30ad605e9e297f79537953523366c05995f5f" +dependencies = [ + "regex", + "thiserror 1.0.65", +] + +[[package]] +name = "anymap2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" + +[[package]] +name = "api" +version = "0.2.0" +dependencies = [ + "ansi-to-html", + "axum", + "base64 0.22.1", + "bcrypt", + "beecdstorage", + "chrono", + "futures", + "jsonwebtoken", + "quick-xml", + "rand 0.9.0", + "reqwest 0.12.14", + "serde", + "serde_json", + "serde_yaml", + "sqlx", + "tinytemplate", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "types", + "utoipa", + "utoipa-swagger-ui", + "uuid", +] + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-compression" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "aws-config" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c39646d1a6b51240a1a23bb57ea4eebede7e16fbc237fdc876980233dcecb4f" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4471bef4c22a06d2c7a1b6492493d3fdf24a805323109d6874f9c94d5906ac14" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b756939cb2f8dc900aa6dcd505e6e2428e9cae7ff7b028c49e3946efa70878" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9f7720b74ed28ca77f90769a71fd8c637a0137f6fae4ae947e1050229cff57f" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aff45ffe35196e593ea3b9dd65b320e51e2dda95aff4390bc459e461d09c6ad" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.82.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6eab2900764411ab01c8e91a76fd11a63b4e12bc3da97d9e14a0ce1343d86d3" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.11", + "http 1.3.1", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d4bdb0e5f80f0689e61c77ab678b2b9304af329616af38aef5b6b967b8e736" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbbb3ce8da257aedbccdcb1aadafbbb6a5fe9adf445db0e1ea897bdc7e22d08" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.65.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96a78a8f50a1630db757b60f679c8226a8a70ee2ab5f5e6e51dc67f6c61c7cfd" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d03c3c05ff80d54ff860fe38c726f6f494c639ae975203a101335f223386db" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.11", + "http 1.3.1", + "once_cell", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.63.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d21e1ba6f2cdec92044f904356a19f5ad86961acf015741106cdfafd747c0" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "crc64fast-nvme", + "hex", + "http 0.2.11", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c45d3dddac16c5c59d553ece225a88870cf81b7b813c9cc17b78cf4685eac7a" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http 1.3.1", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aff1159006441d02e57204bf57a1b890ba68bedb6904ffd2873c1c4c11c546b" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.8", + "http 0.2.11", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.5", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445d065e76bc1ef54963db400319f1dd3ebb3e0a74af20f7f7630625b0cc7cc0" +dependencies = [ + "aws-smithy-runtime-api", + "once_cell", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0152749e17ce4d1b47c7747bdfec09dac1ccafdcbc741ebf9daa2a373356730f" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.11", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.0", + "once_cell", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.11", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3873f8deed8927ce8d04487630dc9ff73193bab64742a61d050e57a68dec4125" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] + +[[package]] +name = "axum" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http 1.3.1", + "http-body 1.0.0", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +dependencies = [ + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.0", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.1", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bcrypt" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92758ad6077e4c76a6cadbce5005f666df70d4f13b19976b1a8062eef880040f" +dependencies = [ + "base64 0.22.1", + "blowfish", + "getrandom 0.3.1", + "subtle", + "zeroize", +] + +[[package]] +name = "beecdstorage" +version = "0.1.0" +dependencies = [ + "async-trait", + "aws-config", + "aws-sdk-s3", + "flate2", + "thiserror 2.0.12", + "url", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.9.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.98", + "which", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + +[[package]] +name = "boolinator" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9" + +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cc" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[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.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.6", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crc64fast-nvme" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3" +dependencies = [ + "crc", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.98", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve", + "rfc6979", + "signature 1.6.4", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff", + "generic-array", + "group", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flate2" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.8", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "gloo" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28999cda5ef6916ffd33fb4a7b87e1de633c47c0dc6d97905fee1cdaa142b94d" +dependencies = [ + "gloo-console 0.2.3", + "gloo-dialogs 0.1.1", + "gloo-events 0.1.2", + "gloo-file 0.2.3", + "gloo-history 0.1.5", + "gloo-net 0.3.1", + "gloo-render 0.1.1", + "gloo-storage 0.2.2", + "gloo-timers 0.2.6", + "gloo-utils 0.1.7", + "gloo-worker 0.2.1", +] + +[[package]] +name = "gloo" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd35526c28cc55c1db77aed6296de58677dbab863b118483a27845631d870249" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.4.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.4.0", +] + +[[package]] +name = "gloo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" +dependencies = [ + "gloo-console 0.3.0", + "gloo-dialogs 0.2.0", + "gloo-events 0.2.0", + "gloo-file 0.3.0", + "gloo-history 0.2.2", + "gloo-net 0.5.0", + "gloo-render 0.2.0", + "gloo-storage 0.3.0", + "gloo-timers 0.3.0", + "gloo-utils 0.2.0", + "gloo-worker 0.5.0", +] + +[[package]] +name = "gloo-console" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b7ce3c05debe147233596904981848862b068862e9ec3e34be446077190d3f" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67062364ac72d27f08445a46cab428188e2e224ec9e37efdba48ae8c289002e6" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b107f8abed8105e4182de63845afcc7b69c098b7852a813ea7462a320992fc" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d5564e570a38b43d78bdc063374a0c3098c4f0d64005b12f9bbe87e869b6d7" +dependencies = [ + "gloo-events 0.1.2", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "futures-channel", + "gloo-events 0.2.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85725d90bf0ed47063b3930ef28e863658a7905989e9929a8708aab74a1d5e7f" +dependencies = [ + "gloo-events 0.1.2", + "gloo-utils 0.1.7", + "serde", + "serde-wasm-bindgen 0.5.0", + "serde_urlencoded", + "thiserror 1.0.65", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom 0.2.12", + "gloo-events 0.2.0", + "gloo-utils 0.2.0", + "serde", + "serde-wasm-bindgen 0.6.3", + "serde_urlencoded", + "thiserror 1.0.65", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "http 0.2.11", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.65", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ac9e8288ae2c632fa9f8657ac70bfe38a1530f345282d7ba66a1f70b72b7dc4" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http 0.2.11", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.65", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.2.0", + "http 0.2.11", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror 1.0.65", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd9306aef67cfd4449823aadcd14e3958e0800aa2183955a309112a84ec7764" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6ab60bf5dbfd6f0ed1f7843da31b41010515c745735c970e821945ca91e480" +dependencies = [ + "gloo-utils 0.1.7", + "js-sys", + "serde", + "serde_json", + "thiserror 1.0.65", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils 0.2.0", + "js-sys", + "serde", + "serde_json", + "thiserror 1.0.65", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13471584da78061a28306d1359dd0178d8d6fc1c7c80e5e35d27260346e0516a" +dependencies = [ + "anymap2", + "bincode", + "gloo-console 0.2.3", + "gloo-utils 0.1.7", + "js-sys", + "serde", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76495d3dd87de51da268fa3a593da118ab43eb7f8809e17eb38d3319b424e400" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror 1.0.65", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +dependencies = [ + "bincode", + "futures", + "gloo-utils 0.2.0", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror 1.0.65", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "htmlescape" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" + +[[package]] +name = "http" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.0", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" + +[[package]] +name = "httparse" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper 0.14.28", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.0", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "implicit-clone" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc06a255cbf402a52ae399c05a518c68c569c916ea11423620111df576b9b9bb" +dependencies = [ + "implicit-clone-derive", + "indexmap", +] + +[[package]] +name = "implicit-clone-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9311685eb9a34808bbb0608ad2fcab9ae216266beca5848613e95553ac914e3b" +dependencies = [ + "quote", + "syn 2.0.98", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.9.2", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[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-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[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.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror 1.0.65", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der 0.7.8", + "pkcs8 0.10.2", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki 0.7.3", +] + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +dependencies = [ + "proc-macro2", + "syn 2.0.98", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[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 1.0.109", + "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.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prokio" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b55e106e5791fa5a13abd13c85d6127312e8e09098059ca2bc9b03ca4cf488" +dependencies = [ + "futures", + "gloo 0.8.1", + "num_cpus", + "once_cell", + "pin-project", + "pinned", + "tokio", + "tokio-stream", + "wasm-bindgen-futures", +] + +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", + "zerocopy", +] + +[[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 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.12", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.1", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "reqwest" +version = "0.11.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body 1.0.0", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.12", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "route-recognizer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "signature 2.2.0", + "spki 0.7.3", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b3aba5104622db5c9fc61098de54708feb732e7763d7faa2fa625899f00bf6f" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f198c73be048d2c5aa8e12f7960ad08443e56fd39cc26336719fdb4ea0ebaae" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.98", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a2fcdc9f40c8dc2922842ca9add611ad19f332227fc651d015881ad1552bd9a" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +dependencies = [ + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.9.2", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9b713f70513ae1f8d92665bbbbda5c295c2cf1da5542881ae5eefe20c9af132" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.12", + "time", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] + +[[package]] +name = "snailquote" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec62a949bda7f15800481a711909f946e1204f2460f89210eaf7f57730f88f86" +dependencies = [ + "thiserror 1.0.65", + "unicode_categories", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" +dependencies = [ + "base64ct", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.8", +] + +[[package]] +name = "sqlx" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" +dependencies = [ + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.12", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 2.0.98", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 2.0.98", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.9.0", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.12", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.3.1", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl 1.0.65", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" + +[[package]] +name = "time-macros" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.23", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" +dependencies = [ + "async-compression", + "base64 0.22.1", + "bitflags 2.9.0", + "bytes", + "futures-core", + "futures-util", + "http 1.3.1", + "http-body 1.0.0", + "http-body-util", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "types" +version = "0.1.0" +dependencies = [ + "chrono", + "serde", + "serde_json", + "sqlx", + "utoipa", + "uuid", +] + +[[package]] +name = "ui" +version = "0.2.0" +dependencies = [ + "ansi-to-html", + "chrono", + "gloo 0.11.0", + "gloo-net 0.5.0", + "gloo-storage 0.3.0", + "htmlescape", + "reqwest 0.11.23", + "serde", + "serde_json", + "snailquote", + "thiserror 1.0.65", + "types", + "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew", + "yew-router", + "yewdux", +] + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utoipa" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.98", + "uuid", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29519b3c485df6b13f4478ac909a491387e9ef70204487c3b64b53749aec0be" +dependencies = [ + "axum", + "base64 0.22.1", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom 0.2.12", + "serde", +] + +[[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 = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.52.0", +] + +[[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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.5.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yew" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f1a03f255c70c7aa3e9c62e15292f142ede0564123543c1cc0c7a4f31660cac" +dependencies = [ + "console_error_panic_hook", + "futures", + "gloo 0.10.0", + "implicit-clone", + "indexmap", + "js-sys", + "prokio", + "rustversion", + "serde", + "slab", + "thiserror 1.0.65", + "tokio", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "yew-macro", +] + +[[package]] +name = "yew-macro" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fd8ca5166d69e59f796500a2ce432ff751edecbbb308ca59fd3fe4d0343de2" +dependencies = [ + "boolinator", + "once_cell", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "yew-router" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca1d5052c96e6762b4d6209a8aded597758d442e6c479995faf0c7b5538e0c6" +dependencies = [ + "gloo 0.10.0", + "js-sys", + "route-recognizer", + "serde", + "serde_urlencoded", + "tracing", + "urlencoding", + "wasm-bindgen", + "web-sys", + "yew", + "yew-router-macro", +] + +[[package]] +name = "yew-router-macro" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bfd190a07ca8cfde7cd4c52b3ac463803dc07323db8c34daa697e86365978c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "yewdux" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8030a7de50678c07c038dcb96a42f1e8a7c4cc5610451fbee0c676aa7df42967" +dependencies = [ + "log", + "serde", + "serde_json", + "slab", + "thiserror 1.0.65", + "wasm-bindgen", + "web-sys", + "yew", + "yewdux-macros", +] + +[[package]] +name = "yewdux-macros" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ac6ccd84a49bbce44610d44eb6686a1266337d0cd3aeadb5564ab76a2819f0" +dependencies = [ + "darling", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + +[[package]] +name = "zip" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zopfli" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1f48f3508a3a3f2faee01629564400bc12260f6214a056d06a3aaaa6ef0736" +dependencies = [ + "crc32fast", + "log", + "simd-adler32", + "typed-arena", +] + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/hive-hq/Dockerfile b/hive-hq/Dockerfile index c174846..48202ae 100644 --- a/hive-hq/Dockerfile +++ b/hive-hq/Dockerfile @@ -1,6 +1,8 @@ # syntax=docker/dockerfile:1.7 # ============================================================================= -# Optimized Hive-HQ Dockerfile with BuildKit cache mounts +# Optimized Hive-HQ Dockerfile - Multi-arch support +# ============================================================================= +# Supports linux/amd64 and linux/arm64 # ============================================================================= # Stage 1: Build the React UI with Node.js @@ -14,34 +16,24 @@ RUN --mount=type=cache,target=/root/.npm,id=npm-cache \ COPY hive-hq/ui ./ RUN npm run build -# Stage 2: Build Rust API binary -FROM rust:1.92.0 AS builder - -RUN apt-get update -y && apt-get install -y --no-install-recommends \ - musl-tools wget ca-certificates linux-libc-dev \ - && rm -rf /var/lib/apt/lists/* - -# Setup musl include paths -RUN mkdir -p /usr/include/x86_64-linux-musl && \ - ln -sf /usr/include/x86_64-linux-gnu/asm /usr/include/x86_64-linux-musl/asm && \ - ln -sf /usr/include/asm-generic /usr/include/x86_64-linux-musl/asm-generic && \ - ln -sf /usr/include/linux /usr/include/x86_64-linux-musl/linux - -# Build OpenSSL (cached as a layer) -WORKDIR /musl -RUN wget -q https://github.com/openssl/openssl/archive/OpenSSL_1_1_1f.tar.gz && \ - tar xzf OpenSSL_1_1_1f.tar.gz && \ - cd openssl-OpenSSL_1_1_1f && \ - CC="musl-gcc -fPIE -pie" ./Configure no-shared no-async --prefix=/musl --openssldir=/musl/ssl linux-x86_64 && \ - make depend && make -j$(nproc) && make install && \ - rm -rf /musl/OpenSSL_1_1_1f.tar.gz /musl/openssl-OpenSSL_1_1_1f - -# Setup Rust targets +# Stage 2: Build Rust API binary using rust-musl-cross +# Declare TARGETARCH at the top level +ARG TARGETARCH + +# Use different base images based on target architecture +FROM messense/rust-musl-cross:x86_64-musl AS builder-amd64 +FROM messense/rust-musl-cross:aarch64-musl AS builder-arm64 + +# Select the appropriate builder based on TARGETARCH +FROM builder-${TARGETARCH} AS builder + +# Redeclare ARG after FROM +ARG TARGETARCH + WORKDIR /usr/src -RUN rustup target add x86_64-unknown-linux-musl # Copy manifests first for dependency caching -COPY Cargo.toml Cargo.lock ./ +COPY Cargo.toml Cargo.lock* ./ COPY agent/Cargo.toml agent/ COPY diff/Cargo.toml diff/ COPY hive/Cargo.toml hive/ @@ -57,13 +49,16 @@ RUN mkdir -p agent/src && echo 'fn main() {}' > agent/src/main.rs && touch agent mkdir -p hive-hq/api/src && echo 'fn main() {}' > hive-hq/api/src/main.rs && touch hive-hq/api/src/lib.rs && \ mkdir -p storage/src && touch storage/src/lib.rs -ENV OPENSSL_DIR=/musl - -# Pre-build dependencies with cache mounts -RUN --mount=type=cache,target=/usr/local/cargo/registry,id=cargo-registry \ - --mount=type=cache,target=/usr/local/cargo/git,id=cargo-git \ - --mount=type=cache,target=/usr/src/target,id=beecd-target \ - cargo build --release --target x86_64-unknown-linux-musl -p api 2>/dev/null || true +# Pre-build dependencies with architecture-specific cache mounts +RUN --mount=type=cache,target=/root/.cargo/registry,id=cargo-registry-${TARGETARCH} \ + --mount=type=cache,target=/root/.cargo/git,id=cargo-git-${TARGETARCH} \ + --mount=type=cache,target=/usr/src/target,id=beecd-target-${TARGETARCH} \ + if [ "$TARGETARCH" = "arm64" ]; then \ + RUST_TARGET="aarch64-unknown-linux-musl"; \ + else \ + RUST_TARGET="x86_64-unknown-linux-musl"; \ + fi && \ + cargo build --release --target $RUST_TARGET -p api 2>/dev/null || true # Copy real source files COPY hive-hq/api/src ./hive-hq/api/src @@ -74,11 +69,20 @@ COPY hive/proto ./hive/proto ARG BUILD_VERSION ENV BUILD_VERSION=${BUILD_VERSION} -RUN cargo build --release --target x86_64-unknown-linux-musl -p api && \ - cp /usr/src/target/x86_64-unknown-linux-musl/release/api /usr/local/bin/hivehq +RUN if [ "$TARGETARCH" = "arm64" ]; then \ + RUST_TARGET="aarch64-unknown-linux-musl"; \ + else \ + RUST_TARGET="x86_64-unknown-linux-musl"; \ + fi && \ + cargo build --release --target $RUST_TARGET -p api && \ + cp /usr/src/target/$RUST_TARGET/release/api /usr/local/bin/hivehq # Runtime image FROM scratch + +ARG IMAGE_SOURCE=https://github.com/galleybytes/beecd +LABEL org.opencontainers.image.source=${IMAGE_SOURCE} + COPY --from=builder /etc/ssl/certs /etc/ssl/certs COPY --from=builder /usr/local/bin/hivehq /usr/local/bin/hivehq COPY --from=ui-builder /ui/dist dist diff --git a/hive-hq/api/Cargo.lock b/hive-hq/api/Cargo.lock new file mode 100644 index 0000000..d0998c9 --- /dev/null +++ b/hive-hq/api/Cargo.lock @@ -0,0 +1,2332 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[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 = "async-compression" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" +dependencies = [ + "brotli", + "flate2", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-trait" +version = "0.1.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-write-file" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcdbedc2236483ab103a53415653d6b4442ea6141baf1ffa85df29635e88436" +dependencies = [ + "nix", + "rand", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "jobserver", + "libc", +] + +[[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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41daef31d7a747c5c847246f36de49ced6f7403b4cdabc807a97b5cc184cda7a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets 0.52.0", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hive-api" +version = "0.1.0" +dependencies = [ + "axum", + "chrono", + "serde", + "serde_json", + "sqlx", + "tokio", + "tower", + "tower-http", + "tracing", + "tracing-subscriber", + "uuid", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ce4ef31cda248bbdb6e6820603b82dfcd9e833db65a43e997a0ccec777d11fe" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "iri-string" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21859b667d66a4c1dacd9df0863b3efb65785474255face87f5bca39dd8407c0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nix" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[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-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +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.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.195" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "serde_json" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba03c279da73694ef99763320dea58b51095dfe87d001b1d4b5fe78ba8763cf" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d84b0a3c3739e220d94b3239fd69fb1f74bc36e16643423bd99de3b43c21bfbd" +dependencies = [ + "ahash", + "atoi", + "byteorder", + "bytes", + "chrono", + "crc", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashlink", + "hex", + "indexmap", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlformat", + "thiserror", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89961c00dc4d7dffb7aee214964b065072bff69e36ddb9e2c107541f75e4f2a5" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0bd4519486723648186a08785143599760f7cc81c52334a55d6a83ea1e20841" +dependencies = [ + "atomic-write-file", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn 1.0.109", + "tempfile", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37195395df71fd068f6e2082247891bc11e3289624bbc776a0cdfa1ca7f1ea4" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.2", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6ac0ac3b7ccd10cc96c7ab29791a7dd236bd94021f31eec7ba3d46a74aa1c24" +dependencies = [ + "atoi", + "base64", + "bitflags 2.4.2", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "210976b7d948c7ba9fced8ca835b11cbb2d677c59c79de41ac0d397e14547490" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", + "uuid", +] + +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da193277a4e2c33e59e09b5861580c33dd0a637c3883d0fa74ba40c0374af2e" +dependencies = [ + "async-compression", + "base64", + "bitflags 2.4.2", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "iri-string", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "uuid", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[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.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[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-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", + "serde", +] + +[[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 = "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.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" + +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.48", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" + +[[package]] +name = "zstd" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffb3309596d527cfcba7dfc6ed6052f1d39dfbd7c867aa2e865e4a449c10110" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43747c7422e2924c11144d5229878b98180ef8b06cca4ab5af37afc8a8d8ea3e" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/hive-hq/api/Cargo.toml b/hive-hq/api/Cargo.toml index 20b04af..52583fa 100644 --- a/hive-hq/api/Cargo.toml +++ b/hive-hq/api/Cargo.toml @@ -40,3 +40,6 @@ regex.workspace = true types = { workspace = true, features = ["api"] } hmac = "0.12" hex = "0.4" +aes-gcm = "0.10" +hkdf = "0.12" +getrandom = "0.2" diff --git a/hive-hq/api/src/auth_tests.rs b/hive-hq/api/src/auth_tests.rs index b97b44f..ed48df1 100644 --- a/hive-hq/api/src/auth_tests.rs +++ b/hive-hq/api/src/auth_tests.rs @@ -17,6 +17,7 @@ mod tests { email: "test@example.com".to_string(), exp: expiration.as_secs() as usize, roles: vec!["admin".to_string(), "aversion".to_string()], + tenant_id: String::new(), }; assert_eq!(claim.roles.len(), 2); @@ -34,6 +35,7 @@ mod tests { email: "admin@galleybytes.com".to_string(), exp: expiration.as_secs() as usize, roles: vec!["admin".to_string()], + tenant_id: String::new(), }; let token = encode( @@ -68,6 +70,7 @@ mod tests { email: "aversion@galleybytes.com".to_string(), exp: expiration.as_secs() as usize, roles: vec!["aversion".to_string()], + tenant_id: String::new(), }; let token = encode( @@ -103,6 +106,7 @@ mod tests { "aversion".to_string(), "operator".to_string(), ], + tenant_id: String::new(), }; let token = encode( diff --git a/hive-hq/api/src/handler.rs b/hive-hq/api/src/handler.rs index 843f14e..c72636a 100644 --- a/hive-hq/api/src/handler.rs +++ b/hive-hq/api/src/handler.rs @@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use sha2::Sha256; use std::collections::HashMap; +use std::sync::OnceLock; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tracing::error; use types::*; @@ -25,6 +26,215 @@ use utoipa::ToSchema; use uuid::Uuid; +/// Base domain for subdomain extraction (e.g., "beecd.example.com") +/// When set, subdomains like "tenant1.beecd.example.com" will extract "tenant1" as the slug. +/// When unset, falls back to extracting the first label (before the first dot). +static BASE_DOMAIN: OnceLock> = OnceLock::new(); + +fn get_base_domain() -> &'static Option { + BASE_DOMAIN.get_or_init(|| { + std::env::var("BASE_DOMAIN").ok().map(|d| { + let d = d.trim().to_lowercase(); + // Remove leading dot if present + if d.starts_with('.') { + d[1..].to_string() + } else { + d + } + }) + }) +} + +/// Extract subdomain slug from a host string given a base domain. +/// If base_domain is provided (e.g., "beecd.example.com"), strips it from the host +/// to get the subdomain. Otherwise, takes the first label before the first dot. +fn extract_subdomain_slug_with_base(host: &str, base_domain: Option<&str>) -> Option { + // Remove port if present + let host_no_port = host.split(':').next().unwrap_or(host).to_lowercase(); + + if let Some(base) = base_domain { + let base = base.to_lowercase(); + // If host ends with .base_domain, extract the prefix + if host_no_port.ends_with(&format!(".{}", base)) { + let prefix_len = host_no_port.len() - base.len() - 1; // -1 for the dot + let prefix = &host_no_port[..prefix_len]; + // Get the rightmost label of the prefix (closest to base domain) + let slug = prefix.rsplit('.').next().unwrap_or(prefix); + if !slug.is_empty() { + return Some(slug.to_string()); + } + } + // If host equals base_domain exactly, no subdomain + if host_no_port == base { + return None; + } + } + + // Fallback: first label before the first dot + let slug = host_no_port.split('.').next().unwrap_or(""); + if slug.is_empty() || slug == "localhost" { + None + } else { + Some(slug.to_string()) + } +} + +/// Extract subdomain slug from a host string using the global BASE_DOMAIN. +fn extract_subdomain_slug(host: &str) -> Option { + extract_subdomain_slug_with_base(host, get_base_domain().as_deref()) +} + +/// Extract tenant_id from Host header +/// Looks up the domain in the tenants table and returns the tenant_id +async fn extract_tenant_from_request( + pool: &sqlx::Pool, + headers: &axum::http::HeaderMap, +) -> Result { + // Prefer X-Forwarded-Host when behind dev proxy; fall back to Host. + // If those are unusable, try Origin then Referer to recover the tenant subdomain. + let xf_host_name = axum::http::HeaderName::from_static("x-forwarded-host"); + let host = headers + .get(&xf_host_name) + .and_then(|h| h.to_str().ok()) + .map(|s| s.split(',').next().unwrap_or(s).trim().to_string()) + .or_else(|| { + headers + .get(axum::http::header::HOST) + .and_then(|h| h.to_str().ok()) + .map(|s| s.to_string()) + }) + .unwrap_or_else(|| "localhost".to_string()); + + // Try to extract slug from host using BASE_DOMAIN + let mut slug = extract_subdomain_slug(&host); + + tracing::debug!( + "extract_tenant_from_request: host={}, slug={:?}, base_domain={:?}", + host, + slug, + get_base_domain() + ); + + // If no slug found, try Origin and Referer headers as fallback + if slug.is_none() { + let fallback_host = headers + .get(axum::http::header::ORIGIN) + .and_then(|h| h.to_str().ok()) + .map(|origin_val| { + origin_val + .split("//") + .nth(1) + .map(|s| s.split('/').next().unwrap_or(s)) + .unwrap_or(origin_val) + .to_string() + }) + .or_else(|| { + headers + .get(axum::http::header::REFERER) + .and_then(|h| h.to_str().ok()) + .map(|referer_val| { + referer_val + .split("//") + .nth(1) + .map(|s| s.split('/').next().unwrap_or(s)) + .unwrap_or(referer_val) + .to_string() + }) + }); + + if let Some(fb_host) = fallback_host { + slug = extract_subdomain_slug(&fb_host); + tracing::debug!( + "extract_tenant_from_request: fallback host={}, slug={:?}", + fb_host, + slug + ); + } + } + + let slug = slug.ok_or_else(|| { + tracing::warn!("No tenant subdomain found in host: {}", host); + ( + StatusCode::NOT_FOUND, + "No tenant subdomain found".to_string(), + ) + })?; + + // Look up tenant by slug (domain column stores just the slug) + let result = sqlx::query_scalar::<_, Uuid>( + r#" + SELECT id FROM tenants + WHERE domain = $1 AND status = 'active' AND deleted_at IS NULL + "#, + ) + .bind(&slug) + .fetch_optional(pool) + .await + .map_err(|e| { + tracing::error!("Failed to lookup tenant by slug {}: {:?}", slug, e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to resolve tenant".to_string(), + ) + })?; + + result.ok_or_else(|| { + tracing::warn!("Tenant not found for slug: {}", slug); + ( + StatusCode::NOT_FOUND, + format!("Tenant not found for subdomain: {}", slug), + ) + }) +} + +/// Set the RLS context for the current request +/// This sets app.tenant_id which is used by RLS policies +async fn set_tenant_context( + client: &mut sqlx::Transaction<'_, sqlx::Postgres>, + tenant_id: Uuid, +) -> Result<(), (StatusCode, String)> { + let query = format!("SET LOCAL app.tenant_id = '{}';", tenant_id); + sqlx::query(&query) + .execute(&mut **client) + .await + .map_err(|e| { + tracing::error!("Failed to set tenant context: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to set tenant context".to_string(), + ) + })?; + Ok(()) +} + +/// Get tenant domain from tenant_id for logging +async fn get_tenant_domain(pool: &sqlx::Pool, tenant_id: Uuid) -> String { + sqlx::query_scalar::<_, String>("SELECT domain FROM tenants WHERE id = $1") + .bind(tenant_id) + .fetch_optional(pool) + .await + .ok() + .flatten() + .unwrap_or_else(|| tenant_id.to_string()) +} + +/// Get a read-only transaction with tenant context set for RLS +/// This ensures all read queries respect tenant isolation +/// Returns (transaction, tenant_id, tenant_domain) for logging +async fn get_tenant_tx<'a>( + pool: &'a sqlx::Pool, + headers: &axum::http::HeaderMap, +) -> Result<(sqlx::Transaction<'a, sqlx::Postgres>, Uuid, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(pool, headers).await?; + let tenant_domain = get_tenant_domain(pool, tenant_id).await; + let mut tx = pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "get_tenant_tx_begin"))?; + set_tenant_context(&mut tx, tenant_id).await?; + Ok((tx, tenant_id, tenant_domain)) +} + /// Sanitize database errors to prevent information leakage /// Logs the full error server-side but returns generic message to client fn sanitize_db_error(e: sqlx::Error, context: &str) -> (StatusCode, String) { @@ -218,6 +428,64 @@ impl Pagination { } } +/// Crypto module for secret encryption/decryption +mod crypto { + use aes_gcm::aead::{Aead, KeyInit}; + use aes_gcm::{Aes256Gcm, Nonce}; + + const NONCE_SIZE: usize = 12; + + /// Encrypt plaintext using AES-256-GCM + pub fn encrypt(key: &[u8; 32], plaintext: &[u8]) -> Result<(Vec, Vec), String> { + if key.len() != 32 { + return Err("Key must be 32 bytes".to_string()); + } + + let cipher = Aes256Gcm::new(key.into()); + let mut nonce_bytes = [0u8; NONCE_SIZE]; + // In production, use a proper random source + getrandom::getrandom(&mut nonce_bytes) + .map_err(|e| format!("Failed to generate nonce: {}", e))?; + let nonce = Nonce::from_slice(&nonce_bytes); + + let ciphertext = cipher + .encrypt(nonce, plaintext) + .map_err(|e| format!("Encryption failed: {}", e))?; + + Ok((ciphertext, nonce_bytes.to_vec())) + } + + /// Decrypt ciphertext using AES-256-GCM + pub fn decrypt(key: &[u8; 32], ciphertext: &[u8], iv: &[u8]) -> Result, String> { + if key.len() != 32 { + return Err("Key must be 32 bytes".to_string()); + } + + if iv.len() != NONCE_SIZE { + return Err(format!("IV must be {} bytes", NONCE_SIZE)); + } + + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(iv); + + cipher + .decrypt(nonce, ciphertext) + .map_err(|e| format!("Decryption failed: {}", e)) + } + + /// Derive a key using HKDF + pub fn derive_key(root_key: &[u8], salt: &[u8]) -> Result<[u8; 32], String> { + use hkdf::Hkdf; + use sha2::Sha256; + + let hkdf = Hkdf::::new(Some(salt), root_key); + let mut key = [0u8; 32]; + hkdf.expand(b"secret-encryption", &mut key) + .map_err(|e| format!("HKDF expansion failed: {}", e))?; + Ok(key) + } +} + /// Get beecd-hive-hq version #[utoipa::path( get, @@ -232,6 +500,34 @@ pub async fn version( Ok((StatusCode::OK, state.version)) } +#[derive(Debug, Serialize, ToSchema)] +pub struct AppConfigResponse { + /// API version + pub version: String, + /// Base domain for tenant subdomains (e.g., "beecd.example.com") + /// When set, tenant URLs are constructed as "{slug}.{base_domain}" + #[serde(skip_serializing_if = "Option::is_none")] + pub base_domain: Option, +} + +/// Get public application configuration +/// +/// Returns non-sensitive configuration values needed by the UI, +/// including version and base domain for subdomain construction. +#[utoipa::path( + get, + path = "/api/config", + responses( + (status = 200, description = "Returns public app configuration", body = AppConfigResponse), + ) +)] +pub async fn get_app_config(State(state): State) -> Json { + Json(AppConfigResponse { + version: state.version.clone(), + base_domain: get_base_domain().clone(), + }) +} + #[derive(Debug, Serialize, ToSchema)] pub struct ClusterDefaultsResponse { /// Default Hive gRPC address in host:port form (no scheme) @@ -245,7 +541,8 @@ pub struct ClusterDefaultsResponse { /// Get default values for cluster creation/manifest generation. /// /// Values come from environment variables configured on the API: -/// - HIVE_DEFAULT_GRPC_SERVER (may be scheme or host:port) +/// - HIVE_DEFAULT_GRPC_SERVER (may include scheme or be host:port) +/// - HIVE_DEFAULT_GRPC_TLS (explicit override: "true" or "false") /// - AGENT_DEFAULT_IMAGE #[utoipa::path( get, @@ -260,7 +557,34 @@ pub struct ClusterDefaultsResponse { pub async fn get_cluster_defaults( State(state): State, ) -> Result, (StatusCode, String)> { - let (grpc_address, grpc_tls) = match state.hive_default_grpc_server.clone() { + let (grpc_address, grpc_tls) = parse_grpc_server( + state.hive_default_grpc_server.as_deref(), + state.hive_default_grpc_tls, + ); + + Ok(Json(ClusterDefaultsResponse { + grpc_address, + grpc_tls, + agent_image: state.agent_default_image.clone(), + })) +} + +/// Parse a gRPC server string into (address, tls) tuple. +/// +/// The input may be: +/// - A full URL with scheme: `https://grpc.example.com:443` or `http://grpc.example.com:5180` +/// - A host:port without scheme: `grpc.example.com:443` +/// +/// The `explicit_tls` parameter overrides TLS detection from the scheme. +/// If not provided, TLS is inferred from the scheme (https = true, http = false), +/// or defaults to false for host:port without scheme. +/// +/// Returns (grpc_address as host:port, tls setting) +fn parse_grpc_server( + raw: Option<&str>, + explicit_tls: Option, +) -> (Option, Option) { + match raw { None => (None, None), Some(raw) => { let trimmed = raw.trim().to_string(); @@ -271,24 +595,227 @@ pub async fn get_cluster_defaults( if rest.is_empty() { (None, None) } else { - let tls = scheme.eq_ignore_ascii_case("https"); + // Explicit TLS takes precedence over scheme + let tls = explicit_tls.unwrap_or_else(|| scheme.eq_ignore_ascii_case("https")); (Some(rest), Some(tls)) } } else { - // No scheme provided; default to plaintext for in-cluster addresses. + // No scheme provided; use explicit TLS setting or default to false for in-cluster ( Some(trimmed.split('/').next().unwrap_or("").to_string()), - Some(false), + Some(explicit_tls.unwrap_or(false)), ) } } - }; + } +} - Ok(Json(ClusterDefaultsResponse { - grpc_address, - grpc_tls, - agent_image: state.agent_default_image.clone(), - })) +#[cfg(test)] +mod grpc_address_tests { + use super::parse_grpc_server; + + // ========================================================================= + // HTTPS scheme tests + // ========================================================================= + + #[test] + fn https_url_with_port_infers_tls_true() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:5180"), None); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn https_url_standard_port_infers_tls_true() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443"), None); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn https_url_explicit_tls_false_overrides_scheme() { + // User explicitly sets TLS=false even with https:// scheme + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:5180"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn https_url_explicit_tls_true_confirms_scheme() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + // ========================================================================= + // HTTP scheme tests + // ========================================================================= + + #[test] + fn http_url_with_port_infers_tls_false() { + let (addr, tls) = parse_grpc_server(Some("http://grpc.example.com:5180"), None); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn http_url_explicit_tls_true_overrides_scheme() { + // User explicitly sets TLS=true even with http:// scheme (edge case) + let (addr, tls) = parse_grpc_server(Some("http://grpc.example.com:443"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + // ========================================================================= + // No scheme (host:port only) tests + // ========================================================================= + + #[test] + fn hostport_only_defaults_tls_false() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:443"), None); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn hostport_only_explicit_tls_true() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:5180"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn hostport_only_explicit_tls_false() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:5180"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn hostport_port_443_explicit_tls_true() { + // Port 443 is commonly TLS, but we need explicit setting without scheme + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:443"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + // ========================================================================= + // Edge cases + // ========================================================================= + + #[test] + fn none_input_returns_none() { + let (addr, tls) = parse_grpc_server(None, None); + assert_eq!(addr, None); + assert_eq!(tls, None); + } + + #[test] + fn empty_string_returns_none() { + let (addr, tls) = parse_grpc_server(Some(""), None); + assert_eq!(addr, None); + assert_eq!(tls, None); + } + + #[test] + fn whitespace_only_returns_none() { + let (addr, tls) = parse_grpc_server(Some(" "), None); + assert_eq!(addr, None); + assert_eq!(tls, None); + } + + #[test] + fn url_with_trailing_path_strips_path() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443/path"), None); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn url_with_trailing_slash_strips_slash() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443/"), None); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn scheme_only_no_host_returns_none() { + let (addr, tls) = parse_grpc_server(Some("https://"), None); + assert_eq!(addr, None); + assert_eq!(tls, None); + } + + #[test] + fn leading_trailing_whitespace_trimmed() { + let (addr, tls) = parse_grpc_server(Some(" https://grpc.example.com:443 "), None); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + // ========================================================================= + // User's test matrix: tls=true explicit override + // ========================================================================= + + #[test] + fn matrix_tls_true_https_url_5180() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:5180"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn matrix_tls_true_https_url_443() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn matrix_tls_true_hostport_443() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:443"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(true)); + } + + #[test] + fn matrix_tls_true_hostport_5180() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:5180"), Some(true)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(true)); + } + + // ========================================================================= + // User's test matrix: tls=false explicit override + // ========================================================================= + + #[test] + fn matrix_tls_false_https_url_5180() { + // Even with https:// scheme, explicit tls=false overrides + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:5180"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn matrix_tls_false_https_url_443() { + let (addr, tls) = parse_grpc_server(Some("https://grpc.example.com:443"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn matrix_tls_false_hostport_443() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:443"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:443".to_string())); + assert_eq!(tls, Some(false)); + } + + #[test] + fn matrix_tls_false_hostport_5180() { + let (addr, tls) = parse_grpc_server(Some("grpc.example.com:5180"), Some(false)); + assert_eq!(addr, Some("grpc.example.com:5180".to_string())); + assert_eq!(tls, Some(false)); + } } #[cfg(feature = "dev-mode")] @@ -298,6 +825,7 @@ pub async fn free_token( let token = generate_jwt( &state.jwt_secret_bytes, String::from("user@galleybytes.com"), + String::from("00000000-0000-0000-0000-000000000000"), // dev default tenant vec![String::from("admin")], ) .map_err(|e| { @@ -329,13 +857,15 @@ pub async fn free_token( )] pub async fn get_service( State(state): State, + headers: axum::http::HeaderMap, Path(name): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, ServiceDefinitionData>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + + let result = sqlx::query_as::<_, ServiceDefinitionData>( + r#" SELECT service_definitions.id AS service_definition_id, service_definitions.name AS name, @@ -359,14 +889,19 @@ pub async fn get_service( ORDER BY service_definitions.name LIMIT $2 OFFSET $3 "#, - ) - .bind(&name) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(&name) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_service"))?; + + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_service"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_service_commit"))?; + + Ok(Json(result)) } /// Get service data via id @@ -384,13 +919,15 @@ pub async fn get_service( )] pub async fn get_service_definition( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, (StatusCode, String)> { // TODO in the ui, make use of the deleted_at timestamp to inform the // user that this resource, while visible, can not be used while it is deleted. - Ok(Json( - sqlx::query_as::<_, ServiceDefinitionData>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + + let result = sqlx::query_as::<_, ServiceDefinitionData>( + r#" SELECT service_definitions.id AS service_definition_id, service_definitions.name AS name, @@ -411,13 +948,18 @@ pub async fn get_service_definition( WHERE service_definitions.id = $1 "#, - ) - .bind(id) - .fetch_one(&state.readonly_pool) + ) + .bind(id) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_service_definition"))?; + + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_service_definition"))?, - )) -} + .map_err(|e| sanitize_db_error(e, "get_service_definition_commit"))?; + + Ok(Json(result)) +} #[derive(Serialize, Deserialize, ToSchema)] pub struct PutServiceData { @@ -439,9 +981,20 @@ pub struct PutServiceData { )] pub async fn put_service_definition( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_service_definition_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -454,9 +1007,14 @@ pub async fn put_service_definition( ) .bind(id) .bind(&data.source_branch_requirements) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_service_definition"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_service_definition_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -479,12 +1037,14 @@ pub async fn put_service_definition( )] pub async fn get_unassociated_service_definitions_for_cluster_group( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, ServiceDefinitionData>( + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + + let result = sqlx::query_as::<_, ServiceDefinitionData>( r#" SELECT service_definitions.id AS service_definition_id, @@ -528,10 +1088,15 @@ pub async fn get_unassociated_service_definitions_for_cluster_group( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await - .map_err(|e| sanitize_db_error(e, "get_put_unassociated_service_definitions"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_put_unassociated_service_definitions"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_unassociated_service_definitions_commit"))?; + + Ok(Json(result)) } /// Get a list of all non-deleted clusters @@ -553,14 +1118,16 @@ pub async fn get_unassociated_service_definitions_for_cluster_group( )] pub async fn get_clusters( State(state): State, + headers: axum::http::HeaderMap, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; // Get total count let (total,): (i64,) = sqlx::query_as(r#"SELECT COUNT(*) FROM clusters WHERE deleted_at IS NULL"#) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_clusters_count"))?; @@ -570,10 +1137,14 @@ pub async fn get_clusters( ) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_clusters"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_clusters_commit"))?; + Ok(Json(PaginatedResponse::new( data, total, @@ -601,9 +1172,12 @@ pub async fn get_clusters( )] pub async fn get_error_count( State(state): State, + headers: axum::http::HeaderMap, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as(r#" SELECT clusters.id as cluster_id, @@ -628,9 +1202,14 @@ pub async fn get_error_count( "#) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_error_count"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_error_count_commit"))?; + Ok(Json(result)) } @@ -649,14 +1228,21 @@ pub async fn get_error_count( )] pub async fn get_cluster( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as("SELECT * FROM clusters WHERE id = $1 AND deleted_at IS NULL") .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_commit"))?; + Ok(Json(result)) } @@ -675,8 +1261,11 @@ pub async fn get_cluster( )] pub async fn delete_cluster( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + // Use transaction to ensure both operations succeed or fail together let mut tx = state .pool @@ -684,6 +1273,8 @@ pub async fn delete_cluster( .await .map_err(|e| sanitize_db_error(e, "delete_cluster_begin_transaction"))?; + set_tenant_context(&mut tx, tenant_id).await?; + // When "soft" deleting a cluster, also delete the user that allows // the agent to register, effectively preventing any new queries to this cluster sqlx::query("DELETE FROM users WHERE name = (SELECT name FROM clusters WHERE id = $1)") @@ -729,6 +1320,7 @@ pub async fn delete_cluster( )] pub async fn post_cluster( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result, (StatusCode, String)> { // Validate cluster name @@ -740,12 +1332,23 @@ pub async fn post_cluster( )); } + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + let tenant_domain = get_tenant_domain(&state.pool, tenant_id).await; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_cluster_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Check if cluster already exists let existing_cluster: Option = sqlx::query_as( "SELECT id, name, metadata, version, kubernetes_version FROM clusters WHERE name = $1 AND deleted_at IS NULL" ) .bind(name) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_cluster_check_existing"))?; @@ -755,13 +1358,14 @@ pub async fn post_cluster( // Insert the cluster sqlx::query_as( r#" - INSERT INTO clusters (id, name) - VALUES (gen_random_uuid(), $1) + INSERT INTO clusters (id, name, tenant_id) + VALUES (gen_random_uuid(), $1, $2) RETURNING id, name, metadata, version, kubernetes_version "#, ) .bind(name) - .fetch_one(&state.pool) + .bind(tenant_id) + .fetch_one(&mut *tx) .await .map_err(|e| match e { sqlx::Error::Database(database_error) => { @@ -784,7 +1388,11 @@ pub async fn post_cluster( } } None => { - tracing::error!("Database error inserting cluster: {}", database_error); + tracing::error!( + "[tenant:{}] Database error inserting cluster: {}", + tenant_domain, + database_error + ); ( StatusCode::INTERNAL_SERVER_ERROR, String::from("Database error while creating cluster"), @@ -793,7 +1401,11 @@ pub async fn post_cluster( } } _ => { - tracing::error!("Unknown error inserting cluster: {}", e); + tracing::error!( + "[tenant:{}] Unknown error inserting cluster: {}", + tenant_domain, + e + ); ( StatusCode::INTERNAL_SERVER_ERROR, String::from("Failed to create cluster"), @@ -806,7 +1418,7 @@ pub async fn post_cluster( let existing_user: Option<(Uuid,)> = sqlx::query_as("SELECT id FROM users WHERE name = $1 AND deleted_at IS NULL") .bind(name) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_cluster_check_user"))?; @@ -1123,24 +1735,29 @@ pub async fn post_cluster( sqlx::query("UPDATE users SET hash = $1, updated_at = NOW() WHERE name = $2") .bind(&hash) .bind(name) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_cluster_update_user"))?; } else { // Insert new user sqlx::query( r#" - INSERT INTO users (id, name, hash) - VALUES (gen_random_uuid(), $1, $2) + INSERT INTO users (id, name, hash, tenant_id) + VALUES (gen_random_uuid(), $1, $2, $3) "#, ) .bind(name) .bind(&hash) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_cluster_insert_user"))?; } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_cluster_commit"))?; + Ok(Json(types::PostClusterResponse { cluster, manifest: Some(manifest), @@ -1170,10 +1787,13 @@ pub async fn post_cluster( )] pub async fn get_cluster_namespaces( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as( r#" SELECT @@ -1197,10 +1817,14 @@ pub async fn get_cluster_namespaces( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_namespace_data"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_namespaces_commit"))?; + Ok(Json(result)) } @@ -1224,13 +1848,15 @@ pub async fn get_cluster_namespaces( )] pub async fn get_cluster_groups( State(state): State, + headers: axum::http::HeaderMap, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { let pagination = pagination.validate(); + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; // Get total count let (total,): (i64,) = sqlx::query_as(r#"SELECT COUNT(*) FROM cluster_groups"#) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_groups_count"))?; @@ -1239,10 +1865,14 @@ pub async fn get_cluster_groups( sqlx::query_as(r#"SELECT * FROM cluster_groups ORDER BY name LIMIT $1 OFFSET $2"#) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_groups"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_groups_commit"))?; + Ok(Json(PaginatedResponse::new( data, total, @@ -1267,9 +1897,11 @@ pub async fn get_cluster_groups( )] pub async fn get_cluster_cluster_groups( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let result = sqlx::query_as( r#" @@ -1289,10 +1921,13 @@ pub async fn get_cluster_cluster_groups( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_cluster_groups"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_cluster_groups_commit"))?; Ok(Json(result)) } @@ -1312,8 +1947,19 @@ pub async fn get_cluster_cluster_groups( )] pub async fn delete_group_relationship( State(state): State, + headers: axum::http::HeaderMap, Path((cluster_id, cluster_group_id)): Path<(Uuid, Uuid)>, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_group_relationship_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" DELETE FROM @@ -1325,10 +1971,14 @@ pub async fn delete_group_relationship( ) .bind(cluster_id) .bind(cluster_group_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_group_relationship"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_group_relationship_commit"))?; + tokio::time::sleep(std::time::Duration::from_millis( state.read_replica_wait_in_ms, )) @@ -1564,9 +2214,11 @@ pub async fn sync_cluster_releases( )] pub async fn get_cluster_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let result = sqlx::query_as( r#" @@ -1618,10 +2270,13 @@ pub async fn get_cluster_service_definitions( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_service_definitions"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_service_definitions_commit"))?; Ok(Json(result)) } @@ -1641,6 +2296,7 @@ pub async fn get_cluster_service_definitions( )] pub async fn add_cluster_groups( State(state): State, + headers: axum::http::HeaderMap, Json(cluster_group): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { if cluster_group.name.is_empty() { @@ -1649,22 +2305,33 @@ pub async fn add_cluster_groups( "Null value for cluster group".to_string(), )); } + + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "add_cluster_groups_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" - INSERT INTO cluster_groups (id, name) - VALUES ( - ( - SELECT gen_random_uuid() - ), - $1 - ); + INSERT INTO cluster_groups (id, name, tenant_id) + VALUES (gen_random_uuid(), $1, $2); "#, ) .bind(cluster_group.name) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "add_cluster_groups"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "add_cluster_groups_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -1683,8 +2350,19 @@ pub async fn add_cluster_groups( )] pub async fn delete_cluster_group( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_cluster_group_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + #[derive(sqlx::FromRow)] struct Cluster { id: Uuid, @@ -1703,16 +2381,20 @@ pub async fn delete_cluster_group( "#, ) .bind(id) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_cluster_group_fetch_clusters"))?; sqlx::query("DELETE FROM cluster_groups WHERE id = $1") .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_cluster_group_delete"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_cluster_group_commit"))?; + tokio::time::sleep(std::time::Duration::from_millis( state.read_replica_wait_in_ms, )) @@ -1742,15 +2424,19 @@ pub async fn delete_cluster_group( )] pub async fn get_cluster_group( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, (StatusCode, String)> { - Ok(Json( - sqlx::query_as("SELECT * FROM cluster_groups WHERE id = $1") - .bind(id) - .fetch_one(&state.readonly_pool) - .await - .map_err(|e| sanitize_db_error(e, "get_cluster_group"))?, - )) + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as("SELECT * FROM cluster_groups WHERE id = $1") + .bind(id) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_group"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_group_commit"))?; + Ok(Json(result)) } /// Update details of cluster group @@ -1768,9 +2454,20 @@ pub async fn get_cluster_group( )] pub async fn put_cluster_group( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_cluster_group_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -1785,7 +2482,7 @@ pub async fn put_cluster_group( .bind(id) .bind(&data.name) .bind(data.priority) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_cluster_group_update"))?; @@ -1807,14 +2504,13 @@ pub async fn put_cluster_group( "#, ) .bind(id) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_cluster_group_fetch_clusters"))?; - tokio::time::sleep(std::time::Duration::from_millis( - state.read_replica_wait_in_ms, - )) - .await; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_cluster_group_commit"))?; // Parallelize sync operations for all clusters let sync_futures = clusters @@ -1844,20 +2540,76 @@ pub async fn put_cluster_group( )] pub async fn get_resource_diffs_for_release( State(state): State, + headers: axum::http::HeaderMap, Path((release_id, diff_generation)): Path<(Uuid, i32)>, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - let diff_data_without_body = - list_resource_diffs(&state.readonly_pool, release_id, diff_generation) - .await - .map_err(|e| { - tracing::error!("get_resource_diffs_for_release error: {:?}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to fetch resource diffs".to_string(), - ) - })?; + + // Query resource diffs within the tenant transaction + let diff_data_without_body: Vec = if diff_generation == -1 { + sqlx::query_as::<_, DiffData>( + r#" + SELECT + resource_diffs.key, + resource_diffs.release_id, + resource_diffs.diff_generation, + resource_diffs.change_order, + resource_diffs.storage_url + FROM + resource_diffs + WHERE + resource_diffs.release_id = $1 + AND resource_diffs.diff_generation = ( + SELECT diff_generation FROM releases WHERE id = $1 + ) + LIMIT 100 + "#, + ) + .bind(release_id) + .fetch_all(&mut *tx) + .await + .map_err(|e| { + tracing::error!("get_resource_diffs_for_release error: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch resource diffs".to_string(), + ) + })? + } else { + sqlx::query_as::<_, DiffData>( + r#" + SELECT + resource_diffs.key, + resource_diffs.release_id, + resource_diffs.diff_generation, + resource_diffs.change_order, + resource_diffs.storage_url + FROM + resource_diffs + WHERE + resource_diffs.release_id = $1 + AND resource_diffs.diff_generation = $2 + LIMIT 100 + "#, + ) + .bind(release_id) + .bind(diff_generation) + .fetch_all(&mut *tx) + .await + .map_err(|e| { + tracing::error!("get_resource_diffs_for_release error: {:?}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to fetch resource diffs".to_string(), + ) + })? + }; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_resource_diffs_for_release_commit"))?; // Apply pagination to results let paginated_diffs: Vec<_> = diff_data_without_body @@ -1925,8 +2677,19 @@ pub async fn get_resource_diffs_for_release( )] pub async fn put_release_selection( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_release_selection_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE releases @@ -1952,7 +2715,7 @@ pub async fn put_release_selection( "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_release_manual_selection_update1"))?; @@ -1971,7 +2734,7 @@ pub async fn put_release_selection( "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_release_manual_selection_update2"))?; @@ -1992,10 +2755,14 @@ pub async fn put_release_selection( "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_release_manual_selection_update3"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_release_selection_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -2016,8 +2783,19 @@ pub async fn put_release_selection( )] pub async fn put_select_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(service_version_id): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_select_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Get the service version details let sv = sqlx::query_as::<_, (Uuid, Uuid, String, String, String)>( r#" @@ -2032,7 +2810,7 @@ pub async fn put_select_service_version( "#, ) .bind(service_version_id) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))? .ok_or_else(|| { @@ -2049,7 +2827,7 @@ pub async fn put_select_service_version( r#"SELECT repo_branch_id, name FROM service_definitions WHERE id = $1"#, ) .bind(service_def_id) - .fetch_one(&state.pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))?; @@ -2062,7 +2840,7 @@ pub async fn put_select_service_version( ) .bind(namespace_id) .bind(service_version_id) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))?; @@ -2085,7 +2863,7 @@ pub async fn put_select_service_version( .bind(namespace_id) .bind(&name) .bind(existing_release_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))?; } else { @@ -2102,7 +2880,7 @@ pub async fn put_select_service_version( ) .bind(namespace_id) .bind(&name) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))?; @@ -2110,8 +2888,8 @@ pub async fn put_select_service_version( sqlx::query( r#" INSERT INTO releases - (id, service_id, namespace_id, name, version, git_sha, path, repo_branch_id, hash, manually_selected_at) - VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $4, NOW()) + (id, service_id, namespace_id, name, version, git_sha, path, repo_branch_id, hash, manually_selected_at, tenant_id) + VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $4, NOW(), $8) "#, ) .bind(service_version_id) @@ -2121,11 +2899,16 @@ pub async fn put_select_service_version( .bind(&git_sha) .bind(&path) .bind(repo_branch_id) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_select_service_version"))?; } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_select_service_version_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -2148,8 +2931,19 @@ pub async fn put_select_service_version( )] pub async fn put_restore_latest_release( State(state): State, + headers: axum::http::HeaderMap, Path((id, release_name)): Path<(Uuid, String)>, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_restore_latest_release_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + match sqlx::query( r#" UPDATE releases @@ -2164,7 +2958,7 @@ pub async fn put_restore_latest_release( ) .bind(id) .bind(&release_name) - .execute(&state.pool) + .execute(&mut *tx) .await { Ok(pg_query_result) => { @@ -2198,7 +2992,7 @@ pub async fn put_restore_latest_release( ) .bind(id) .bind(&release_name) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await { Ok(row) => row.path, @@ -2229,7 +3023,7 @@ pub async fn put_restore_latest_release( ) .bind(id) .bind(&release_name) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await{ Ok(row) => row.repo_branch_id, Err(e) => { return Err(sanitize_db_error(e, "put_restore_latest_release_find_repo_branch"));} @@ -2247,7 +3041,8 @@ pub async fn put_restore_latest_release( repo_branch_id, version, git_sha, - hash + hash, + tenant_id ) VALUES ( @@ -2259,17 +3054,23 @@ pub async fn put_restore_latest_release( $4, '-', '', - '' + '', + $5 )"#, ) .bind(id) .bind(&path) .bind(&release_name) .bind(repo_branch_id) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_restore_latest_release_insert"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_restore_latest_release_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -2292,9 +3093,11 @@ pub async fn put_restore_latest_release( )] pub async fn get_release_status( State(state): State, + headers: axum::http::HeaderMap, Path(cluster_id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let result = sqlx::query_as::<_, ReleaseData>( r#" @@ -2345,7 +3148,7 @@ pub async fn get_release_status( .bind(cluster_id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_releases_via_id"))? .into_iter() @@ -2357,6 +3160,9 @@ pub async fn get_release_status( }) .collect::>(); + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_release_status_commit"))?; Ok(Json(result)) } @@ -2379,9 +3185,11 @@ pub async fn get_release_status( )] pub async fn get_namespace_releases( State(state): State, + headers: axum::http::HeaderMap, Path((id, release_name)): Path<(Uuid, String)>, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let mut releases = sqlx::query_as::<_, ReleaseData>( r#" @@ -2439,10 +3247,14 @@ pub async fn get_namespace_releases( .bind(&release_name) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_namespace_releases"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_namespace_releases_commit"))?; + // Compute paths from templates for release in &mut releases { compute_release_path(release); @@ -2474,10 +3286,12 @@ pub async fn get_namespace_releases( )] pub async fn get_release_service_versions( State(state): State, + headers: axum::http::HeaderMap, Path((namespace_id, release_name)): Path<(Uuid, String)>, Query(pagination): Query, Query(params): Query>, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let deployed_only = params .get("deployed_only") @@ -2496,7 +3310,7 @@ pub async fn get_release_service_versions( ) .bind(namespace_id) .bind(&release_name) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_release_service_versions"))? .ok_or_else(|| (StatusCode::NOT_FOUND, "Release not found".to_string()))?; @@ -2511,7 +3325,7 @@ pub async fn get_release_service_versions( ) .bind(namespace_id) .bind(&release_name) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_release_service_versions"))? .flatten(); @@ -2542,7 +3356,7 @@ pub async fn get_release_service_versions( let total = sqlx::query_scalar::<_, i64>(&count_str) .bind(service_def_id) .bind(namespace_id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_release_service_versions"))?; @@ -2585,10 +3399,14 @@ pub async fn get_release_service_versions( .bind(pagination.limit) .bind(current_service_id) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_release_service_versions"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_release_service_versions_commit"))?; + Ok(Json(PaginatedResponse::new( versions, total, @@ -2612,8 +3430,10 @@ pub async fn get_release_service_versions( )] pub async fn get_namespace_release_info( State(state): State, + headers: axum::http::HeaderMap, Path((id, release_name)): Path<(Uuid, String)>, ) -> Result, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let mut result = sqlx::query_as::<_, ReleaseData>( r#" SELECT @@ -2655,10 +3475,14 @@ pub async fn get_namespace_release_info( ) .bind(id) .bind(&release_name) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_namespace_release_info"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_namespace_release_info_commit"))?; + // Compute path from template compute_release_path(&mut result); @@ -2685,11 +3509,12 @@ pub async fn get_namespace_release_info( )] pub async fn get_hive_agent_errors( State(state): State, + headers: axum::http::HeaderMap, Path(cluster_id): Path, ) -> Result>, (StatusCode, String)> { - Ok(Json( - sqlx::query_as::<_, HiveError>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as::<_, HiveError>( + r#" SELECT message, updated_at @@ -2700,12 +3525,15 @@ pub async fn get_hive_agent_errors( AND deprecated_at IS NULL ORDER BY updated_at DESC LIMIT 10 "#, - ) - .bind(cluster_id) - .fetch_all(&state.readonly_pool) + ) + .bind(cluster_id) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_hive_agent_errors"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_hive_agent_errors"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_hive_agent_errors_commit"))?; + Ok(Json(result)) } /// Gets the last heartbeat information for an agent given the cluster id @@ -2723,11 +3551,12 @@ pub async fn get_hive_agent_errors( )] pub async fn get_hive_agent_heartbeat( State(state): State, + headers: axum::http::HeaderMap, Path(cluster_id): Path, ) -> Result, (StatusCode, String)> { - Ok(Json( - sqlx::query_as::<_, Heartbeat>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as::<_, Heartbeat>( + r#" SELECT last_check_in_at, deleted_at @@ -2736,12 +3565,15 @@ pub async fn get_hive_agent_heartbeat( WHERE id = $1 "#, - ) - .bind(cluster_id) - .fetch_one(&state.readonly_pool) + ) + .bind(cluster_id) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_heartbeat"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_heartbeat"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_hive_agent_heartbeat_commit"))?; + Ok(Json(result)) } /// Gets a list of pending all pending releases @@ -2759,10 +3591,11 @@ pub async fn get_hive_agent_heartbeat( )] pub async fn get_pending_releases( State(state): State, + headers: axum::http::HeaderMap, ) -> Result>, (StatusCode, String)> { - Ok(Json( - sqlx::query_as::<_, PendingReleases>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as::<_, PendingReleases>( + r#" SELECT clusters.id AS cluster_id, STRING_AGG(releases.name, ', ') AS release_names, @@ -2777,11 +3610,14 @@ pub async fn get_pending_releases( WHERE clusters.deleted_at IS NULL GROUP BY clusters.id "#, - ) - .fetch_all(&state.readonly_pool) + ) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_pending_releases_without_pagination"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_pending_releases_without_pagination"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_pending_releases_commit"))?; + Ok(Json(result)) } /// Gets a list of the latest errors produced by a specific release @@ -2803,13 +3639,14 @@ pub async fn get_pending_releases( )] pub async fn get_release_errors( State(state): State, + headers: axum::http::HeaderMap, Path(cluster_id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, HiveError>( - r#" + let result = sqlx::query_as::<_, HiveError>( + r#" SELECT message, updated_at @@ -2821,14 +3658,17 @@ pub async fn get_release_errors( ORDER BY updated_at DESC LIMIT $2 OFFSET $3 "#, - ) - .bind(cluster_id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(cluster_id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_release_errors"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_release_errors"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_release_errors_commit"))?; + Ok(Json(result)) } /// Update a list of releases for approval @@ -2847,6 +3687,7 @@ pub async fn get_release_errors( )] pub async fn put_approvals( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result>, (StatusCode, String)> { if is_empty_or_has_empty_string(&data.ids) { @@ -2855,6 +3696,16 @@ pub async fn put_approvals( "No releases subbmited for approval".to_string(), )) } else { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_approvals_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE releases @@ -2867,7 +3718,7 @@ pub async fn put_approvals( "#, ) .bind(&data.ids) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_approvals_update"))?; @@ -2885,6 +3736,10 @@ pub async fn put_approvals( }) .collect::, _>>()?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_approvals_commit"))?; + // Batch fetch all release candidates at once instead of N queries let all_release_candidates = list_mass_approval_release_candidates_batch(&state.readonly_pool, release_ids) @@ -2917,6 +3772,7 @@ pub async fn put_approvals( )] pub async fn put_unapprovals( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { if is_empty_or_has_empty_string(&data.ids) { @@ -2925,6 +3781,16 @@ pub async fn put_unapprovals( "No releases subbmited for unapproval".to_string(), )) } else { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_unapprovals_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE releases @@ -2938,10 +3804,14 @@ pub async fn put_unapprovals( "#, ) .bind(&data.ids) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_unapprovals"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_unapprovals_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } } @@ -2965,9 +3835,11 @@ pub async fn put_unapprovals( )] pub async fn get_cluster_group_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let result = sqlx::query_as( r#" @@ -3001,10 +3873,13 @@ pub async fn get_cluster_group_service_definitions( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_cluster_group_service_definitions"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_group_service_definitions_commit"))?; Ok(Json(result)) } @@ -3024,22 +3899,28 @@ pub async fn get_cluster_group_service_definitions( )] pub async fn delete_service_definition_relationship( State(state): State, + headers: axum::http::HeaderMap, Path((cluster_group_id, service_definition_id)): Path<(Uuid, Uuid)>, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_definition_relationship_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( "DELETE FROM service_definition_cluster_group_relationships WHERE cluster_group_id = $1 AND service_definition_id = $2", ) .bind(cluster_group_id) .bind(service_definition_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_service_definition_relationship"))?; - tokio::time::sleep(std::time::Duration::from_millis( - state.read_replica_wait_in_ms, - )) - .await; - #[derive(sqlx::FromRow)] struct Cluster { id: Uuid, @@ -3058,10 +3939,14 @@ pub async fn delete_service_definition_relationship( "#, ) .bind(cluster_group_id) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_fetch_clusters"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_definition_relationship_commit"))?; + // Parallelize sync operations for all clusters let sync_futures = clusters .into_iter() @@ -3087,8 +3972,19 @@ pub async fn delete_service_definition_relationship( )] pub async fn delete_service_from_namespace( State(state): State, + headers: axum::http::HeaderMap, Path((namespace_id, service_name)): Path<(String, String)>, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_from_namespace_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -3103,9 +3999,14 @@ pub async fn delete_service_from_namespace( ) .bind(namespace_id) .bind(service_name) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_service_from_namespace"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_from_namespace_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -3128,13 +4029,14 @@ pub async fn delete_service_from_namespace( )] pub async fn get_cluster_group_cluster_association( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as( - r#" + let result = sqlx::query_as( + r#" SELECT clusters.*, CASE @@ -3160,26 +4062,30 @@ pub async fn get_cluster_group_cluster_association( ORDER BY clusters.name LIMIT $2 OFFSET $3 "#, - ) - .bind(id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_cluster_group_cluster_association"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_cluster_group_cluster_association"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_cluster_group_cluster_association_commit"))?; + Ok(Json(result)) } fn generate_jwt( jwt_secret_bytes: &[u8], email: String, + tenant_id: String, roles: Vec, ) -> Result> { // Token expiration set to 2 hours for security // NOTE: Database has refresh_tokens table (see migrations/20241216000001_add_refresh_tokens.sql) // TODO: Implement refresh token flow to allow token renewal without re-authentication let expiration = match SystemTime::now().duration_since(UNIX_EPOCH) { - Ok(now) => now + Duration::from_secs(2 * 60 * 60), // 2 hours (reduced from 12 for security) + Ok(now) => now + Duration::from_secs(2 * 60 * 60), // 2 hours Err(e) => { let message = format!("Error generating JWT expiration date: {:?}", e); error!("{}", message); @@ -3189,6 +4095,7 @@ fn generate_jwt( let claims = Claim { email, + tenant_id, exp: expiration.as_secs() as usize, roles, }; @@ -3286,6 +4193,15 @@ async fn validate_auth_with_roles( .map_err(|_| StatusCode::UNAUTHORIZED)? .ok_or(StatusCode::UNAUTHORIZED)?; + // Enforce that the session tenant matches the domain being used + let request_tenant_id = extract_tenant_from_request(&state.pool, req.headers()) + .await + .map_err(|_| StatusCode::UNAUTHORIZED)?; + let user_tenant_id = user.tenant_id.ok_or(StatusCode::UNAUTHORIZED)?; + if user_tenant_id != request_tenant_id { + return Err(StatusCode::UNAUTHORIZED); + } + if let Some(required) = required_roles { let has_required_role = user .roles @@ -3330,6 +4246,10 @@ pub struct UiAuthMeResponse { pub id: Uuid, pub username: String, pub roles: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub tenant_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tenant_name: Option, } #[derive(Debug, Serialize, ToSchema)] @@ -3337,6 +4257,49 @@ pub struct UiAuthBootstrapStatusResponse { pub bootstrap_required: bool, } +#[derive(Debug, Deserialize, ToSchema)] +pub struct TenantRegisterRequest { + #[serde(alias = "email")] + pub username: String, + pub password: String, + pub tenant_name: String, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct TenantRegisterResponse { + pub tenant_id: Uuid, + pub domain: String, + pub user_id: Uuid, + pub email: String, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct TenantData { + pub id: Uuid, + pub domain: String, + pub name: String, + pub status: String, +} + +#[derive(Debug, Deserialize, ToSchema)] +pub struct CreateSecretRequest { + pub purpose: String, + pub plaintext: String, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct SecretMetadata { + pub id: Uuid, + pub purpose: String, + pub created_at: String, + pub key_version: i16, +} + +#[derive(Debug, Serialize, ToSchema)] +pub struct SecretListResponse { + pub secrets: Vec, +} + fn cookie_value(headers: &axum::http::HeaderMap, name: &str) -> Option { let raw = headers.get(axum::http::header::COOKIE)?; let s = std::str::from_utf8(raw.as_bytes()).ok()?; @@ -3356,18 +4319,28 @@ fn cookie_value(headers: &axum::http::HeaderMap, name: &str) -> Option { None } -fn build_session_cookie_value(token: &str, max_age_seconds: i64, secure: bool) -> String { +fn build_session_cookie_value( + token: &str, + max_age_seconds: i64, + secure: bool, + domain: Option<&str>, +) -> String { // SameSite Strict since you said same-site and no CORS. // Secure is optional for local http dev. + let domain_attr = domain + .filter(|d| !d.is_empty()) + .map(|d| format!("; Domain={}", d)) + .unwrap_or_default(); + if secure { format!( - "{}={}; Path=/; HttpOnly; SameSite=Strict; Max-Age={}; Secure", - UI_SESSION_COOKIE_NAME, token, max_age_seconds + "{}={}; Path=/; HttpOnly; SameSite=Strict; Max-Age={}; Secure{}", + UI_SESSION_COOKIE_NAME, token, max_age_seconds, domain_attr ) } else { format!( - "{}={}; Path=/; HttpOnly; SameSite=Strict; Max-Age={}", - UI_SESSION_COOKIE_NAME, token, max_age_seconds + "{}={}; Path=/; HttpOnly; SameSite=Strict; Max-Age={}{}", + UI_SESSION_COOKIE_NAME, token, max_age_seconds, domain_attr ) } } @@ -3411,43 +4384,25 @@ async fn lookup_ui_session( session_token: &str, ) -> Result, sqlx::Error> { let token_hash = util::hash_string(session_token); - let row = sqlx::query_as::<_, (Uuid, String, Vec)>( + + // Use SECURITY DEFINER function to bypass RLS for session lookup + let row = sqlx::query_as::<_, (Uuid, String, Vec, Option, Option)>( r#" - SELECT - u.id, - u.username, - u.roles - FROM - ui_sessions s - JOIN ui_users u ON u.id = s.user_id - WHERE - s.token_hash = $1 - AND s.revoked_at IS NULL - AND s.expires_at > NOW() - AND u.deleted_at IS NULL + SELECT user_id, username, roles, tenant_id, tenant_name + FROM auth_lookup_ui_session($1) "#, ) - .bind(token_hash) + .bind(&token_hash) .fetch_optional(pool) .await?; - if let Some((id, username, roles)) = row { - // Best-effort touch. Don't fail auth if this update fails. - let _ = sqlx::query( - r#" - UPDATE ui_sessions - SET last_seen_at = NOW() - WHERE token_hash = $1 - "#, - ) - .bind(util::hash_string(session_token)) - .execute(pool) - .await; - + if let Some((id, username, roles, tenant_id, tenant_name)) = row { Ok(Some(UiAuthMeResponse { id, username, roles, + tenant_id, + tenant_name, })) } else { Ok(None) @@ -3515,17 +4470,53 @@ pub async fn ui_auth_bootstrap( let roles: Vec = vec!["admin".to_string()]; + // Start a transaction for creating tenant, user, and session + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_begin"))?; + + // Create a bootstrap tenant (domain based on username or default) + let tenant_domain = format!( + "bootstrap-{}", + username + .split('@') + .next() + .unwrap_or("admin") + .chars() + .filter(|c| c.is_alphanumeric() || *c == '-') + .take(20) + .collect::() + ); + let tenant_id: Uuid = sqlx::query_scalar( + r#" + INSERT INTO tenants (id, domain, name, status, config) + VALUES (gen_random_uuid(), $1, 'Bootstrap Tenant', 'active', '{}') + RETURNING id + "#, + ) + .bind(&tenant_domain) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_tenant"))?; + + // Set tenant context for RLS + set_tenant_context(&mut tx, tenant_id).await?; + + // Create the first admin user let created = sqlx::query_as::<_, (Uuid,)>( r#" - INSERT INTO ui_users (id, username, password_hash, roles) - VALUES (gen_random_uuid(), $1, $2, $3) + INSERT INTO ui_users (id, tenant_id, username, password_hash, roles) + VALUES (gen_random_uuid(), $1, $2, $3, $4) RETURNING id "#, ) + .bind(tenant_id) .bind(username) .bind(password_hash) .bind(&roles) - .fetch_one(&state.pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_insert"))?; @@ -3539,20 +4530,21 @@ pub async fn ui_auth_bootstrap( })?; let session_hash = util::hash_string(&session_token); - sqlx::query( - r#" - INSERT INTO ui_sessions (id, user_id, token_hash, expires_at) - VALUES (gen_random_uuid(), $1, $2, $3) - "#, - ) - .bind(created.0) - .bind(session_hash) - .bind(expires_at) - .execute(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_session_insert"))?; + // Use SECURITY DEFINER function to bypass RLS (no tenant context yet) + sqlx::query("SELECT auth_create_ui_session($1, $2, $3, $4)") + .bind(&session_hash) + .bind(created.0) + .bind(tenant_id) + .bind(expires_at) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_session_insert"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_bootstrap_commit"))?; - let cookie = build_session_cookie_value(&session_token, ttl_seconds, ui_cookie_secure()); + let cookie = build_session_cookie_value(&session_token, ttl_seconds, ui_cookie_secure(), None); let header = ( axum::http::header::SET_COOKIE, axum::http::HeaderValue::from_str(&cookie).map_err(|_| { @@ -3570,6 +4562,8 @@ pub async fn ui_auth_bootstrap( id: created.0, username: username.to_string(), roles, + tenant_id: None, + tenant_name: None, }), )) } @@ -3614,6 +4608,7 @@ pub async fn ui_auth_bootstrap_status( )] pub async fn ui_auth_login( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result< ( @@ -3632,25 +4627,51 @@ pub async fn ui_auth_login( )); } - let row = sqlx::query_as::<_, (Uuid, String, String, Vec)>( + // Extract tenant from request first + // This ensures we look up the user within the correct tenant + let request_tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + // Use transaction with tenant context for RLS + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_login_begin"))?; + set_tenant_context(&mut tx, request_tenant_id).await?; + + // Query user by username AND tenant_id + // This prevents returning the wrong user when same username exists in multiple tenants + let row = sqlx::query_as::<_, (Uuid, String, String, Vec, Uuid)>( r#" - SELECT id, username, password_hash, roles + SELECT id, username, password_hash, roles, tenant_id FROM ui_users - WHERE deleted_at IS NULL AND username = $1 + WHERE deleted_at IS NULL AND username = $1 AND tenant_id = $2 "#, ) .bind(username) - .fetch_optional(&state.pool) + .bind(request_tenant_id) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "ui_auth_login_select"))?; - let Some((user_id, username_db, password_hash, roles)) = row else { + let Some((user_id, username_db, password_hash, roles, tenant_id)) = row else { return Err(( StatusCode::UNAUTHORIZED, String::from("Invalid username or password"), )); }; + // Fetch tenant name for response (tenants table has permissive RLS) + let tenant_name: Option = sqlx::query_scalar( + r#" + SELECT name FROM tenants WHERE id = $1 + "#, + ) + .bind(tenant_id) + .fetch_optional(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_login_tenant_select"))?; + let ok = bcrypt::verify(password.trim(), &password_hash).unwrap_or(false); if !ok { return Err(( @@ -3669,20 +4690,21 @@ pub async fn ui_auth_login( })?; let session_hash = util::hash_string(&session_token); - sqlx::query( - r#" - INSERT INTO ui_sessions (id, user_id, token_hash, expires_at) - VALUES (gen_random_uuid(), $1, $2, $3) - "#, - ) - .bind(user_id) - .bind(session_hash) - .bind(expires_at) - .execute(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "ui_auth_login_session_insert"))?; + // Use SECURITY DEFINER function to bypass RLS (no tenant context yet) + sqlx::query("SELECT auth_create_ui_session($1, $2, $3, $4)") + .bind(&session_hash) + .bind(user_id) + .bind(tenant_id) + .bind(expires_at) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_login_session_insert"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "ui_auth_login_commit"))?; - let cookie = build_session_cookie_value(&session_token, ttl_seconds, ui_cookie_secure()); + let cookie = build_session_cookie_value(&session_token, ttl_seconds, ui_cookie_secure(), None); let header = ( axum::http::header::SET_COOKIE, axum::http::HeaderValue::from_str(&cookie).map_err(|_| { @@ -3700,6 +4722,8 @@ pub async fn ui_auth_login( id: user_id, username: username_db, roles, + tenant_id: Some(tenant_id), + tenant_name, }), )) } @@ -3728,16 +4752,11 @@ pub async fn ui_auth_logout( > { if let Some(token) = cookie_value(req.headers(), UI_SESSION_COOKIE_NAME) { let token_hash = util::hash_string(&token); - let _ = sqlx::query( - r#" - UPDATE ui_sessions - SET revoked_at = NOW() - WHERE token_hash = $1 AND revoked_at IS NULL - "#, - ) - .bind(token_hash) - .execute(&state.pool) - .await; + // Use SECURITY DEFINER function to bypass RLS + let _ = sqlx::query("SELECT auth_revoke_ui_session($1)") + .bind(&token_hash) + .execute(&state.pool) + .await; } let cookie = build_clear_session_cookie_value(ui_cookie_secure()); @@ -3781,6 +4800,800 @@ pub async fn ui_auth_me( Ok(Json(user)) } +/// Generate a domain slug from tenant name. +/// Rules: +/// - Only lowercase ASCII letters (a-z), digits (0-9), and hyphens (-) allowed +/// - No leading or trailing hyphens +/// - No consecutive hyphens +/// - Non-alphanumeric characters become hyphens (collapsed) +/// - Maximum 63 characters (DNS subdomain limit) +/// - Empty result if no valid characters +fn generate_domain_slug(name: &str) -> String { + let mut result = String::new(); + let mut last_was_hyphen = true; // Treat start as hyphen to prevent leading hyphen + + for c in name.chars() { + if result.len() >= 63 { + break; + } + + if c.is_ascii_lowercase() || c.is_ascii_digit() { + result.push(c); + last_was_hyphen = false; + } else if c.is_ascii_uppercase() { + result.push(c.to_ascii_lowercase()); + last_was_hyphen = false; + } else if !last_was_hyphen { + // Any non-alphanumeric character becomes a hyphen + result.push('-'); + last_was_hyphen = true; + } + // Skip consecutive non-alphanumeric characters + } + + // Remove trailing hyphen if present + while result.ends_with('-') { + result.pop(); + } + + result +} + +#[cfg(test)] +mod slug_tests { + use super::generate_domain_slug; + + #[test] + fn test_simple_lowercase() { + assert_eq!(generate_domain_slug("acme"), "acme"); + } + + #[test] + fn test_uppercase_to_lowercase() { + assert_eq!(generate_domain_slug("ACME"), "acme"); + assert_eq!(generate_domain_slug("AcMe"), "acme"); + } + + #[test] + fn test_spaces_become_hyphens() { + assert_eq!(generate_domain_slug("acme corp"), "acme-corp"); + assert_eq!(generate_domain_slug("my company name"), "my-company-name"); + } + + #[test] + fn test_underscores_become_hyphens() { + assert_eq!(generate_domain_slug("acme_corp"), "acme-corp"); + } + + #[test] + fn test_no_double_hyphens() { + assert_eq!(generate_domain_slug("acme--corp"), "acme-corp"); + assert_eq!(generate_domain_slug("acme---corp"), "acme-corp"); + assert_eq!(generate_domain_slug("acme - corp"), "acme-corp"); + assert_eq!(generate_domain_slug("acme corp"), "acme-corp"); + } + + #[test] + fn test_no_leading_hyphen() { + assert_eq!(generate_domain_slug("-acme"), "acme"); + assert_eq!(generate_domain_slug("--acme"), "acme"); + assert_eq!(generate_domain_slug(" acme"), "acme"); + } + + #[test] + fn test_no_trailing_hyphen() { + assert_eq!(generate_domain_slug("acme-"), "acme"); + assert_eq!(generate_domain_slug("acme--"), "acme"); + assert_eq!(generate_domain_slug("acme "), "acme"); + } + + #[test] + fn test_numbers_allowed() { + assert_eq!(generate_domain_slug("acme123"), "acme123"); + assert_eq!(generate_domain_slug("123acme"), "123acme"); + assert_eq!(generate_domain_slug("acme-123-corp"), "acme-123-corp"); + } + + #[test] + fn test_special_chars_become_hyphens() { + assert_eq!(generate_domain_slug("acme!@#$%corp"), "acme-corp"); + assert_eq!(generate_domain_slug("acme.corp"), "acme-corp"); + assert_eq!(generate_domain_slug("acme&corp"), "acme-corp"); + } + + #[test] + fn test_unicode_becomes_hyphens() { + assert_eq!(generate_domain_slug("acme\u{00e9}corp"), "acme-corp"); // e with accent + assert_eq!(generate_domain_slug("acme\u{4e2d}corp"), "acme-corp"); // Chinese char + assert_eq!(generate_domain_slug("\u{00fc}ber"), "ber"); // u with umlaut at start + } + + #[test] + fn test_empty_input() { + assert_eq!(generate_domain_slug(""), ""); + } + + #[test] + fn test_only_invalid_chars() { + assert_eq!(generate_domain_slug("!@#$%^"), ""); + assert_eq!(generate_domain_slug("---"), ""); + assert_eq!(generate_domain_slug(" "), ""); + } + + #[test] + fn test_mixed_valid_invalid() { + assert_eq!(generate_domain_slug(" --acme-- "), "acme"); + assert_eq!(generate_domain_slug("!!!acme!!!"), "acme"); + } + + #[test] + fn test_realistic_company_names() { + assert_eq!(generate_domain_slug("Acme Corporation"), "acme-corporation"); + assert_eq!(generate_domain_slug("Smith & Sons Ltd."), "smith-sons-ltd"); + assert_eq!(generate_domain_slug("O'Reilly Media"), "o-reilly-media"); + assert_eq!(generate_domain_slug("AT&T"), "at-t"); + assert_eq!(generate_domain_slug("3M Company"), "3m-company"); + } + + #[test] + fn test_max_length_63_chars() { + // Exactly 63 chars should be preserved + let input_63 = "a".repeat(63); + assert_eq!(generate_domain_slug(&input_63).len(), 63); + + // 64+ chars should be truncated to 63 + let input_100 = "a".repeat(100); + assert_eq!(generate_domain_slug(&input_100).len(), 63); + + // Long name with spaces should truncate correctly + let long_name = + "this is an extremely long company name that exceeds the dns subdomain limit"; + let slug = generate_domain_slug(long_name); + assert!(slug.len() <= 63); + assert!(!slug.ends_with('-')); + assert!(!slug.starts_with('-')); + } + + #[test] + fn test_truncation_removes_trailing_hyphen() { + // If truncation lands on a hyphen, it should be removed + // "abcdefghij" repeated 6 times = 60 chars, then " xy" would add "-xy" making 63 + // but if we have 62 chars + space, truncation at 63 would leave trailing hyphen + let input = "a".repeat(62) + " xyz"; + let slug = generate_domain_slug(&input); + assert!(slug.len() <= 63); + assert!(!slug.ends_with('-')); + } +} + +#[cfg(test)] +mod subdomain_extraction_tests { + use super::extract_subdomain_slug_with_base; + + #[test] + fn test_with_base_domain_extracts_subdomain() { + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("tenant1.beecd.example.com", base), + Some("tenant1".to_string()) + ); + assert_eq!( + extract_subdomain_slug_with_base("acme-corp.beecd.example.com", base), + Some("acme-corp".to_string()) + ); + } + + #[test] + fn test_with_base_domain_handles_port() { + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("tenant1.beecd.example.com:8080", base), + Some("tenant1".to_string()) + ); + } + + #[test] + fn test_with_base_domain_nested_subdomain() { + // If someone has extra levels like "www.tenant1.beecd.example.com", + // we extract the rightmost prefix label (closest to base domain) + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("www.tenant1.beecd.example.com", base), + Some("tenant1".to_string()) + ); + } + + #[test] + fn test_with_base_domain_exact_match_returns_none() { + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("beecd.example.com", base), + None + ); + } + + #[test] + fn test_with_base_domain_case_insensitive() { + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("TENANT1.BEECD.EXAMPLE.COM", base), + Some("tenant1".to_string()) + ); + } + + #[test] + fn test_without_base_domain_uses_first_label() { + assert_eq!( + extract_subdomain_slug_with_base("tenant1.beecd.example.com", None), + Some("tenant1".to_string()) + ); + assert_eq!( + extract_subdomain_slug_with_base("beecd.example.com", None), + Some("beecd".to_string()) // This is the problematic case BASE_DOMAIN fixes + ); + } + + #[test] + fn test_without_base_domain_localhost_returns_none() { + assert_eq!(extract_subdomain_slug_with_base("localhost", None), None); + assert_eq!( + extract_subdomain_slug_with_base("localhost:3000", None), + None + ); + } + + #[test] + fn test_different_domain_with_base_falls_back() { + // If the host doesn't match the base domain, fall back to first label + let base = Some("beecd.example.com"); + assert_eq!( + extract_subdomain_slug_with_base("tenant1.other.com", base), + Some("tenant1".to_string()) + ); + } +} + +/// Register a new tenant and create first admin user +#[utoipa::path( + post, + path = "/api/tenants/register", + request_body = TenantRegisterRequest, + responses( + (status = 201, description = "Tenant and user created successfully", body = TenantRegisterResponse), + (status = 400, description = "Invalid request data"), + (status = 409, description = "Email or domain already exists"), + (status = 500, description = "Database or server error"), + ) +)] +pub async fn register_tenant( + State(state): State, + headers: axum::http::HeaderMap, + Json(data): Json, +) -> Result< + ( + StatusCode, + [(axum::http::HeaderName, axum::http::HeaderValue); 1], + Json, + ), + (StatusCode, String), +> { + let email = data.username.trim().to_lowercase(); + let password = data.password.trim(); + let tenant_name = data.tenant_name.trim(); + + // Validation + if email.is_empty() || !email.contains('@') { + return Err((StatusCode::BAD_REQUEST, "Invalid email".to_string())); + } + if password.len() < 8 { + return Err(( + StatusCode::BAD_REQUEST, + "Password must be at least 8 characters".to_string(), + )); + } + if tenant_name.is_empty() || tenant_name.len() > 255 { + return Err(( + StatusCode::BAD_REQUEST, + "Tenant name must be between 1 and 255 characters".to_string(), + )); + } + + // Generate domain slug from tenant name + let domain_slug = generate_domain_slug(tenant_name); + if domain_slug.is_empty() { + return Err(( + StatusCode::BAD_REQUEST, + "Tenant name must contain alphanumeric characters".to_string(), + )); + } + + // Store just the slug in domain column (e.g., 'acme' not 'acme.beecd.local') + // This prevents collisions and works with any host variant + let domain = domain_slug.clone(); + + let password_hash = util::bcrypt_string(password).map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to hash password".to_string(), + ) + })?; + + // Start transaction: create tenant, then user + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "register_tenant_begin_tx"))?; + + // Create tenant + let tenant_id: Uuid = sqlx::query_scalar( + r#" + INSERT INTO tenants (id, domain, name, status, config) + VALUES (gen_random_uuid(), $1, $2, 'active', '{}') + ON CONFLICT (domain) DO NOTHING + RETURNING id + "#, + ) + .bind(&domain) + .bind(tenant_name) + .fetch_optional(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "register_tenant_insert_tenant"))? + .ok_or_else(|| (StatusCode::CONFLICT, "Domain already exists".to_string()))?; + + tracing::info!("Created tenant with id: {}", tenant_id); + + // Set tenant context for RLS - required before inserting tenant-scoped data + set_tenant_context(&mut tx, tenant_id).await?; + tracing::info!("Set tenant context for RLS"); + + // Create user in the new tenant + let user_id: Uuid = sqlx::query_scalar( + r#" + INSERT INTO ui_users (id, tenant_id, username, password_hash, roles) + VALUES (gen_random_uuid(), $1, $2, $3, ARRAY['admin']::text[]) + RETURNING id + "#, + ) + .bind(tenant_id) + .bind(&email) + .bind(&password_hash) + .fetch_one(&mut *tx) + .await + .map_err(|e| { + tracing::error!("Failed to insert user: {:?}", e); + // Check for unique constraint violation + if let sqlx::Error::Database(ref db_err) = e { + if db_err.is_unique_violation() { + return ( + StatusCode::CONFLICT, + "Email already registered in this tenant".to_string(), + ); + } + } + sanitize_db_error(e, "register_tenant_insert_user") + })?; + + tracing::info!("Created user with id: {}", user_id); + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "register_tenant_commit_tx"))?; + + // Create session for the new user + let ttl_seconds = ui_session_ttl_seconds(); + let expires_at = Utc::now() + chrono::Duration::seconds(ttl_seconds); + let session_token = util::generate_secure_token_256().map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to generate session token".to_string(), + ) + })?; + let session_hash = util::hash_string(&session_token); + + // Use SECURITY DEFINER function to bypass RLS (tenant just created) + sqlx::query("SELECT auth_create_ui_session($1, $2, $3, $4)") + .bind(&session_hash) + .bind(user_id) + .bind(tenant_id) + .bind(expires_at) + .execute(&state.pool) + .await + .map_err(|e| sanitize_db_error(e, "register_tenant_session_insert"))?; + + // Share cookie across subdomains so redirect lands authenticated + // Use the Host header to extract the parent domain (not the stored domain which may differ .local vs .localhost) + let host = headers + .get(axum::http::header::HOST) + .and_then(|h| h.to_str().ok()) + .unwrap_or("localhost"); + let request_domain = host.split(':').next().unwrap_or(host); + let cookie_domain = request_domain + .split_once('.') + .map(|(_, rest)| format!(".{}", rest)) + .unwrap_or_default(); + + let cookie = build_session_cookie_value( + &session_token, + ttl_seconds, + ui_cookie_secure(), + if cookie_domain.is_empty() { + None + } else { + Some(&cookie_domain) + }, + ); + let header = ( + axum::http::header::SET_COOKIE, + axum::http::HeaderValue::from_str(&cookie).map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Failed to set session cookie".to_string(), + ) + })?, + ); + + Ok(( + StatusCode::CREATED, + [header], + Json(TenantRegisterResponse { + tenant_id, + domain, + user_id, + email, + }), + )) +} + +/// Store a secret (GitHub token, PGP key, etc.) encrypted in the database +#[utoipa::path( + post, + path = "/api/secrets", + security( + ("bearerAuth"=[]), + ), + request_body = CreateSecretRequest, + responses( + (status = 201, description = "Secret created successfully"), + (status = 400, description = "Invalid request"), + (status = 401, description = "Not authenticated"), + (status = 409, description = "Secret purpose already exists for this tenant"), + (status = 500, description = "Database or encryption error"), + ) +)] +pub async fn create_secret( + State(state): State, + headers: axum::http::HeaderMap, + Json(data): Json, +) -> Result<(StatusCode, Json), (StatusCode, String)> { + // Get current user and tenant + let Some(token) = headers + .get(axum::http::header::COOKIE) + .and_then(|h| h.to_str().ok()) + .and_then(|s| { + for part in s.split(';') { + let part = part.trim(); + if let Some(v) = part.strip_prefix(format!("{}=", UI_SESSION_COOKIE_NAME).as_str()) + { + return Some(v.to_string()); + } + } + None + }) + else { + return Err((StatusCode::UNAUTHORIZED, "Not authenticated".to_string())); + }; + + let _user = lookup_ui_session(&state.pool, &token) + .await + .map_err(|e| sanitize_db_error(e, "create_secret_lookup_user"))? + .ok_or_else(|| (StatusCode::UNAUTHORIZED, "Not authenticated".to_string()))?; + + // Get bootstrap key from environment + let bootstrap_key_str = std::env::var("HIVE_CRYPTO_ROOT_KEY").map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Bootstrap key not configured".to_string(), + ) + })?; + + let bootstrap_key_bytes = base64::engine::general_purpose::STANDARD + .decode(&bootstrap_key_str) + .map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Invalid bootstrap key format".to_string(), + ) + })?; + + if bootstrap_key_bytes.len() != 32 { + return Err(( + StatusCode::INTERNAL_SERVER_ERROR, + "Bootstrap key must be 32 bytes".to_string(), + )); + } + + let mut bootstrap_key = [0u8; 32]; + bootstrap_key.copy_from_slice(&bootstrap_key_bytes); + + // Validate inputs + if data.purpose.is_empty() || data.plaintext.is_empty() { + return Err(( + StatusCode::BAD_REQUEST, + "purpose and plaintext are required".to_string(), + )); + } + + // Resolve tenant from Host header + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + // Encrypt the secret + let (ciphertext, iv) = + crypto::encrypt(&bootstrap_key, data.plaintext.as_bytes()).map_err(|e| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Encryption failed: {}", e), + ) + })?; + + // Persist to database with upsert + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "create_secret_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + + let row = sqlx::query_as::<_, (Uuid, chrono::DateTime, i16)>( + r#" + INSERT INTO tenant_secrets (tenant_id, purpose, ciphertext, iv, key_version) + VALUES ($1, $2, $3, $4, 1) + ON CONFLICT (tenant_id, purpose) WHERE deleted_at IS NULL + DO UPDATE SET + ciphertext = EXCLUDED.ciphertext, + iv = EXCLUDED.iv, + updated_at = NOW() + RETURNING id, created_at, key_version + "#, + ) + .bind(tenant_id) + .bind(&data.purpose) + .bind(&ciphertext) + .bind(&iv) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "create_secret_upsert"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "create_secret_commit"))?; + + Ok(( + StatusCode::CREATED, + Json(SecretMetadata { + id: row.0, + purpose: data.purpose, + created_at: row.1.to_rfc3339(), + key_version: row.2, + }), + )) +} + +/// List secrets (metadata only, no plaintext decryption) +#[utoipa::path( + get, + path = "/api/secrets", + security( + ("bearerAuth"=[]), + ), + responses( + (status = 200, description = "List of secrets", body = SecretListResponse), + (status = 401, description = "Not authenticated"), + (status = 500, description = "Database error"), + ) +)] +pub async fn list_secrets( + State(state): State, + headers: axum::http::HeaderMap, +) -> Result, (StatusCode, String)> { + let Some(_token) = headers + .get(axum::http::header::COOKIE) + .and_then(|h| h.to_str().ok()) + .and_then(|s| { + for part in s.split(';') { + let part = part.trim(); + if let Some(v) = part.strip_prefix(format!("{}=", UI_SESSION_COOKIE_NAME).as_str()) + { + return Some(v.to_string()); + } + } + None + }) + else { + return Err((StatusCode::UNAUTHORIZED, "Not authenticated".to_string())); + }; + + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "list_secrets_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + + let rows = sqlx::query_as::<_, (Uuid, String, chrono::DateTime, i16)>( + r#" + SELECT id, purpose, created_at, key_version + FROM tenant_secrets + WHERE tenant_id = $1 AND deleted_at IS NULL + ORDER BY created_at DESC + "#, + ) + .bind(tenant_id) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "list_secrets_query"))?; + + let secrets: Vec = rows + .into_iter() + .map(|(id, purpose, created_at, key_version)| SecretMetadata { + id, + purpose, + created_at: created_at.to_rfc3339(), + key_version, + }) + .collect(); + + Ok(Json(SecretListResponse { secrets })) +} + +/// Delete a secret (soft delete) +#[utoipa::path( + delete, + path = "/api/secrets/{purpose}", + security( + ("bearerAuth"=[]), + ), + responses( + (status = 204, description = "Secret deleted"), + (status = 401, description = "Not authenticated"), + (status = 404, description = "Secret not found"), + (status = 500, description = "Database error"), + ) +)] +pub async fn delete_secret( + State(state): State, + headers: axum::http::HeaderMap, + axum::extract::Path(purpose): axum::extract::Path, +) -> Result { + let Some(_token) = headers + .get(axum::http::header::COOKIE) + .and_then(|h| h.to_str().ok()) + .and_then(|s| { + for part in s.split(';') { + let part = part.trim(); + if let Some(v) = part.strip_prefix(format!("{}=", UI_SESSION_COOKIE_NAME).as_str()) + { + return Some(v.to_string()); + } + } + None + }) + else { + return Err((StatusCode::UNAUTHORIZED, "Not authenticated".to_string())); + }; + + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_secret_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + + let result = sqlx::query( + r#" + UPDATE tenant_secrets + SET deleted_at = NOW(), updated_at = NOW() + WHERE tenant_id = $1 AND purpose = $2 AND deleted_at IS NULL + "#, + ) + .bind(tenant_id) + .bind(&purpose) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "delete_secret_update"))?; + + if result.rows_affected() == 0 { + return Err((StatusCode::NOT_FOUND, "Secret not found".to_string())); + } + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_secret_commit"))?; + + Ok(StatusCode::NO_CONTENT) +} + +/// Retrieve a secret's encrypted ciphertext for agent-side decryption +/// This endpoint supports the optional Phase 5 agent-side secret passing pattern +/// The agent can optionally decrypt secrets locally using its own key material +#[utoipa::path( + get, + path = "/api/secrets/{purpose}/encrypted", + security( + ("bearerAuth"=[]), + ), + responses( + (status = 200, description = "Encrypted secret data"), + (status = 401, description = "Not authenticated"), + (status = 404, description = "Secret not found"), + (status = 500, description = "Database error"), + ) +)] +pub async fn get_encrypted_secret( + State(state): State, + headers: axum::http::HeaderMap, + axum::extract::Path(purpose): axum::extract::Path, +) -> Result, (StatusCode, String)> { + let Some(_token) = headers + .get(axum::http::header::COOKIE) + .and_then(|h| h.to_str().ok()) + .and_then(|s| { + for part in s.split(';') { + let part = part.trim(); + if let Some(v) = part.strip_prefix(format!("{}=", UI_SESSION_COOKIE_NAME).as_str()) + { + return Some(v.to_string()); + } + } + None + }) + else { + return Err((StatusCode::UNAUTHORIZED, "Not authenticated".to_string())); + }; + + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "get_encrypted_secret_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + + let row = sqlx::query_as::<_, (Vec, Vec, i16)>( + r#" + SELECT ciphertext, iv, key_version + FROM tenant_secrets + WHERE tenant_id = $1 AND purpose = $2 AND deleted_at IS NULL + "#, + ) + .bind(tenant_id) + .bind(&purpose) + .fetch_optional(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_encrypted_secret_query"))?; + + let (ciphertext, iv, key_version) = + row.ok_or_else(|| (StatusCode::NOT_FOUND, "Secret not found".to_string()))?; + + // Return encrypted data as base64 for safe transport + let result = json!({ + "ciphertext": base64::engine::general_purpose::STANDARD.encode(&ciphertext), + "iv": base64::engine::general_purpose::STANDARD.encode(&iv), + "key_version": key_version, + }); + + Ok(Json(result)) +} + /// Add a new namespace to a cluster via id #[utoipa::path( post, @@ -3797,6 +5610,7 @@ pub async fn ui_auth_me( )] pub async fn post_create_cluster_namespaces( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { @@ -3806,18 +5620,34 @@ pub async fn post_create_cluster_namespaces( "Null value in namespace entry".to_string(), )) } else { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_create_cluster_namespaces_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" - INSERT INTO namespaces (id, cluster_id, name) - SELECT gen_random_uuid(), $1, unnest($2::text[]) - ON CONFLICT DO NOTHING + INSERT INTO namespaces (id, cluster_id, name, tenant_id) + SELECT gen_random_uuid(), $1, unnest($2::text[]), $3 + ON CONFLICT (tenant_id, cluster_id, name) DO NOTHING "#, ) .bind(id) .bind(data.namespace_names) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_namespace"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_create_cluster_namespaces_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } } @@ -3838,9 +5668,20 @@ pub async fn post_create_cluster_namespaces( )] pub async fn post_subscribe_clusters( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Validation make sure that no 'service @ repo' of proposed cluster is a dedup of service @ different repo #[derive(sqlx::FromRow)] struct ClusterGroup { @@ -3853,7 +5694,7 @@ pub async fn post_subscribe_clusters( "#, ) .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_fetch_group"))?; @@ -3902,7 +5743,7 @@ pub async fn post_subscribe_clusters( .bind(id) .bind(cluster_id) .bind(priority) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_validate"))?; @@ -3922,29 +5763,25 @@ pub async fn post_subscribe_clusters( } } - let list = data - .ids - .iter() - .map(|s| format!("('{}', '{}')", id, s)) - .collect::>(); - sqlx::query(&format!( - " - INSERT INTO group_relationships - (cluster_group_id, cluster_id) - VALUES - {} - ON CONFLICT DO NOTHING - ", - list.join(",") - )) - .execute(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_insert"))?; + for cluster_id in data.ids.iter() { + sqlx::query( + r#" + INSERT INTO group_relationships (tenant_id, cluster_group_id, cluster_id) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING + "#, + ) + .bind(tenant_id) + .bind(id) + .bind(cluster_id) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_insert"))?; + } - tokio::time::sleep(std::time::Duration::from_millis( - state.read_replica_wait_in_ms, - )) - .await; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_clusters_commit"))?; // Parallelize sync operations for all clusters let sync_futures = data @@ -3972,9 +5809,20 @@ pub async fn post_subscribe_clusters( )] pub async fn post_subscribe_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + #[derive(sqlx::FromRow)] struct ClusterGroup { priority: Option, @@ -3986,7 +5834,7 @@ pub async fn post_subscribe_service_definitions( "#, ) .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_fetch_group"))?; @@ -4030,7 +5878,7 @@ pub async fn post_subscribe_service_definitions( .bind(id) .bind(service_definition_id) .bind(priority) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_validate"))?; @@ -4048,7 +5896,7 @@ pub async fn post_subscribe_service_definitions( "#, ) .bind(service_definition_id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map(|r| r.name) .unwrap_or_else(|_| service_definition_id.to_string()); @@ -4064,28 +5912,21 @@ pub async fn post_subscribe_service_definitions( } } - let list = data - .ids - .into_iter() - .map(|s| format!("('{}', '{}')", id, s)) - .collect::>(); - sqlx::query(&format!( - " - INSERT INTO service_definition_cluster_group_relationships - (cluster_group_id, service_definition_id) - VALUES - {} - ", - list.join(",") - )) - .execute(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_insert"))?; - - tokio::time::sleep(std::time::Duration::from_millis( - state.read_replica_wait_in_ms, - )) - .await; + for service_definition_id in data.ids.iter() { + sqlx::query( + r#" + INSERT INTO service_definition_cluster_group_relationships (tenant_id, cluster_group_id, service_definition_id) + VALUES ($1, $2, $3) + ON CONFLICT DO NOTHING + "#, + ) + .bind(tenant_id) + .bind(id) + .bind(service_definition_id) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_insert"))?; + } #[derive(sqlx::FromRow)] struct Cluster { @@ -4105,10 +5946,14 @@ pub async fn post_subscribe_service_definitions( "#, ) .bind(id) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_fetch_clusters"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_subscribe_service_definitions_commit"))?; + // Parallelize sync operations for all clusters let sync_futures = clusters .into_iter() @@ -4134,9 +5979,20 @@ pub async fn post_subscribe_service_definitions( )] pub async fn put_subscribe_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Validation make sure that no registered clusters of this group have the same service already associated via another group #[derive(sqlx::FromRow)] struct ClusterGroup { @@ -4149,7 +6005,7 @@ pub async fn put_subscribe_service_definitions( "#, ) .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_fetch_group"))?; @@ -4193,7 +6049,7 @@ pub async fn put_subscribe_service_definitions( .bind(id) .bind(service_definition_id) .bind(priority) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_validate"))?; @@ -4233,7 +6089,7 @@ pub async fn put_subscribe_service_definitions( ) .bind(id) .bind(service_definition_id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_fetch_old"))?; @@ -4242,33 +6098,25 @@ pub async fn put_subscribe_service_definitions( ) .bind(id) .bind(old_service_definition.id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_delete"))?; } - let list = data - .ids - .into_iter() - .map(|s| format!("('{}', '{}')", id, s)) - .collect::>(); - sqlx::query(&format!( - " - INSERT INTO service_definition_cluster_group_relationships - (cluster_group_id, service_definition_id) - VALUES - {} - ", - list.join(",") - )) - .execute(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "post_global_repo_service_insert"))?; - - tokio::time::sleep(std::time::Duration::from_millis( - state.read_replica_wait_in_ms, - )) - .await; + for service_definition_id in data.ids.iter() { + sqlx::query( + r#" + INSERT INTO service_definition_cluster_group_relationships (tenant_id, cluster_group_id, service_definition_id) + VALUES ($1, $2, $3) + "#, + ) + .bind(tenant_id) + .bind(id) + .bind(service_definition_id) + .execute(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_insert"))?; + } #[derive(sqlx::FromRow)] struct Cluster { @@ -4288,10 +6136,14 @@ pub async fn put_subscribe_service_definitions( "#, ) .bind(id) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_fetch_clusters"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_subscribe_service_definitions_commit"))?; + // Parallelize sync operations for all clusters let sync_futures = clusters .into_iter() @@ -4320,14 +6172,16 @@ pub async fn put_subscribe_service_definitions( )] pub async fn get_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); // Get total count let (total,): (i64,) = sqlx::query_as(r#"SELECT COUNT(*) FROM service_definitions WHERE deleted_at IS NULL"#) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_service_definitions_count"))?; @@ -4361,10 +6215,14 @@ pub async fn get_service_definitions( ) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_service_definitions"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_service_definitions_commit"))?; + Ok(Json(PaginatedResponse::new( data, total, @@ -4392,9 +6250,11 @@ pub async fn get_service_definitions( )] pub async fn get_service_releases( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let releases = sqlx::query_as::<_, ReleaseData>( r#" @@ -4446,10 +6306,14 @@ pub async fn get_service_releases( .bind(id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_service_releases"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_service_releases_commit"))?; + Ok(Json( releases .into_iter() @@ -4477,8 +6341,11 @@ pub async fn get_service_releases( )] pub async fn post_repo( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result, (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + let parsed = parse_repo_url(&data.url).ok_or(( StatusCode::UNPROCESSABLE_ENTITY, "Invalid repo URL. Expected like https://// or git@:/.git" @@ -4514,11 +6381,19 @@ pub async fn post_repo( let web_base_url = data.web_base_url.clone().unwrap_or(parsed.web_base_url); let api_base_url = data.api_base_url.clone().unwrap_or(parsed.api_base_url); + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_repo_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let ci_upsert = sqlx::query_as::<_, RepoData>( r#" - INSERT INTO repos (id, org, repo, provider, host, web_base_url, api_base_url) - VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6) - ON CONFLICT ON CONSTRAINT unique_repo_identity_ci + INSERT INTO repos (id, org, repo, provider, host, web_base_url, api_base_url, tenant_id) + VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7) + ON CONFLICT ON CONSTRAINT unique_repo_identity_per_tenant DO UPDATE SET provider = EXCLUDED.provider, web_base_url = EXCLUDED.web_base_url, @@ -4531,45 +6406,17 @@ pub async fn post_repo( .bind(&provider) .bind(&parsed.host) .bind(&web_base_url) - .bind(&api_base_url); - - let repo = match ci_upsert.fetch_one(&state.pool).await { - Ok(repo) => repo, - Err(sqlx::Error::Database(db_err)) => { - // If the case-insensitive constraint isn't present (or migration is mid-flight), - // fall back to the older constraint so repo creation still works. - let pg_code = db_err.code().map(|c| c.to_string()); - if matches!(pg_code.as_deref(), Some("42704") | Some("42P10")) { - sqlx::query_as::<_, RepoData>( - r#" - INSERT INTO repos (id, org, repo, provider, host, web_base_url, api_base_url) - VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6) - ON CONFLICT ON CONSTRAINT unique_repo_identity - DO UPDATE SET - provider = EXCLUDED.provider, - web_base_url = EXCLUDED.web_base_url, - api_base_url = EXCLUDED.api_base_url - RETURNING id, provider, host, web_base_url, api_base_url, org, repo - "#, - ) - .bind(&parsed.org) - .bind(&parsed.repo) - .bind(&provider) - .bind(&parsed.host) - .bind(&web_base_url) - .bind(&api_base_url) - .fetch_one(&state.pool) - .await - .map_err(|e| sanitize_db_error(e, "post_repo_upsert_legacy"))? - } else { - return Err(sanitize_db_error( - sqlx::Error::Database(db_err), - "post_repo_upsert", - )); - } - } - Err(e) => return Err(sanitize_db_error(e, "post_repo_upsert")), - }; + .bind(&api_base_url) + .bind(tenant_id); + + let repo = ci_upsert + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "post_repo_upsert"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_repo_commit"))?; Ok(Json(repo)) } @@ -4593,13 +6440,15 @@ pub async fn post_repo( )] pub async fn get_repos( State(state): State, + headers: axum::http::HeaderMap, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); // Get total count let (total,): (i64,) = sqlx::query_as(r#"SELECT COUNT(*) FROM repos"#) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_repos_count"))?; @@ -4609,10 +6458,14 @@ pub async fn get_repos( ) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_repos"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_repos_commit"))?; + Ok(Json(PaginatedResponse::new( data, total, @@ -4636,19 +6489,23 @@ pub async fn get_repos( )] pub async fn get_repo( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, (StatusCode, String)> { - Ok(Json( - sqlx::query_as::<_, RepoData>( - r#" + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as::<_, RepoData>( + r#" SELECT id, provider, host, web_base_url, api_base_url, org, repo FROM repos WHERE id = $1 "#, - ) - .bind(id) - .fetch_one(&state.readonly_pool) + ) + .bind(id) + .fetch_one(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_repo"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_repo"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_repo_commit"))?; + Ok(Json(result)) } /// Gets branches for a specific repo via id @@ -4670,13 +6527,14 @@ pub async fn get_repo( )] pub async fn get_branches( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, RepoBranches>( - r#" + let result = sqlx::query_as::<_, RepoBranches>( + r#" SELECT repo_branches.id as id, repos.provider as provider, @@ -4696,14 +6554,17 @@ pub async fn get_branches( ORDER BY branch LIMIT $2 OFFSET $3 "#, - ) - .bind(id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_branches"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_branches"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_branches_commit"))?; + Ok(Json(result)) } /// Add a new branch to a specific repo via id @@ -4722,6 +6583,7 @@ pub async fn get_branches( )] pub async fn post_branch( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { @@ -4733,22 +6595,27 @@ pub async fn post_branch( )); } + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + let mut tx = state .pool .begin() .await .map_err(|e| sanitize_db_error(e, "post_branch_begin"))?; + set_tenant_context(&mut tx, tenant_id).await?; + // Create the branch and capture its ID so we can copy existing repo services onto it. let new_branch_id: Uuid = sqlx::query_scalar( r#" - INSERT INTO repo_branches (id, repo_id, branch) - VALUES (gen_random_uuid(), $1, $2) + INSERT INTO repo_branches (id, repo_id, branch, tenant_id) + VALUES (gen_random_uuid(), $1, $2, $3) RETURNING id "#, ) .bind(id) .bind(branch) + .bind(tenant_id) .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_branch_insert"))?; @@ -4783,7 +6650,8 @@ pub async fn post_branch( repo_branch_id, name, source_branch_requirements, - manifest_path_template + manifest_path_template, + tenant_id ) SELECT gen_random_uuid(), @@ -4796,14 +6664,16 @@ pub async fn post_branch( CASE WHEN es.manifest_template_distinct_count = 1 AND es.manifest_template_single IS NOT NULL THEN es.manifest_template_single ELSE '{cluster}/manifests/{namespace}/' || es.name || '/' || es.name || '.yaml' - END + END, + $3 FROM existing_services es - ON CONFLICT (repo_branch_id, name) DO NOTHING + ON CONFLICT (tenant_id, repo_branch_id, name) DO NOTHING "#, ) .bind(new_branch_id) .bind(id) + .bind(tenant_id) .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_branch_copy_union_services"))?; @@ -4834,13 +6704,14 @@ pub async fn post_branch( )] pub async fn get_branch_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, ServiceDefinitionData>( - r#" + let result = sqlx::query_as::<_, ServiceDefinitionData>( + r#" SELECT service_definitions.id AS service_definition_id, service_definitions.deleted_at AS service_deleted_at, @@ -4869,14 +6740,17 @@ pub async fn get_branch_service_definitions( ORDER BY service_definitions.name LIMIT $2 OFFSET $3 "#, - ) - .bind(id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_branch_service_definitions"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_branch_service_definitions"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_branch_service_definitions_commit"))?; + Ok(Json(result)) } /// Given a branch id, get a list other branches with "sync" configuration data @@ -4898,13 +6772,14 @@ pub async fn get_branch_service_definitions( )] pub async fn get_autosync_data( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, AutosyncData>( - r#" + let result = sqlx::query_as::<_, AutosyncData>( + r#" SELECT id, branch, @@ -4940,14 +6815,17 @@ pub async fn get_autosync_data( ORDER BY branch LIMIT $2 OFFSET $3; "#, - ) - .bind(id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_branch_service_definitions_fetch"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_branch_service_definitions_fetch"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_autosync_data_commit"))?; + Ok(Json(result)) } /// Update a branch to by synced with other branches @@ -4965,9 +6843,20 @@ pub async fn get_autosync_data( )] pub async fn put_branch_autosync( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "put_branch_autosync_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -4980,7 +6869,7 @@ pub async fn put_branch_autosync( ) .bind(id) .bind(&data.ids) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_branch_autosync_update"))?; @@ -5004,23 +6893,29 @@ pub async fn put_branch_autosync( ) ) INSERT INTO - service_definitions (id, repo_branch_id, name, deleted_at, manifest_path_template) + service_definitions (id, repo_branch_id, name, deleted_at, manifest_path_template, tenant_id) SELECT gen_random_uuid(), $1, service_names.name, -- Instead of preventing sync, add the resource as deleted service_names.deleted_at, - service_names.manifest_path_template + service_names.manifest_path_template, + $2 FROM service_names ON CONFLICT DO NOTHING "#, ) .bind(id) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "put_branch_autosync_insert"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "put_branch_autosync_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } @@ -5039,6 +6934,7 @@ pub async fn put_branch_autosync( )] pub async fn post_branch_service( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { @@ -5068,6 +6964,16 @@ pub async fn post_branch_service( // ``` // // For now, just re-enable the service that is synced with this branch + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_branch_service_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" WITH matching_repo_branches AS ( @@ -5080,15 +6986,16 @@ pub async fn post_branch_service( OR $1 = ANY(service_autosync) ) INSERT INTO - service_definitions (id, repo_branch_id, name, manifest_path_template) + service_definitions (id, repo_branch_id, name, manifest_path_template, tenant_id) SELECT gen_random_uuid(), matching_repo_branches.id, $2, - $3 + $3, + $4 FROM matching_repo_branches - ON CONFLICT (repo_branch_id, name) DO UPDATE SET + ON CONFLICT (tenant_id, repo_branch_id, name) DO UPDATE SET deleted_at = NULL "#, @@ -5099,9 +7006,14 @@ pub async fn post_branch_service( "{{cluster}}/manifests/{{namespace}}/{}/{}.yaml", &data.name, &data.name )) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_branch_service"))?; + + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_branch_service_commit"))?; Ok((StatusCode::NO_CONTENT, String::new())) } @@ -5124,13 +7036,14 @@ pub async fn post_branch_service( )] pub async fn get_repo_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, ServiceName>( - r#" + let result = sqlx::query_as::<_, ServiceName>( + r#" SELECT service_definitions.name, MIN(service_definitions.manifest_path_template) AS manifest_path_template @@ -5161,14 +7074,17 @@ pub async fn get_repo_service_definitions( ORDER BY service_definitions.name LIMIT $2 OFFSET $3; "#, - ) - .bind(id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_repo_service_definitions"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_repo_service_definitions"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_repo_service_definitions_commit"))?; + Ok(Json(result)) } #[derive(Serialize, Deserialize, sqlx::FromRow, ToSchema)] @@ -5193,17 +7109,22 @@ pub struct NamespaceData { )] pub async fn get_namespaces_via_cluster_name( State(state): State, + headers: axum::http::HeaderMap, Path(cluster_name): Path, ) -> Result, (StatusCode, String)> { - let result = sqlx::query_as::<_, NamespaceData>(r#"SELECT id, name FROM namespaces WHERE cluster_id = (SELECT id FROM clusters WHERE clusters.name = $1 AND clusters.deleted_at IS NULL)"#) + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; + let result = sqlx::query_as::<_, NamespaceData>(r#"SELECT id, name FROM namespaces WHERE cluster_id = (SELECT id FROM clusters WHERE clusters.name = $1 AND clusters.deleted_at IS NULL)"#) .bind(&cluster_name) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_namespaces_via_cluster_name"))? .into_iter() .map(|r| (r.name, r.id.to_string())) .collect::>(); + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_namespaces_via_cluster_name_commit"))?; Ok(Json(json!(result))) } @@ -5223,6 +7144,7 @@ pub async fn get_namespaces_via_cluster_name( )] pub async fn post_global_repo_service( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result<(StatusCode, String), (StatusCode, String)> { @@ -5252,30 +7174,46 @@ pub async fn post_global_repo_service( // ``` // // For now, just re-enable the service + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_global_repo_service_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" INSERT INTO - service_definitions (id, repo_branch_id, name, manifest_path_template) + service_definitions (id, repo_branch_id, name, manifest_path_template, tenant_id) SELECT GEN_RANDOM_UUID(), id, $2, - $3 + $3, + $4 FROM repo_branches WHERE repo_id = $1 - ON CONFLICT (repo_branch_id, name) DO UPDATE SET + ON CONFLICT (tenant_id, repo_branch_id, name) DO UPDATE SET deleted_at = NULL "#, ) .bind(id) .bind(&data.name) .bind(&template) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_branch_service_upsert"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_global_repo_service_commit"))?; + Ok((StatusCode::NO_CONTENT, String::new())) } } @@ -5284,7 +7222,14 @@ async fn insert_new_releases_to_namespace( db: &sqlx::Pool, namespace_id: &Uuid, service_definitions: &[ServiceDefinitionInfo], + tenant_id: Uuid, ) -> Result<(), Box> { + let mut tx = db.begin().await?; + + // Set tenant context for RLS + let query = format!("SET LOCAL app.tenant_id = '{}';", tenant_id); + sqlx::query(&query).execute(&mut *tx).await?; + let cluster_data = sqlx::query_as::<_, ClusterNamespaceServicesData>( r#" SELECT @@ -5305,7 +7250,7 @@ async fn insert_new_releases_to_namespace( "#, ) .bind(namespace_id) - .fetch_one(db) + .fetch_one(&mut *tx) .await?; let cluster_name = cluster_data.name; @@ -5316,6 +7261,7 @@ async fn insert_new_releases_to_namespace( INSERT INTO releases ( id, + tenant_id, service_id, namespace_id, path, @@ -5346,6 +7292,7 @@ async fn insert_new_releases_to_namespace( let new_item = format!( r#"( (SELECT GEN_RANDOM_UUID()), + '{}', (SELECT GEN_RANDOM_UUID()), '{}', '{}', @@ -5355,7 +7302,7 @@ async fn insert_new_releases_to_namespace( '', '' )"#, - namespace_id, path, item.name, item.repo_branch_id + tenant_id, namespace_id, path, item.name, item.repo_branch_id ); if index == 0 { @@ -5365,7 +7312,8 @@ async fn insert_new_releases_to_namespace( } } - sqlx::query(&query).execute(db).await?; + sqlx::query(&query).execute(&mut *tx).await?; + tx.commit().await?; Ok(()) } @@ -5373,7 +7321,14 @@ async fn get_new_service_definitions_to_namespace( db: &sqlx::Pool, namespace_id: &Uuid, service_definition_ids: &Vec, + tenant_id: Uuid, ) -> Result, Box> { + let mut tx = db.begin().await?; + + // Set tenant context for RLS + let query = format!("SET LOCAL app.tenant_id = '{}';", tenant_id); + sqlx::query(&query).execute(&mut *tx).await?; + let existing_releases = sqlx::query_as::<_, NamespaceServiceData>( r#" SELECT @@ -5390,14 +7345,14 @@ async fn get_new_service_definitions_to_namespace( ;"#, ) .bind(namespace_id) - .fetch_all(db) + .fetch_all(&mut *tx) .await?; let service_info = sqlx::query_as::<_, ServiceDefinitionInfo>( "SELECT id, name, repo_branch_id, manifest_path_template FROM service_definitions WHERE id = ANY($1) AND service_definitions.deleted_at IS NULL", ) .bind(service_definition_ids) - .fetch_all(db) + .fetch_all(&mut *tx) .await?; let existing_service_names = existing_releases @@ -5434,24 +7389,31 @@ struct ServiceDefinitionInfo { )] pub async fn post_init_release( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result>, (StatusCode, String)> { - let new_service_definitions = - get_new_service_definitions_to_namespace(&state.pool, &id, &data.service_definition_ids) - .await - .map_err(|e| { - tracing::error!("Failed finding service_definitions new to namespace: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Database error occurred".to_string(), - ) - })?; + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let new_service_definitions = get_new_service_definitions_to_namespace( + &state.pool, + &id, + &data.service_definition_ids, + tenant_id, + ) + .await + .map_err(|e| { + tracing::error!("Failed finding service_definitions new to namespace: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error occurred".to_string(), + ) + })?; let additional_installations = if new_service_definitions.is_empty() { vec![] } else { - insert_new_releases_to_namespace(&state.pool, &id, &new_service_definitions) + insert_new_releases_to_namespace(&state.pool, &id, &new_service_definitions, tenant_id) .await .map_err(|e| { tracing::error!("Failed to insert new service release to namespace: {}", e); @@ -5469,12 +7431,31 @@ pub async fn post_init_release( exists: bool, } - let new_service_definitions_stream = stream::iter(new_service_definitions); + // Create a transaction with tenant context for the additional installations query + let mut tx = state.pool.begin().await.map_err(|e| { + tracing::error!("Failed to begin transaction: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error occurred".to_string(), + ) + })?; + + let ctx_query = format!("SET LOCAL app.tenant_id = '{}';", tenant_id); + sqlx::query(&ctx_query) + .execute(&mut *tx) + .await + .map_err(|e| { + tracing::error!("Failed to set tenant context: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Database error occurred".to_string(), + ) + })?; + + let mut service_definitions_new_to_cluster_namespaces = vec![]; - let db_pool = &state.pool.clone(); - let service_definitions_new_to_cluster_namespaces = new_service_definitions_stream - .fold(vec![], |mut a, service_definition| async move { - let clusters_with_same_namespace_in_group_with_service_relationship = match sqlx::query_as::<_, ClusterInGroupWithServiceDefinition>( + for service_definition in new_service_definitions.iter() { + let clusters_with_same_namespace_in_group_with_service_relationship = sqlx::query_as::<_, ClusterInGroupWithServiceDefinition>( r#" SELECT clusters_in_group_with_common_namespace.namespace_id AS namespace_id, @@ -5567,30 +7548,25 @@ pub async fn post_init_release( ) .bind(id) .bind(service_definition.id) - .fetch_all(db_pool) - .await{ - Ok(v) => v, - Err(e) => { - error!("Failed to find additional installation canidates: {}", e); - vec![] - }, - }; + .fetch_all(&mut *tx) + .await + .map_err(|e| { + tracing::error!("Failed to find additional installation candidates: {}", e); + (StatusCode::INTERNAL_SERVER_ERROR, "Database error occurred".to_string()) + })?; - clusters_with_same_namespace_in_group_with_service_relationship - .iter() - .filter(|item| !item.exists) - .for_each(|item| { - a.push(AdditionalInstallation{ - namespace_id: item.namespace_id, - namespace_name: item.namespace_name.clone(), - service_definition_id: service_definition.id, - cluster_name: item.cluster_name.clone(), - service_name: service_definition.name.clone(), - }) + for item in clusters_with_same_namespace_in_group_with_service_relationship.iter() { + if !item.exists { + service_definitions_new_to_cluster_namespaces.push(AdditionalInstallation { + namespace_id: item.namespace_id, + namespace_name: item.namespace_name.clone(), + service_definition_id: service_definition.id, + cluster_name: item.cluster_name.clone(), + service_name: service_definition.name.clone(), }); - a - }) - .await; + } + } + } service_definitions_new_to_cluster_namespaces }; @@ -5617,6 +7593,7 @@ pub async fn post_init_release( )] pub async fn post_additional_installations( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json>, ) -> Result<(StatusCode, String), (StatusCode, String)> { if data.is_empty() { @@ -5626,6 +7603,8 @@ pub async fn post_additional_installations( )); } + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + let installation_map = data.iter() .fold::>, _>(HashMap::new(), |mut acc, item| { @@ -5643,6 +7622,7 @@ pub async fn post_additional_installations( &state.pool, namespace_id, service_definition_id, + tenant_id, ) .await .map_err(|e| { @@ -5653,15 +7633,20 @@ pub async fn post_additional_installations( ) })?; - insert_new_releases_to_namespace(&state.pool, namespace_id, &service_definitions) - .await - .map_err(|e| { - tracing::error!("Failed to insert additional releases: {}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - String::from("Failed to insert releases for namespace"), - ) - })?; + insert_new_releases_to_namespace( + &state.pool, + namespace_id, + &service_definitions, + tenant_id, + ) + .await + .map_err(|e| { + tracing::error!("Failed to insert additional releases: {}", e); + ( + StatusCode::INTERNAL_SERVER_ERROR, + String::from("Failed to insert releases for namespace"), + ) + })?; } Ok((StatusCode::NO_CONTENT, String::new())) @@ -5685,8 +7670,11 @@ pub async fn post_additional_installations( )] pub async fn post_user( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result, (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + let secret = util::generate_random_string(256); let manifest = match data.context { @@ -5760,17 +7748,26 @@ pub async fn post_user( String::from("Failed to create secure hash for user"), ) })?; + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_user_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" INSERT INTO users - (id, name, hash) + (id, name, hash, tenant_id) VALUES - ((SELECT gen_random_uuid()), $1, $2) + ((SELECT gen_random_uuid()), $1, $2, $3) "#, ) .bind(&data.name) .bind(&hash) - .execute(&state.pool) + .bind(tenant_id) + .execute(&mut *tx) .await .map_err(|e| match e { sqlx::Error::Database(database_error) => { @@ -5808,6 +7805,10 @@ pub async fn post_user( } })?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_user_commit"))?; + Ok(Json(UserData { secret, manifest })) } @@ -5862,8 +7863,19 @@ async fn clean_up_service_relationships( )] pub async fn delete_service_definitions( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_definitions_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -5876,10 +7888,14 @@ pub async fn delete_service_definitions( "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_definitions_commit"))?; + clean_up_service_relationships(&state.pool).await?; Ok((StatusCode::NO_CONTENT, String::new())) @@ -5900,8 +7916,19 @@ pub async fn delete_service_definitions( )] pub async fn delete_service( State(state): State, + headers: axum::http::HeaderMap, Path(name): Path, ) -> Result<(StatusCode, String), (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + sqlx::query( r#" UPDATE @@ -5914,10 +7941,14 @@ pub async fn delete_service( "#, ) .bind(&name) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_commit"))?; + clean_up_service_relationships(&state.pool).await?; Ok((StatusCode::NO_CONTENT, String::new())) @@ -6142,13 +8173,14 @@ pub async fn list_resource_diffs( )] pub async fn get_namespace_service_versions( State(state): State, + headers: axum::http::HeaderMap, Path(namespace_id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); - Ok(Json( - sqlx::query_as::<_, ServiceVersionWithDetails>( - r#" + let result = sqlx::query_as::<_, ServiceVersionWithDetails>( + r#" SELECT sv.id, sv.created_at, @@ -6184,14 +8216,17 @@ pub async fn get_namespace_service_versions( ORDER BY sv.created_at DESC LIMIT $2 OFFSET $3 "#, - ) - .bind(namespace_id) - .bind(pagination.limit) - .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + ) + .bind(namespace_id) + .bind(pagination.limit) + .bind(pagination.offset) + .fetch_all(&mut *tx) + .await + .map_err(|e| sanitize_db_error(e, "get_namespace_service_versions"))?; + tx.commit() .await - .map_err(|e| sanitize_db_error(e, "get_namespace_service_versions"))?, - )) + .map_err(|e| sanitize_db_error(e, "get_namespace_service_versions_commit"))?; + Ok(Json(result)) } /// Get service versions for a specific service definition @@ -6215,10 +8250,12 @@ pub async fn get_namespace_service_versions( )] pub async fn get_service_definition_versions( State(state): State, + headers: axum::http::HeaderMap, Path(service_definition_id): Path, Query(pagination): Query, Query(params): Query>, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let include_deprecated = params .get("include_deprecated") @@ -6266,7 +8303,7 @@ pub async fn get_service_definition_versions( .bind(service_definition_id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await } else { sqlx::query_as::<_, ServiceVersionWithDetails>( @@ -6310,13 +8347,15 @@ pub async fn get_service_definition_versions( .bind(service_definition_id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await }; - Ok(Json(query.map_err(|e| { - sanitize_db_error(e, "get_service_definition_versions") - })?)) + let result = query.map_err(|e| sanitize_db_error(e, "get_service_definition_versions"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_service_definition_versions_commit"))?; + Ok(Json(result)) } /// Create a new service version (for CI/CD pipelines) @@ -6337,6 +8376,7 @@ pub async fn get_service_definition_versions( )] pub async fn post_service_version( State(state): State, + headers: axum::http::HeaderMap, Json(data): Json, ) -> Result<(StatusCode, Json), (StatusCode, String)> { // Validate git_sha format (should be 40 char hex) @@ -6347,6 +8387,16 @@ pub async fn post_service_version( )); } + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let git_sha_short = data.git_sha[..7].to_string(); // Step 1: Deprecate any existing non-pinned active version for this service+namespace @@ -6370,7 +8420,7 @@ pub async fn post_service_version( .bind(data.service_definition_id) .bind(data.namespace_id) .bind(&data.git_sha) - .fetch_all(&state.pool) + .fetch_all(&mut *tx) .await .map(|rows| rows.len() as i64) .unwrap_or(0); @@ -6413,7 +8463,7 @@ pub async fn post_service_version( .bind(data.service_definition_id) .bind(data.namespace_id) .bind(&data.git_sha) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_service_version"))?; @@ -6423,6 +8473,9 @@ pub async fn post_service_version( "Manual version: Version with git_sha {} already exists, returning existing", &data.git_sha[..7] ); + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_service_version_commit"))?; return Ok((StatusCode::OK, Json(existing))); } @@ -6438,9 +8491,10 @@ pub async fn post_service_version( path, hash, source, - source_metadata + source_metadata, + tenant_id ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, created_at, @@ -6468,10 +8522,15 @@ pub async fn post_service_version( .bind(&data.hash) .bind(&data.source) .bind(&data.source_metadata) - .fetch_one(&state.pool) + .bind(tenant_id) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_service_version"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_service_version_commit"))?; + Ok((StatusCode::CREATED, Json(result))) } @@ -6495,9 +8554,20 @@ pub async fn post_service_version( )] pub async fn post_deprecate_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(data): Json, ) -> Result, (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_deprecate_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let result = sqlx::query_as::<_, ServiceVersionData>( r#" UPDATE service_versions @@ -6528,10 +8598,14 @@ pub async fn post_deprecate_service_version( .bind(id) .bind(&data.deprecated_by) .bind(&data.deprecated_reason) - .fetch_one(&state.pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_deprecate_service_version"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_deprecate_service_version_commit"))?; + Ok(Json(result)) } @@ -6556,9 +8630,20 @@ pub async fn post_deprecate_service_version( )] pub async fn post_pin_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, body: Option>, ) -> Result { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_pin_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let pinned_by = body.and_then(|b| b.pinned_by.clone()); let result = sqlx::query( r#" @@ -6569,7 +8654,7 @@ pub async fn post_pin_service_version( ) .bind(id) .bind(&pinned_by) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_pin_service_version"))?; @@ -6580,6 +8665,10 @@ pub async fn post_pin_service_version( )); } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_pin_service_version_commit"))?; + Ok(StatusCode::OK) } @@ -6602,8 +8691,19 @@ pub async fn post_pin_service_version( )] pub async fn post_unpin_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "post_unpin_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let result = sqlx::query( r#" UPDATE service_versions @@ -6612,7 +8712,7 @@ pub async fn post_unpin_service_version( "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "post_unpin_service_version"))?; @@ -6623,6 +8723,10 @@ pub async fn post_unpin_service_version( )); } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "post_unpin_service_version_commit"))?; + Ok(StatusCode::OK) } @@ -6645,15 +8749,26 @@ pub async fn post_unpin_service_version( )] pub async fn delete_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_version_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + let result = sqlx::query( r#" DELETE FROM service_versions WHERE id = $1 "#, ) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_service_version"))?; @@ -6664,6 +8779,10 @@ pub async fn delete_service_version( )); } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_service_version_commit"))?; + Ok(StatusCode::NO_CONTENT) } @@ -6686,8 +8805,10 @@ pub async fn delete_service_version( )] pub async fn get_service_version( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let result = sqlx::query_as::<_, ServiceVersionWithDetails>( r#" SELECT @@ -6724,10 +8845,13 @@ pub async fn get_service_version( "#, ) .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_service_version"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_service_version_commit"))?; Ok(Json(result)) } @@ -6805,6 +8929,7 @@ fn validate_path_template(template: &str) -> PathTemplateValidation { )] pub async fn update_manifest_path_template( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, Json(body): Json, ) -> Result, (StatusCode, String)> { @@ -6820,6 +8945,16 @@ pub async fn update_manifest_path_template( )); } + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "update_manifest_path_template_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Update the service definition let result = sqlx::query( r#" @@ -6830,7 +8965,7 @@ pub async fn update_manifest_path_template( ) .bind(&body.manifest_path_template) .bind(id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "update_manifest_path_template"))?; @@ -6841,6 +8976,10 @@ pub async fn update_manifest_path_template( )); } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "update_manifest_path_template_commit"))?; + Ok(Json(validation)) } @@ -6863,8 +9002,10 @@ pub async fn update_manifest_path_template( )] pub async fn get_manifest_path_template( State(state): State, + headers: axum::http::HeaderMap, Path(id): Path, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let result = sqlx::query_scalar::<_, Option>( r#" SELECT manifest_path_template @@ -6873,10 +9014,13 @@ pub async fn get_manifest_path_template( "#, ) .bind(id) - .fetch_one(&state.readonly_pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_manifest_path_template"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_manifest_path_template_commit"))?; Ok(Json(result)) } @@ -6917,8 +9061,10 @@ pub async fn validate_path_template_endpoint( )] pub async fn get_repo_webhook( State(state): State, + headers: axum::http::HeaderMap, Path(repo_id): Path, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let result = sqlx::query_as::<_, RepoWebhookData>( r#" SELECT @@ -6938,10 +9084,13 @@ pub async fn get_repo_webhook( "#, ) .bind(repo_id) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_repo_webhook"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_repo_webhook_commit"))?; Ok(Json(result)) } @@ -6968,9 +9117,20 @@ pub async fn get_repo_webhook( )] pub async fn register_repo_webhook( State(state): State, + headers: axum::http::HeaderMap, Path(repo_id): Path, Json(body): Json, ) -> Result, (StatusCode, String)> { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "register_repo_webhook_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Get the repo info let repo = sqlx::query_as::<_, RepoData>( r#" @@ -6987,7 +9147,7 @@ pub async fn register_repo_webhook( "#, ) .bind(repo_id) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "register_repo_webhook"))? .ok_or((StatusCode::NOT_FOUND, "Repo not found".to_string()))?; @@ -7005,7 +9165,7 @@ pub async fn register_repo_webhook( r#"SELECT id, deleted_at FROM repo_webhooks WHERE repo_id = $1"#, ) .bind(repo_id) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "register_repo_webhook"))?; @@ -7110,14 +9270,14 @@ pub async fn register_repo_webhook( .bind(provider_webhook_id) .bind(&webhook_secret) .bind(&webhook_secret_hash) - .fetch_one(&state.pool) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "register_repo_webhook"))? } else { sqlx::query_scalar::<_, Uuid>( r#" - INSERT INTO repo_webhooks (repo_id, provider_webhook_id, secret, secret_hash, active) - VALUES ($1, $2, $3, $4, true) + INSERT INTO repo_webhooks (repo_id, provider_webhook_id, secret, secret_hash, active, tenant_id) + VALUES ($1, $2, $3, $4, true, $5) RETURNING id "#, ) @@ -7125,11 +9285,16 @@ pub async fn register_repo_webhook( .bind(provider_webhook_id) .bind(&webhook_secret) .bind(&webhook_secret_hash) - .fetch_one(&state.pool) + .bind(tenant_id) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "register_repo_webhook"))? }; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "register_repo_webhook_commit"))?; + Ok(Json(RegisterRepoWebhookResponse { webhook_id, provider_webhook_id, @@ -7160,9 +9325,20 @@ pub async fn register_repo_webhook( )] pub async fn delete_repo_webhook( State(state): State, + headers: axum::http::HeaderMap, Path(repo_id): Path, body: Option>, ) -> Result { + let tenant_id = extract_tenant_from_request(&state.pool, &headers).await?; + + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "delete_repo_webhook_begin"))?; + + set_tenant_context(&mut tx, tenant_id).await?; + // Load webhook + repo info so we can optionally delete the remote GitHub hook. let webhook = sqlx::query_as::<_, (Option, String, String, String)>( r#" @@ -7177,7 +9353,7 @@ pub async fn delete_repo_webhook( "#, ) .bind(repo_id) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_repo_webhook"))? .ok_or((StatusCode::NOT_FOUND, "Webhook not found".to_string()))?; @@ -7242,7 +9418,7 @@ pub async fn delete_repo_webhook( "#, ) .bind(repo_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "delete_repo_webhook"))?; @@ -7251,6 +9427,10 @@ pub async fn delete_repo_webhook( return Err((StatusCode::NOT_FOUND, "Webhook not found".to_string())); } + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "delete_repo_webhook_commit"))?; + Ok(StatusCode::OK) } @@ -7320,9 +9500,9 @@ pub async fn receive_github_webhook( // Find the webhook and its secret. // Prefer repo_id from callback URL query param; fall back to org/repo lookup. let webhook_info = if let Some(repo_id) = repo_id { - sqlx::query_as::<_, (Uuid, Uuid, Option)>( + sqlx::query_as::<_, (Uuid, Uuid, Option, Uuid)>( r#" - SELECT gw.id, gw.repo_id, gw.secret + SELECT gw.id, gw.repo_id, gw.secret, gw.tenant_id FROM repo_webhooks gw WHERE gw.repo_id = $1 AND gw.deleted_at IS NULL AND gw.active = true @@ -7333,9 +9513,9 @@ pub async fn receive_github_webhook( .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))? } else { - sqlx::query_as::<_, (Uuid, Uuid, Option)>( + sqlx::query_as::<_, (Uuid, Uuid, Option, Uuid)>( r#" - SELECT gw.id, gw.repo_id, gw.secret + SELECT gw.id, gw.repo_id, gw.secret, gw.tenant_id FROM repo_webhooks gw JOIN repos r ON r.id = gw.repo_id WHERE LOWER(r.org) = LOWER($1) AND LOWER(r.repo) = LOWER($2) @@ -7353,7 +9533,7 @@ pub async fn receive_github_webhook( "No webhook registered for this repo".to_string(), ))?; - let (webhook_id, _repo_id, webhook_secret) = webhook_info; + let (webhook_id, _repo_id, webhook_secret, tenant_id) = webhook_info; let webhook_secret = webhook_secret.ok_or(( StatusCode::INTERNAL_SERVER_ERROR, "Webhook secret is not configured for this repo".to_string(), @@ -7388,12 +9568,20 @@ pub async fn receive_github_webhook( )); } + // Start transaction and set tenant context for all subsequent queries + let mut tx = state + .pool + .begin() + .await + .map_err(|e| sanitize_db_error(e, "receive_github_webhook_begin"))?; + set_tenant_context(&mut tx, tenant_id).await?; + // Create webhook event record let event_id = sqlx::query_scalar::<_, Uuid>( r#" INSERT INTO repo_webhook_events - (webhook_id, delivery_id, event_type, ref, before_sha, after_sha, pusher) - VALUES ($1, $2, $3, $4, $5, $6, $7) + (webhook_id, delivery_id, event_type, ref, before_sha, after_sha, pusher, tenant_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id "#, ) @@ -7404,7 +9592,8 @@ pub async fn receive_github_webhook( .bind(&payload.before) .bind(&payload.after) .bind(&payload.pusher.name) - .fetch_one(&state.pool) + .bind(tenant_id) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7426,7 +9615,7 @@ pub async fn receive_github_webhook( .bind(org) .bind(repo_name) .bind(&branch) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7497,7 +9686,7 @@ pub async fn receive_github_webhook( ) .bind(namespace) .bind(cluster) - .fetch_optional(&state.readonly_pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7591,7 +9780,7 @@ pub async fn receive_github_webhook( .bind(service_def_id) .bind(ns_id) .bind(&payload.after) - .fetch_all(&state.pool) + .fetch_all(&mut *tx) .await .map(|rows| rows.len() as i64) .unwrap_or(0); @@ -7620,7 +9809,7 @@ pub async fn receive_github_webhook( .bind(service_def_id) .bind(ns_id) .bind(&payload.after) - .fetch_optional(&state.pool) + .fetch_optional(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7637,7 +9826,7 @@ pub async fn receive_github_webhook( .bind(is_directory) .bind(event_id) .bind(existing_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7652,8 +9841,8 @@ pub async fn receive_github_webhook( sqlx::query_scalar::<_, Uuid>( r#" INSERT INTO service_versions - (service_definition_id, namespace_id, version, git_sha, git_sha_short, path, is_directory_pattern, hash, source, webhook_event_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'webhook', $9) + (service_definition_id, namespace_id, version, git_sha, git_sha_short, path, is_directory_pattern, hash, source, webhook_event_id, tenant_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'webhook', $9, $10) RETURNING id "#, ) @@ -7666,7 +9855,8 @@ pub async fn receive_github_webhook( .bind(is_directory) .bind("pending") // Hash will be computed later when manifests are fetched .bind(event_id) - .fetch_one(&state.pool) + .bind(tenant_id) + .fetch_one(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))? }; @@ -7696,7 +9886,7 @@ pub async fn receive_github_webhook( .bind(&matched_paths) .bind(&updated_versions) .bind(event_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; @@ -7707,10 +9897,15 @@ pub async fn receive_github_webhook( "#, ) .bind(webhook_id) - .execute(&state.pool) + .execute(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "receive_github_webhook"))?; + // Commit the transaction + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "receive_github_webhook_commit"))?; + Ok(Json(json!({ "status": "ok", "event_id": event_id, @@ -8669,9 +10864,11 @@ mod path_template_tests { )] pub async fn get_webhook_events( State(state): State, + headers: axum::http::HeaderMap, Path(repo_id): Path, Query(pagination): Query, ) -> Result>, (StatusCode, String)> { + let (mut tx, _tenant_id, _tenant_domain) = get_tenant_tx(&state.pool, &headers).await?; let pagination = pagination.validate(); let events = sqlx::query_as::<_, RepoWebhookEvent>( @@ -8700,9 +10897,12 @@ pub async fn get_webhook_events( .bind(repo_id) .bind(pagination.limit) .bind(pagination.offset) - .fetch_all(&state.readonly_pool) + .fetch_all(&mut *tx) .await .map_err(|e| sanitize_db_error(e, "get_webhook_events"))?; + tx.commit() + .await + .map_err(|e| sanitize_db_error(e, "get_webhook_events_commit"))?; Ok(Json(events)) } diff --git a/hive-hq/api/src/lib.rs b/hive-hq/api/src/lib.rs index 0b0fd07..b22cb37 100644 --- a/hive-hq/api/src/lib.rs +++ b/hive-hq/api/src/lib.rs @@ -7,6 +7,11 @@ pub mod util; #[cfg(test)] mod auth_tests; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// Application state shared across all API handlers. #[derive(Clone)] pub struct ServerState { pub pool: sqlx::Pool, @@ -14,9 +19,9 @@ pub struct ServerState { pub agent_manifest_template: String, pub agent_default_image: Option, pub hive_default_grpc_server: Option, + /// Explicit TLS override for HIVE_DEFAULT_GRPC_SERVER (None = infer from scheme) + pub hive_default_grpc_tls: Option, pub version: String, - pub gh_token: String, - pub github_api_url: String, /// JWT secret bytes - either decoded from base64 or raw UTF-8 bytes pub jwt_secret_bytes: Vec, pub read_replica_wait_in_ms: u64, @@ -24,4 +29,10 @@ pub struct ServerState { /// This must be externally reachable by GitHub, e.g.: /// https://hive-hq.example.com/api/webhooks/github pub github_webhook_callback_url: Option, + /// In-memory, per-tenant encrypted secret cache. No DB persistence. + pub secret_cache: Arc< + RwLock< + HashMap<(uuid::Uuid, String), (Vec, Vec, i16, chrono::DateTime)>, + >, + >, } diff --git a/hive-hq/api/src/main.rs b/hive-hq/api/src/main.rs index 9730d41..0a0b526 100644 --- a/hive-hq/api/src/main.rs +++ b/hive-hq/api/src/main.rs @@ -4,6 +4,9 @@ use axum::{ }; use base64::{engine::general_purpose::STANDARD, Engine}; use sqlx::postgres::PgPoolOptions; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; use tower_http::{ cors::{Any, CorsLayer}, services::{ServeDir, ServeFile}, @@ -17,25 +20,14 @@ use utoipa_swagger_ui::SwaggerUi; mod handler; mod util; +// Re-export ServerState from lib.rs (single source of truth) +use api::ServerState; + // const VERSION: Option<&'static str> = std::option_env!("API_VERSION"); const VERSION: &str = env!("CARGO_PKG_VERSION"); const BUILD_VERSION: Option<&str> = option_env!("BUILD_VERSION"); static AGENT_MANIFEST_TEMPLATE: &str = include_str!("static/agent.tpl.yaml"); -#[derive(Clone)] -pub struct ServerState { - pool: sqlx::Pool, - readonly_pool: sqlx::Pool, - agent_manifest_template: String, - agent_default_image: Option, - hive_default_grpc_server: Option, - version: String, - /// JWT secret bytes - either decoded from base64 or raw UTF-8 bytes - jwt_secret_bytes: Vec, - read_replica_wait_in_ms: u64, - github_webhook_callback_url: Option, -} - /// Decode JWT secret from string. /// Supports both base64-encoded secrets (e.g., from `openssl rand -base64 32`) /// and raw string secrets for backward compatibility. @@ -306,6 +298,14 @@ async fn main() -> Result<(), Box> { let agent_default_image = std::env::var("AGENT_DEFAULT_IMAGE").ok(); let hive_default_grpc_server = std::env::var("HIVE_DEFAULT_GRPC_SERVER").ok(); + let hive_default_grpc_tls = + std::env::var("HIVE_DEFAULT_GRPC_TLS") + .ok() + .and_then(|v| match v.to_lowercase().as_str() { + "true" | "1" | "yes" => Some(true), + "false" | "0" | "no" => Some(false), + _ => None, + }); let github_webhook_callback_url = match std::env::var("GITHUB_WEBHOOK_CALLBACK_URL") { Ok(s) if !s.trim().is_empty() => match normalize_github_webhook_callback_url(&s) { Ok((normalized, changed)) => { @@ -358,10 +358,12 @@ async fn main() -> Result<(), Box> { agent_manifest_template: String::from(AGENT_MANIFEST_TEMPLATE), agent_default_image, hive_default_grpc_server, + hive_default_grpc_tls, version: crate::BUILD_VERSION.map_or(crate::VERSION.to_string(), String::from), jwt_secret_bytes, read_replica_wait_in_ms: read_replica_wait_in_ms.parse().unwrap_or(75), github_webhook_callback_url, + secret_cache: Arc::new(RwLock::new(HashMap::new())), }; let cors = CorsLayer::new().allow_origin(Any); @@ -376,12 +378,16 @@ async fn main() -> Result<(), Box> { // build our application with a route let public_routes = Router::new() .route("/api/version", get(handler::version)) + .route("/api/config", get(handler::get_app_config)) .route("/api/auth/bootstrap", post(handler::ui_auth_bootstrap)) .route( "/api/auth/bootstrap/status", get(handler::ui_auth_bootstrap_status), ) - .route("/api/auth/login", post(handler::ui_auth_login)); + .route("/api/auth/login", post(handler::ui_auth_login)) + .route("/api/tenants/register", post(handler::register_tenant)) + // temporary alias to handle environments that double-prefix /api + .route("/api/api/tenants/register", post(handler::register_tenant)); #[cfg(feature = "dev-mode")] let public_routes = public_routes.route("/api/free-token", get(handler::free_token)); @@ -411,6 +417,15 @@ async fn main() -> Result<(), Box> { .layer(cors) .route("/api/auth/me", get(handler::ui_auth_me)) .route("/api/auth/logout", post(handler::ui_auth_logout)) + .route( + "/api/secrets", + get(handler::list_secrets).post(handler::create_secret), + ) + .route("/api/secrets/{purpose}", delete(handler::delete_secret)) + .route( + "/api/secrets/{purpose}/encrypted", + get(handler::get_encrypted_secret), + ) .route("/api/cluster-defaults", get(handler::get_cluster_defaults)) .route( "/api/clusters", diff --git a/hive-hq/api/tests/acl_tests.rs b/hive-hq/api/tests/acl_tests.rs index 3bdcb80..adace74 100644 --- a/hive-hq/api/tests/acl_tests.rs +++ b/hive-hq/api/tests/acl_tests.rs @@ -7,11 +7,12 @@ use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tower::ServiceExt; use types::Claim; +use uuid::Uuid; // Tests must run serially to avoid port-forward conflicts // Run with: cargo test --test acl_tests -- --ignored --nocapture --test-threads=1 -fn generate_test_jwt(jwt_secret: &str, roles: Vec) -> String { +fn generate_test_jwt(jwt_secret: &str, roles: Vec, tenant_id: Uuid) -> String { let secret_bytes = jwt_secret.as_bytes(); let expiration = @@ -21,6 +22,7 @@ fn generate_test_jwt(jwt_secret: &str, roles: Vec) -> String { email: "test@galleybytes.com".to_string(), exp: expiration.as_secs() as usize, roles, + tenant_id: tenant_id.to_string(), }; encode( @@ -95,7 +97,7 @@ async fn test_aversion_endpoint_requires_role() { let app = env.create_test_app(); // Create token without aversion role - let token = generate_test_jwt(&env.jwt_secret, vec!["some-other-role".to_string()]); + let token = generate_test_jwt(&env.jwt_secret, vec!["some-other-role".to_string()], env.tenant_id); let request = Request::builder() .uri("/api/aversion/clusters/test-cluster/namespaces") @@ -122,7 +124,7 @@ async fn test_aversion_endpoint_accepts_aversion_role() { let app = env.create_test_app(); // Create token with aversion role - let token = generate_test_jwt(&env.jwt_secret, vec!["aversion".to_string()]); + let token = generate_test_jwt(&env.jwt_secret, vec!["aversion".to_string()], env.tenant_id); let request = Request::builder() .uri("/api/aversion/clusters/test-cluster/namespaces") @@ -150,7 +152,7 @@ async fn test_aversion_endpoint_accepts_admin_role() { let app = env.create_test_app(); // Create token with admin role - let token = generate_test_jwt(&env.jwt_secret, vec!["admin".to_string()]); + let token = generate_test_jwt(&env.jwt_secret, vec!["admin".to_string()], env.tenant_id); let request = Request::builder() .uri("/api/aversion/clusters/test-cluster/namespaces") @@ -178,7 +180,7 @@ async fn test_admin_role_accesses_protected_endpoints() { let app = env.create_test_app(); // Create token with admin role - let token = generate_test_jwt(&env.jwt_secret, vec!["admin".to_string()]); + let token = generate_test_jwt(&env.jwt_secret, vec!["admin".to_string()], env.tenant_id); let request = Request::builder() .uri("/api/clusters") @@ -205,7 +207,7 @@ async fn test_aversion_role_accesses_protected_endpoints() { let app = env.create_test_app(); // Create token with aversion role - let token = generate_test_jwt(&env.jwt_secret, vec!["aversion".to_string()]); + let token = generate_test_jwt(&env.jwt_secret, vec!["aversion".to_string()], env.tenant_id); let request = Request::builder() .uri("/api/clusters") @@ -235,6 +237,7 @@ async fn test_multiple_roles() { let token = generate_test_jwt( &env.jwt_secret, vec!["admin".to_string(), "aversion".to_string()], + env.tenant_id, ); let request = Request::builder() diff --git a/hive-hq/api/tests/api_integration_tests.rs b/hive-hq/api/tests/api_integration_tests.rs index b9849ae..41711b0 100644 --- a/hive-hq/api/tests/api_integration_tests.rs +++ b/hive-hq/api/tests/api_integration_tests.rs @@ -17,7 +17,7 @@ async fn test_get_clusters_with_bulk_data() { // Create 200 test clusters let mut cluster_ids = Vec::new(); for _i in 0..200 { - let id = create_test_cluster(&env.pool).await; + let id = create_test_cluster(&env.pool, env.tenant_id).await; cluster_ids.push(id); } @@ -56,7 +56,7 @@ async fn test_post_cluster_group_with_auth() { // Create a cluster group let group_name = format!("test-group-{}", Uuid::new_v4()); - let group_id = create_test_cluster_group(&env.pool, &group_name).await; + let group_id = create_test_cluster_group(&env.pool, &group_name, env.tenant_id).await; // Verify it was created let result: (Uuid, String) = @@ -85,8 +85,8 @@ async fn test_put_service_definition_update() { .expect("Failed to generate JWT"); // Create test data - let (_, branch_id) = create_test_repo(&env.pool).await; - let bt_id = create_test_service_definition(&env.pool, branch_id, "original-name").await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; + let bt_id = create_test_service_definition(&env.pool, branch_id, "original-name", env.tenant_id).await; // Update the build target let new_name = format!("updated-name-{}", Uuid::new_v4()); @@ -123,7 +123,7 @@ async fn test_delete_cluster_group() { // Create a cluster group let group_name = format!("test-group-{}", Uuid::new_v4()); - let group_id = create_test_cluster_group(&env.pool, &group_name).await; + let group_id = create_test_cluster_group(&env.pool, &group_name, env.tenant_id).await; // Verify it exists let count_before: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM cluster_groups WHERE id = $1") @@ -163,7 +163,7 @@ async fn test_authentication_failure() { // but would fail at API level (not testing full API stack here) // Create data without auth to verify DB operations work - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; let result: (Uuid,) = sqlx::query_as("SELECT id FROM clusters WHERE id = $1") .bind(cluster_id) @@ -184,7 +184,7 @@ async fn test_bulk_operations_with_300_entries() { .await .expect("Failed to setup test environment"); - let fixtures = create_bulk_fixtures(&env.pool, 300).await; + let fixtures = create_bulk_fixtures(&env.pool, 300, env.tenant_id).await; assert_eq!(fixtures.service_definition_ids.len(), 300); assert_eq!(fixtures.release_ids.len(), 300); @@ -231,7 +231,7 @@ async fn test_complex_query_with_joins() { .await .expect("Failed to setup test environment"); - let fixtures = create_bulk_fixtures(&env.pool, 100).await; + let fixtures = create_bulk_fixtures(&env.pool, 100, env.tenant_id).await; // Complex query joining multiple tables let results: Vec<(Uuid, String, String)> = sqlx::query_as( @@ -274,8 +274,8 @@ async fn test_data_integrity_after_crud_operations() { .expect("Failed to generate JWT"); // CREATE - let (_, branch_id) = create_test_repo(&env.pool).await; - let bt_id = create_test_service_definition(&env.pool, branch_id, "test-service").await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; + let bt_id = create_test_service_definition(&env.pool, branch_id, "test-service", env.tenant_id).await; // READ let read_result: (String,) = @@ -333,7 +333,8 @@ async fn test_concurrent_operations() { let mut tasks = Vec::new(); for _ in 0..50 { let pool = env.pool.clone(); - let task = tokio::spawn(async move { create_test_cluster(&pool).await }); + let tenant_id = env.tenant_id; + let task = tokio::spawn(async move { create_test_cluster(&pool, tenant_id).await }); tasks.push(task); } diff --git a/hive-hq/api/tests/error_path_tests.rs b/hive-hq/api/tests/error_path_tests.rs index c9eeeb0..5014e95 100644 --- a/hive-hq/api/tests/error_path_tests.rs +++ b/hive-hq/api/tests/error_path_tests.rs @@ -90,10 +90,12 @@ async fn test_foreign_key_violation_on_namespace() { let non_existent_cluster_id = Uuid::new_v4(); // Try to create namespace with non-existent cluster - let result = sqlx::query("INSERT INTO namespaces (id, name, cluster_id) VALUES ($1, $2, $3)") + // Even with valid tenant_id, the FK constraint should fail + let result = sqlx::query("INSERT INTO namespaces (id, name, cluster_id, tenant_id) VALUES ($1, $2, $3, $4)") .bind(Uuid::new_v4()) .bind("orphan-namespace") .bind(non_existent_cluster_id) + .bind(env.tenant_id) .execute(&env.pool) .await; @@ -161,7 +163,7 @@ async fn test_concurrent_updates_on_same_record() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; // Launch 10 concurrent updates to the same cluster let mut tasks = Vec::new(); @@ -251,9 +253,9 @@ async fn test_batch_operations_near_parameter_limit() { // With 4 parameters per insert, we can do ~8000 at once // Let's try with 2000 to be safe - let cluster_id = create_test_cluster(&env.pool).await; - let namespace_id = create_test_namespace(&env.pool, cluster_id).await; - let (_, _branch_id) = create_test_repo(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; + let namespace_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; + let (_, _branch_id) = create_test_repo(&env.pool, env.tenant_id).await; let mut release_names = Vec::new(); for i in 0..2000 { @@ -284,17 +286,18 @@ async fn test_transaction_rollback_with_foreign_keys() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; // Start transaction let mut tx = env.pool.begin().await.expect("Failed to start transaction"); // Create namespace in transaction let namespace_id = Uuid::new_v4(); - sqlx::query("INSERT INTO namespaces (id, name, cluster_id) VALUES ($1, $2, $3)") + sqlx::query("INSERT INTO namespaces (id, name, cluster_id, tenant_id) VALUES ($1, $2, $3, $4)") .bind(namespace_id) .bind("tx-namespace") .bind(cluster_id) + .bind(env.tenant_id) .execute(&mut *tx) .await .expect("Failed to insert namespace"); @@ -379,16 +382,17 @@ async fn test_empty_string_vs_null() { .await .expect("Failed to setup test environment"); - let (_, branch_id) = create_test_repo(&env.pool).await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; // Insert build target with empty name (should work - no constraint) let result = sqlx::query( - "INSERT INTO service_definitions (id, name, repo_branch_id, source_branch_requirements) VALUES ($1, $2, $3, $4)" + "INSERT INTO service_definitions (id, name, repo_branch_id, source_branch_requirements, tenant_id) VALUES ($1, $2, $3, $4, $5)" ) .bind(Uuid::new_v4()) .bind("") // empty string .bind(branch_id) .bind("[]") + .bind(env.tenant_id) .execute(&env.pool) .await; @@ -454,16 +458,17 @@ async fn test_special_characters_in_metadata() { .expect("Failed to setup test environment"); let special_metadata = - r#"{"key": "value with \"quotes\"", "emoji": "🚀", "newline": "line1\nline2"}"#; + r#"{"key": "value with \"quotes\"", "emoji": "rocket", "newline": "line1\nline2"}"#; let result = sqlx::query( - "INSERT INTO clusters (id, name, metadata, version, kubernetes_version) VALUES ($1, $2, $3, $4, $5)" + "INSERT INTO clusters (id, name, metadata, version, kubernetes_version, tenant_id) VALUES ($1, $2, $3, $4, $5, $6)" ) .bind(Uuid::new_v4()) .bind("special-cluster") .bind(special_metadata) .bind("1.0") .bind("1.28") + .bind(env.tenant_id) .execute(&env.pool) .await; @@ -523,7 +528,7 @@ async fn test_optimistic_locking_pattern() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; // Read with timestamp let original_updated: chrono::DateTime = diff --git a/hive-hq/api/tests/http_api_tests.rs b/hive-hq/api/tests/http_api_tests.rs index 4616720..2435dc5 100644 --- a/hive-hq/api/tests/http_api_tests.rs +++ b/hive-hq/api/tests/http_api_tests.rs @@ -51,7 +51,7 @@ async fn test_get_clusters_with_auth() { // Create test clusters for _ in 0..10 { - create_test_cluster(&env.pool).await; + create_test_cluster(&env.pool, env.tenant_id).await; } let app = env.create_test_app(); @@ -205,6 +205,7 @@ async fn test_wrong_signature_returns_401() { email: "test@galleybytes.com".to_string(), exp: expiration.as_secs() as usize, roles: vec!["admin".to_string()], + tenant_id: env.tenant_id.to_string(), }; let wrong_token = encode( @@ -332,7 +333,7 @@ async fn test_get_cluster_groups_with_performance() { // Create 50 cluster groups with unique names for _ in 0..50 { let unique_name = format!("perf-group-{}", Uuid::new_v4()); - create_test_cluster_group(&env.pool, &unique_name).await; + create_test_cluster_group(&env.pool, &unique_name, env.tenant_id).await; } let app = env.create_test_app(); @@ -393,7 +394,7 @@ async fn test_get_clusters_bulk_with_performance() { println!("Creating 200 test clusters..."); let create_start = Instant::now(); for _ in 0..200 { - create_test_cluster(&env.pool).await; + create_test_cluster(&env.pool, env.tenant_id).await; } let create_elapsed = create_start.elapsed(); println!("✓ Created 200 clusters in {:?}", create_elapsed); @@ -459,7 +460,7 @@ async fn test_delete_cluster() { .expect("Failed to setup test environment"); // Create a test cluster - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; let app = env.create_test_app(); let token = env diff --git a/hive-hq/api/tests/integration/test_env.rs b/hive-hq/api/tests/integration/test_env.rs index 5440c46..6a1596b 100644 --- a/hive-hq/api/tests/integration/test_env.rs +++ b/hive-hq/api/tests/integration/test_env.rs @@ -5,7 +5,7 @@ use axum::{ Router, }; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; -use sqlx::PgPool; +use sqlx::{Executor, PgPool}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::time::sleep; use types::Claim; @@ -15,6 +15,8 @@ pub struct TestEnvironment { pub namespace: String, pub pool: PgPool, pub jwt_secret: String, + pub tenant_id: Uuid, + pub tenant_subdomain: String, } impl TestEnvironment { @@ -47,15 +49,46 @@ impl TestEnvironment { let jwt_secret = "test_jwt_secret_for_integration_tests_minimum_length_required".to_string(); - println!("✓ Test environment ready"); + // Create test tenant for isolation + let tenant_id = Uuid::new_v4(); + let tenant_subdomain = format!("test-{}", &tenant_id.to_string()[..8]); + + sqlx::query( + r#" + INSERT INTO tenants (id, name, domain) + VALUES ($1, $2, $3) + ON CONFLICT (domain) DO NOTHING + "#, + ) + .bind(tenant_id) + .bind(&format!("Test Tenant {}", tenant_subdomain)) + .bind(&tenant_subdomain) + .execute(&pool) + .await?; + + println!("Created test tenant: {} ({})", tenant_subdomain, tenant_id); + println!("Test environment ready"); Ok(Self { namespace, pool, jwt_secret, + tenant_id, + tenant_subdomain, }) } + /// Set tenant context for RLS policies. Call this before any tenant-scoped queries. + pub async fn set_tenant_context<'e, E>(&self, executor: E) -> Result<(), sqlx::Error> + where + E: Executor<'e, Database = sqlx::Postgres>, + { + sqlx::query(&format!("SET LOCAL app.tenant_id = '{}'", self.tenant_id)) + .execute(executor) + .await?; + Ok(()) + } + pub async fn cleanup(self) { println!("Cleaning up test environment..."); @@ -74,6 +107,7 @@ impl TestEnvironment { email: email.to_string(), exp: expiration.as_secs() as usize, roles: vec!["admin".to_string()], + tenant_id: self.tenant_id.to_string(), }; let token = encode( @@ -92,6 +126,7 @@ impl TestEnvironment { email: email.to_string(), exp: expiration.as_secs() as usize, roles: vec!["admin".to_string()], + tenant_id: self.tenant_id.to_string(), }; let token = encode( @@ -109,6 +144,9 @@ impl TestEnvironment { // Import the handler module from the main crate use api::handler; use api::ServerState; + use std::collections::HashMap; + use std::sync::Arc; + use tokio::sync::RwLock; use tower_http::cors::{Any, CorsLayer}; let server_state = ServerState { @@ -117,12 +155,12 @@ impl TestEnvironment { agent_manifest_template: "test-template".to_string(), agent_default_image: Some("test-image:latest".to_string()), hive_default_grpc_server: Some("test-grpc:50051".to_string()), + hive_default_grpc_tls: Some(false), version: "test-1.0.0".to_string(), - gh_token: "test-token".to_string(), - github_api_url: "https://api.github.com".to_string(), jwt_secret_bytes: self.jwt_secret.clone().into_bytes(), read_replica_wait_in_ms: 0, // No wait for tests github_webhook_callback_url: None, + secret_cache: Arc::new(RwLock::new(HashMap::new())), }; let cors = CorsLayer::new().allow_origin(Any); @@ -229,55 +267,58 @@ impl TestEnvironment { } } -// Test fixture helpers -pub async fn create_test_cluster(pool: &PgPool) -> Uuid { +// Test fixture helpers - all require tenant context set first +pub async fn create_test_cluster(pool: &PgPool, tenant_id: Uuid) -> Uuid { let id = Uuid::new_v4(); let name = format!("test-cluster-{}", Uuid::new_v4()); sqlx::query( r#" - INSERT INTO clusters (id, name, metadata, version, kubernetes_version) - VALUES ($1, $2, 'test', '1.0', '1.28') + INSERT INTO clusters (id, name, metadata, version, kubernetes_version, tenant_id) + VALUES ($1, $2, 'test', '1.0', '1.28', $3) "#, ) .bind(id) .bind(name) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test cluster"); id } -pub async fn create_test_namespace(pool: &PgPool, cluster_id: Uuid) -> Uuid { +pub async fn create_test_namespace(pool: &PgPool, cluster_id: Uuid, tenant_id: Uuid) -> Uuid { let id = Uuid::new_v4(); let name = format!("test-namespace-{}", Uuid::new_v4()); sqlx::query( r#" - INSERT INTO namespaces (id, name, cluster_id) - VALUES ($1, $2, $3) + INSERT INTO namespaces (id, name, cluster_id, tenant_id) + VALUES ($1, $2, $3, $4) "#, ) .bind(id) .bind(name) .bind(cluster_id) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test namespace"); id } -pub async fn create_test_repo(pool: &PgPool) -> (Uuid, Uuid) { +pub async fn create_test_repo(pool: &PgPool, tenant_id: Uuid) -> (Uuid, Uuid) { let repo_id = Uuid::new_v4(); let org = format!("test-org-{}", Uuid::new_v4()); let repo = format!("test-repo-{}", Uuid::new_v4()); sqlx::query( r#" - INSERT INTO repos (id, org, repo) - VALUES ($1, $2, $3) + INSERT INTO repos (id, org, repo, tenant_id) + VALUES ($1, $2, $3, $4) "#, ) .bind(repo_id) .bind(&org) .bind(&repo) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test repo"); @@ -285,12 +326,13 @@ pub async fn create_test_repo(pool: &PgPool) -> (Uuid, Uuid) { let branch_id = Uuid::new_v4(); sqlx::query( r#" - INSERT INTO repo_branches (id, branch, repo_id) - VALUES ($1, 'main', $2) + INSERT INTO repo_branches (id, branch, repo_id, tenant_id) + VALUES ($1, 'main', $2, $3) "#, ) .bind(branch_id) .bind(repo_id) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test repo branch"); @@ -302,33 +344,36 @@ pub async fn create_test_service_definition( pool: &PgPool, repo_branch_id: Uuid, name: &str, + tenant_id: Uuid, ) -> Uuid { let id = Uuid::new_v4(); sqlx::query( r#" - INSERT INTO service_definitions (id, name, repo_branch_id, source_branch_requirements) - VALUES ($1, $2, $3, '[]') + INSERT INTO service_definitions (id, name, repo_branch_id, source_branch_requirements, tenant_id) + VALUES ($1, $2, $3, '[]', $4) "#, ) .bind(id) .bind(name) .bind(repo_branch_id) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test build target"); id } -pub async fn create_test_cluster_group(pool: &PgPool, name: &str) -> Uuid { +pub async fn create_test_cluster_group(pool: &PgPool, name: &str, tenant_id: Uuid) -> Uuid { let id = Uuid::new_v4(); sqlx::query( r#" - INSERT INTO cluster_groups (id, name) - VALUES ($1, $2) + INSERT INTO cluster_groups (id, name, tenant_id) + VALUES ($1, $2, $3) "#, ) .bind(id) .bind(name) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test cluster group"); @@ -340,21 +385,23 @@ pub async fn create_test_release( namespace_id: Uuid, repo_branch_id: Uuid, name: &str, + tenant_id: Uuid, ) -> Uuid { let id = Uuid::new_v4(); sqlx::query( r#" INSERT INTO releases ( id, namespace_id, name, path, repo_branch_id, - hash, version, git_sha + hash, version, git_sha, tenant_id ) - VALUES ($1, $2, $3, '/test/path', $4, 'hash123', 'v1.0', 'abc123') + VALUES ($1, $2, $3, '/test/path', $4, 'hash123', 'v1.0', 'abc123', $5) "#, ) .bind(id) .bind(namespace_id) .bind(name) .bind(repo_branch_id) + .bind(tenant_id) .execute(pool) .await .expect("Failed to create test release"); @@ -362,13 +409,13 @@ pub async fn create_test_release( } // Bulk fixture creation for performance testing -pub async fn create_bulk_fixtures(pool: &PgPool, count: usize) -> BulkFixtures { +pub async fn create_bulk_fixtures(pool: &PgPool, count: usize, tenant_id: Uuid) -> BulkFixtures { println!("Creating {} bulk test fixtures...", count); // Create base cluster and repo - let cluster_id = create_test_cluster(pool).await; - let namespace_id = create_test_namespace(pool, cluster_id).await; - let (repo_id, branch_id) = create_test_repo(pool).await; + let cluster_id = create_test_cluster(pool, tenant_id).await; + let namespace_id = create_test_namespace(pool, cluster_id, tenant_id).await; + let (repo_id, branch_id) = create_test_repo(pool, tenant_id).await; let mut service_definition_ids = Vec::new(); let mut release_ids = Vec::new(); @@ -376,15 +423,22 @@ pub async fn create_bulk_fixtures(pool: &PgPool, count: usize) -> BulkFixtures { // Batch create build targets and releases for i in 0..count { let bt_id = - create_test_service_definition(pool, branch_id, &format!("service-{}", i)).await; + create_test_service_definition(pool, branch_id, &format!("service-{}", i), tenant_id) + .await; service_definition_ids.push(bt_id); - let rel_id = - create_test_release(pool, namespace_id, branch_id, &format!("release-{}", i)).await; + let rel_id = create_test_release( + pool, + namespace_id, + branch_id, + &format!("release-{}", i), + tenant_id, + ) + .await; release_ids.push(rel_id); } - println!("✓ Created {} build targets and releases", count); + println!("Created {} build targets and releases", count); BulkFixtures { cluster_id, diff --git a/hive-hq/api/tests/integration_tests.rs b/hive-hq/api/tests/integration_tests.rs index 63d6e9e..c9301c4 100644 --- a/hive-hq/api/tests/integration_tests.rs +++ b/hive-hq/api/tests/integration_tests.rs @@ -13,13 +13,20 @@ async fn test_sync_cluster_releases_basic_flow() { .await .expect("Failed to setup test environment"); - // Create test fixtures - let cluster_id = create_test_cluster(&env.pool).await; - let namespace_id = create_test_namespace(&env.pool, cluster_id).await; - let (_, branch_id) = create_test_repo(&env.pool).await; + // Create test fixtures with tenant_id + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; + let namespace_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; // Create initial release - let release_id = create_test_release(&env.pool, namespace_id, branch_id, "test-app").await; + let release_id = create_test_release( + &env.pool, + namespace_id, + branch_id, + "test-app", + env.tenant_id, + ) + .await; // Verify release was created let count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM releases WHERE id = $1") @@ -40,27 +47,28 @@ async fn test_transaction_atomicity_on_error() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; - let namespace_id = create_test_namespace(&env.pool, cluster_id).await; - let (_, branch_id) = create_test_repo(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; + let namespace_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; // Start a transaction let mut tx = env.pool.begin().await.expect("Failed to start transaction"); - // Insert a release within the transaction + // Insert a release within the transaction (includes tenant_id) let release_id = Uuid::new_v4(); sqlx::query( r#" INSERT INTO releases ( id, namespace_id, name, path, repo_branch_id, - hash, version, git_sha + hash, version, git_sha, tenant_id ) - VALUES ($1, $2, 'test-app', '/test/path', $3, 'hash123', 'v1.0', 'abc123') + VALUES ($1, $2, 'test-app', '/test/path', $3, 'hash123', 'v1.0', 'abc123', $4) "#, ) .bind(release_id) .bind(namespace_id) .bind(branch_id) + .bind(env.tenant_id) .execute(&mut *tx) .await .expect("Failed to insert release"); @@ -90,9 +98,9 @@ async fn test_batch_operations_within_limits() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; - let namespace_id = create_test_namespace(&env.pool, cluster_id).await; - let (_, branch_id) = create_test_repo(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; + let namespace_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; // Create 100 releases to test batch operations for i in 0..100 { @@ -101,6 +109,7 @@ async fn test_batch_operations_within_limits() { namespace_id, branch_id, &format!("test-app-{}", i), + env.tenant_id, ) .await; } @@ -141,17 +150,17 @@ async fn test_namespace_isolation() { .await .expect("Failed to setup test environment"); - let cluster_id = create_test_cluster(&env.pool).await; + let cluster_id = create_test_cluster(&env.pool, env.tenant_id).await; // Create two namespaces - let ns1_id = create_test_namespace(&env.pool, cluster_id).await; - let ns2_id = create_test_namespace(&env.pool, cluster_id).await; + let ns1_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; + let ns2_id = create_test_namespace(&env.pool, cluster_id, env.tenant_id).await; - let (_, branch_id) = create_test_repo(&env.pool).await; + let (_, branch_id) = create_test_repo(&env.pool, env.tenant_id).await; // Create releases in both namespaces - create_test_release(&env.pool, ns1_id, branch_id, "app-ns1").await; - create_test_release(&env.pool, ns2_id, branch_id, "app-ns2").await; + create_test_release(&env.pool, ns1_id, branch_id, "app-ns1", env.tenant_id).await; + create_test_release(&env.pool, ns2_id, branch_id, "app-ns2", env.tenant_id).await; // Verify namespace 1 only has its own release let ns1_count: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM releases WHERE namespace_id = $1") diff --git a/hive-hq/types/src/lib.rs b/hive-hq/types/src/lib.rs index 26b50eb..6234f75 100644 --- a/hive-hq/types/src/lib.rs +++ b/hive-hq/types/src/lib.rs @@ -552,6 +552,7 @@ pub struct DeleteId { #[cfg_attr(feature = "api", derive(sqlx::FromRow, utoipa::ToSchema))] pub struct Claim { pub email: String, + pub tenant_id: String, pub exp: usize, pub roles: Vec, } diff --git a/hive-hq/ui/src/App.tsx b/hive-hq/ui/src/App.tsx index ea3642e..4d6946f 100644 --- a/hive-hq/ui/src/App.tsx +++ b/hive-hq/ui/src/App.tsx @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { useEffect } from 'react'; import { ThemeProvider } from '@/contexts/ThemeContext'; +import { ConfigProvider } from '@/contexts/ConfigContext'; import { MainLayout } from '@/layouts'; import { AUTH_REDIRECT_EVENT } from '@/lib/api-client'; import { @@ -16,7 +17,9 @@ import { ClusterGroupsPage, ClusterGroupDetailPage, ReleaseDetailPage, - LoginPage + LoginPage, + RegisterPage, + SettingsPage } from '@/pages'; const queryClient = new QueryClient({ @@ -50,29 +53,33 @@ function App() { return ( - - - - {/* Login page - outside main layout */} - } /> + + + + + {/* Public routes - outside main layout */} + } /> + } /> - {/* Main app routes */} - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> -

404 - Not Found

} /> -
-
-
+ {/* Main app routes */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> +

404 - Not Found

} /> +
+
+
+
); diff --git a/hive-hq/ui/src/components/Alert.tsx b/hive-hq/ui/src/components/Alert.tsx index b70bd28..7e29df0 100644 --- a/hive-hq/ui/src/components/Alert.tsx +++ b/hive-hq/ui/src/components/Alert.tsx @@ -5,7 +5,8 @@ type AlertType = 'success' | 'error' | 'warning' | 'info'; interface AlertProps { type: AlertType; title?: string; - message: string; + message?: string; + children?: React.ReactNode; onDismiss?: () => void; } @@ -36,7 +37,7 @@ const alertStyles: Record
{title &&

{title}

} -

{message}

+ {message &&

{message}

} + {children &&
{children}
}
{onDismiss && (
diff --git a/hive-hq/ui/src/contexts/ConfigContext.tsx b/hive-hq/ui/src/contexts/ConfigContext.tsx new file mode 100644 index 0000000..fe7b1d4 --- /dev/null +++ b/hive-hq/ui/src/contexts/ConfigContext.tsx @@ -0,0 +1,114 @@ +import { createContext, useContext, useEffect, useState, type ReactNode } from 'react'; +import apiClient from '@/lib/api-client'; + +interface AppConfig { + version: string; + baseDomain: string | null; +} + +interface ConfigContextType { + config: AppConfig | null; + loading: boolean; + error: string | null; +} + +const ConfigContext = createContext(undefined); + +export function ConfigProvider({ children }: { children: ReactNode }) { + const [config, setConfig] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchConfig = async () => { + try { + const response = await apiClient.get('/config'); + setConfig({ + version: response.data.version, + baseDomain: response.data.base_domain ?? null, + }); + } catch (err) { + console.error('Failed to fetch app config:', err); + setError('Failed to load configuration'); + } finally { + setLoading(false); + } + }; + + fetchConfig(); + }, []); + + return ( + + {children} + + ); +} + +export function useConfig(): ConfigContextType { + const context = useContext(ConfigContext); + if (context === undefined) { + throw new Error('useConfig must be used within a ConfigProvider'); + } + return context; +} + +/** + * Extract tenant slug from the current hostname using the configured base domain. + * + * With baseDomain = "beecd.example.com": + * - "tenant1.beecd.example.com" → "tenant1" + * - "beecd.example.com" → null (base domain, no tenant) + * + * Without baseDomain (fallback): + * - "tenant1.localhost" → "tenant1" + * - "localhost" → null + */ +export function useCurrentTenant(): string | null { + const { config } = useConfig(); + const host = window.location.hostname.toLowerCase(); + + if (config?.baseDomain) { + const base = config.baseDomain.toLowerCase(); + // If host ends with .baseDomain, extract the subdomain + if (host.endsWith(`.${base}`)) { + const prefix = host.slice(0, host.length - base.length - 1); + // Get the rightmost label (closest to base domain) + const parts = prefix.split('.'); + return parts[parts.length - 1] || null; + } + // If host equals baseDomain exactly, no tenant + if (host === base) { + return null; + } + } + + // Fallback: first label if not localhost + const parts = host.split('.'); + if (parts.length >= 2 && parts[0] !== 'localhost') { + return parts[0]; + } + + return null; +} + +/** + * Construct a URL for a tenant subdomain. + * Uses the configured base domain if available, otherwise uses current hostname. + */ +export function useTenantUrl(): (slug: string, path?: string) => string { + const { config } = useConfig(); + + return (slug: string, path: string = '/') => { + const protocol = window.location.protocol; + const port = window.location.port ? `:${window.location.port}` : ''; + + if (config?.baseDomain) { + return `${protocol}//${slug}.${config.baseDomain}${port}${path}`; + } + + // Fallback: use current hostname + const baseDomain = window.location.hostname; + return `${protocol}//${slug}.${baseDomain}${port}${path}`; + }; +} diff --git a/hive-hq/ui/src/layouts/MainLayout.tsx b/hive-hq/ui/src/layouts/MainLayout.tsx index 0a2fbef..34e2083 100644 --- a/hive-hq/ui/src/layouts/MainLayout.tsx +++ b/hive-hq/ui/src/layouts/MainLayout.tsx @@ -8,7 +8,8 @@ import { Menu, X, User, - LogOut + LogOut, + Settings } from 'lucide-react'; import { useEffect, useRef, useState } from 'react'; import { ThemeToggle } from '@/components'; @@ -21,6 +22,7 @@ const navigation = [ { name: 'Services', href: '/services', icon: Layers }, { name: 'Repositories', href: '/repos', icon: FolderGit2 }, { name: 'Cluster Groups', href: '/cluster-groups', icon: GitBranch }, + { name: 'Settings', href: '/settings', icon: Settings }, ]; export function MainLayout() { @@ -37,7 +39,7 @@ export function MainLayout() { queryKey: ['auth', 'me'], queryFn: async () => { const res = await apiClient.get('/auth/me'); - return res.data as { username?: string }; + return res.data as { username?: string; tenant_name?: string; tenant_id?: string }; }, retry: false, }); @@ -183,9 +185,6 @@ export function MainLayout() { ); })} -
- -
@@ -235,11 +234,22 @@ export function MainLayout() { className={`absolute right-0 top-full pt-2 ${userMenuOpen ? 'block' : 'hidden'}`} ref={userMenuDropdownRef} > -
-
+
+
{me?.username || 'User'}
+ {me?.tenant_name && ( +
+ {me.tenant_name} +
+ )} +
+
+
+ Theme +
+
-
- -
diff --git a/hive-hq/ui/src/pages/ClusterDetailPage.tsx b/hive-hq/ui/src/pages/ClusterDetailPage.tsx index 2705092..e004841 100644 --- a/hive-hq/ui/src/pages/ClusterDetailPage.tsx +++ b/hive-hq/ui/src/pages/ClusterDetailPage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react'; import { useParams, useNavigate, Link } from 'react-router-dom'; -import { ArrowLeft, RefreshCw, Server, Layers, AlertTriangle, Activity, Trash2, Plus, FileCode, X, Package, Eye, GitCompare } from 'lucide-react'; +import { ArrowLeft, RefreshCw, Server, Layers, AlertTriangle, Activity, Trash2, FileCode, X, Package, Eye, GitCompare } from 'lucide-react'; import { PageHeader, DataTable, Alert, Modal, ConfirmModal } from '@/components'; import { useCluster, @@ -798,6 +798,7 @@ export function ClusterDetailPage() { namespaceId: editingNamespace.namespace_id, serviceDefinitionIds }); + setEditingNamespace(null); refetchNamespaces(); refetchReleases(); } catch (err) { @@ -812,7 +813,14 @@ export function ClusterDetailPage() { namespaceId: editingNamespace.namespace_id, serviceName }); - setEditingNamespace(null); + // Update the local editingNamespace state to reflect the removal + setEditingNamespace(prev => { + if (!prev) return null; + return { + ...prev, + service_names: (prev.service_names ?? []).filter(name => name !== serviceName) + }; + }); refetchNamespaces(); refetchReleases(); } catch (err) { @@ -1248,6 +1256,68 @@ export function ClusterDetailPage() { )} + {/* Releases Section */} +
+
+
+

+ Releases ({releases.length}) +

+
+
+ r.data.id} + isLoading={loadingReleases} + emptyMessage="No releases found for this cluster" + searchPlaceholder="Search releases..." + isLoadingMore={loadingMoreReleases} + allLoaded={allReleasesLoaded} + totalItems={releases.length} + /> +
+ + {/* Namespaces Section */} +
+
+

+ Namespaces ({namespaces.length}) +

+
+ {namespaces.length === 0 ? ( +
+ +

No namespaces registered yet

+

+ Namespaces are automatically registered when the agent detects them in your cluster. +

+

To register a namespace:

+
    +
  1. Add the label beecd.io/enabled=true to your namespace
  2. +
  3. The agent will automatically discover and register it on its next sync
  4. +
+

+ Example: kubectl label namespace my-namespace beecd.io/enabled=true +

+
+
+ ) : ( + ns.namespace_id} + isLoading={loadingNamespaces} + emptyMessage="No namespaces found for this cluster" + searchPlaceholder="Search namespaces..." + isLoadingMore={loadingMoreNamespaces} + allLoaded={allNamespacesLoaded} + totalItems={namespaces.length} + onRowClick={(ns) => setEditingNamespace(ns)} + /> + )} +
+ {/* Cluster Groups */} {groups && groups.length > 0 && (
@@ -1288,58 +1358,6 @@ export function ClusterDetailPage() { />
- {/* Releases Section */} -
-
-
-

- Releases ({releases.length}) -

-
-
- r.data.id} - isLoading={loadingReleases} - emptyMessage="No releases found for this cluster" - searchPlaceholder="Search releases..." - isLoadingMore={loadingMoreReleases} - allLoaded={allReleasesLoaded} - totalItems={releases.length} - /> -
- - {/* Namespaces Section */} -
-
-
-

- Namespaces ({namespaces.length}) -

- -
-
- ns.namespace_id} - isLoading={loadingNamespaces} - emptyMessage="No namespaces found for this cluster" - searchPlaceholder="Search namespaces..." - isLoadingMore={loadingMoreNamespaces} - allLoaded={allNamespacesLoaded} - totalItems={namespaces.length} - onRowClick={(ns) => setEditingNamespace(ns)} - /> -
- {/* Edit Namespace Services Modal */} (null); - const [mode, setMode] = useState<'login' | 'bootstrap'>('login'); - const [bootstrapAvailable, setBootstrapAvailable] = useState(false); - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); + const { loading: configLoading } = useConfig(); + const tenantSlug = useCurrentTenant(); + const getTenantUrl = useTenantUrl(); + const isBaseDomain = tenantSlug === null; - useEffect(() => { - let cancelled = false; - (async () => { - try { - const res = await apiClient.get('/auth/bootstrap/status'); - const required = !!res.data?.bootstrap_required; - if (!cancelled) { - setBootstrapAvailable(required); - if (!required && mode === 'bootstrap') { - setMode('login'); - } - } - } catch { - // If status check fails, default to hiding bootstrap. It's safer. - if (!cancelled) { - setBootstrapAvailable(false); - if (mode === 'bootstrap') { - setMode('login'); - } - } - } - })(); + // Derive step from isBaseDomain - recalculates when config loads + const step = useMemo(() => isBaseDomain ? 'tenant' : 'credentials', [isBaseDomain]); + const [tenantName, setTenantName] = useState(''); - return () => { - cancelled = true; - }; - // mode is intentionally included so we can force-switch back to login if needed. - }, [mode]); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); @@ -48,17 +27,26 @@ export function LoginPage() { setError(null); try { - const path = mode === 'bootstrap' ? '/auth/bootstrap' : '/auth/login'; - await apiClient.post(path, { + // Base domain two-step flow: redirect to tenant subdomain + if (isBaseDomain && step === 'tenant') { + const slug = tenantName.trim().toLowerCase().replace(/[^a-z0-9-]/g, ''); + if (!slug) { + setError('Please enter a valid tenant name'); + setLoading(false); + return; + } + // Redirect to tenant subdomain using configured base domain + window.location.href = getTenantUrl(slug, '/login'); + return; + } + + // Actual login (either from tenant subdomain or after redirect) + await apiClient.post('/auth/login', { username: username.trim(), password, }); navigate('/'); } catch (err: any) { - if (mode === 'bootstrap' && err?.response?.status === 409) { - setBootstrapAvailable(false); - setMode('login'); - } const msg = err?.response?.data && typeof err.response.data === 'string' ? err.response.data @@ -70,6 +58,15 @@ export function LoginPage() { } }; + // Show loading while config is being fetched + if (configLoading) { + return ( +
+
Loading...
+
+ ); + } + return (
{/* Theme toggle in top right */} @@ -93,59 +90,119 @@ export function LoginPage() {
-
-

- {mode === 'bootstrap' ? 'Initial Setup' : 'Sign in'} -

- {bootstrapAvailable && ( - - )} -
- -
-
- - setUsername(e.target.value)} - autoComplete="username" - className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" - /> -
-
- - setPassword(e.target.value)} - autoComplete={mode === 'bootstrap' ? 'new-password' : 'current-password'} - className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" - /> -
- - -
+ {isBaseDomain && step === 'tenant' ? ( + // Step 1: Ask for tenant name at base domain + <> +

+ Enter your tenant +

+
+
+ + setTenantName(e.target.value)} + placeholder="your-company" + autoFocus + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+ +
+
+ +
+ + ) : ( + // Step 2: Username/password (either on tenant subdomain or after redirect) + <> +
+

+ Sign in +

+
+ +
+
+ + setUsername(e.target.value)} + autoComplete="username" + autoFocus + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+
+ + setPassword(e.target.value)} + autoComplete="current-password" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+ + +
+ + {!isBaseDomain && ( +
+ +
+ )} + + {isBaseDomain && ( +
+ +
+ )} + + )}
diff --git a/hive-hq/ui/src/pages/RegisterPage.tsx b/hive-hq/ui/src/pages/RegisterPage.tsx new file mode 100644 index 0000000..7b597cf --- /dev/null +++ b/hive-hq/ui/src/pages/RegisterPage.tsx @@ -0,0 +1,191 @@ +import { useState, type FormEvent } from 'react'; +import { useNavigate } from 'react-router-dom'; +import apiClient from '@/lib/api-client'; +import { ThemeToggle } from '@/components'; +import { useTenantUrl, useConfig } from '@/contexts/ConfigContext'; + +export function RegisterPage() { + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + + const { loading: configLoading } = useConfig(); + const getTenantUrl = useTenantUrl(); + + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [confirmPassword, setConfirmPassword] = useState(''); + const [tenantName, setTenantName] = useState(''); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(null); + setSuccess(null); + + if (password !== confirmPassword) { + setError('Passwords do not match'); + setLoading(false); + return; + } + + if (!email.includes('@')) { + setError('Username must be a valid email address'); + setLoading(false); + return; + } + + try { + const res = await apiClient.post('/api/tenants/register', { + username: email.trim(), + password, + tenant_name: tenantName.trim(), + }); + + const domain = res.data?.domain; + if (domain) { + // Extract slug from domain (the domain from API is just the slug) + const slug = domain.split('.')[0]; + setSuccess(`Tenant created! Redirecting...`); + + // Redirect to tenant subdomain after brief delay using configured base domain + setTimeout(() => { + window.location.href = getTenantUrl(slug, '/'); + }, 2000); + } else { + navigate('/login'); + } + } catch (err: any) { + const msg = + err?.response?.data && typeof err.response.data === 'string' + ? err.response.data + : 'Registration failed'; + setError(msg); + console.error(err); + } finally { + setLoading(false); + } + }; + + // Show loading while config is being fetched + if (configLoading) { + return ( +
+
Loading...
+
+ ); + } + + return ( +
+
+ +
+ +
+
+

🐝 BeeCD

+

+ Create Your Tenant +

+

+ Register your organization on BeeCD +

+
+ + {error && ( +
+

{error}

+
+ )} + + {success && ( +
+

{success}

+
+ )} + +
+
+
+
+ + setTenantName(e.target.value)} + placeholder="e.g., Acme Inc" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +

+ Your organization's name +

+
+ +
+ + setEmail(e.target.value)} + placeholder="admin@example.com" + autoComplete="email" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+ +
+ + setPassword(e.target.value)} + autoComplete="new-password" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+ +
+ + setConfirmPassword(e.target.value)} + autoComplete="new-password" + className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> +
+ + +
+ +
+ +
+
+
+
+
+ ); +} diff --git a/hive-hq/ui/src/pages/ReposPage.tsx b/hive-hq/ui/src/pages/ReposPage.tsx index 62e211e..7a2b8a0 100644 --- a/hive-hq/ui/src/pages/ReposPage.tsx +++ b/hive-hq/ui/src/pages/ReposPage.tsx @@ -58,7 +58,8 @@ export function ReposPage() { const host = inferHost(newUrl); const needsProvider = !!host && host !== 'github.com'; - const handleAddRepo = async () => { + const handleAddRepo = async (e?: React.FormEvent) => { + e?.preventDefault(); if (!newUrl.trim()) return; if (needsProvider && !selectedProvider) return; @@ -186,7 +187,7 @@ export function ReposPage() { : 'Add a GitHub repository to track for deployments.'}

-
+
@@ -220,7 +222,7 @@ export function ReposPage() {
)} - + {createRepoMutation.isError && (
@@ -234,12 +236,14 @@ export function ReposPage() {
+ )} +
+ + {hasGithubToken ? ( +
+ ✓ Configured (created:{' '} + {new Date( + secrets?.find((s) => s.purpose === 'github_token')?.created_at || '' + ).toLocaleString()} + ) +
+ ) : ( +
+ setGithubToken(e.target.value)} + placeholder="ghp_xxxxxxxxxxxx" + className="w-full px-3 py-2 mb-2 border border-gray-300 dark:border-gray-600 rounded-md shadow-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-blue-500 focus:border-blue-500" + /> + +
+ )} +
+ + {/* PGP Key */} +
+
+

+ PGP Private Key (SOPS) +

+ {hasPgpKey && ( + + )} +
+ + {hasPgpKey ? ( +
+ ✓ Configured (created:{' '} + {new Date( + secrets?.find((s) => s.purpose === 'pgp_private_key')?.created_at || '' + ).toLocaleString()} + ) +
+ ) : ( +
+