diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a4f4b19 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +target/ +plugins/ \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..ba02e78 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,23 @@ +on: + push: + branches: + - main + pull_request: + branches: + - main + +name: Checks + +permissions: + contents: read + +jobs: + licenses: + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + FORCE_COLOR: 1 + steps: + - uses: actions/checkout@v4 + - name: Check license headers + uses: korandoru/hawkeye@v5 \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000..2552dc1 --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -0,0 +1,94 @@ +name: Docker + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + push: + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0 + with: + cosign-release: 'v2.2.4' + + # Set up BuildKit Docker container builder to be able to build + # multi-platform images and export cache + # https://github.com/docker/setup-buildx-action + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + # This will only write to the public Rekor transparency log when the Docker + # repository is public to avoid leaking data. If you would like to publish + # transparency data even for private images, pass --force to cosign below. + # https://github.com/sigstore/cosign + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + # https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + # This step uses the identity token to provision an ephemeral certificate + # against the sigstore community Fulcio instance. + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dda22ed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,281 @@ +# Copyright 2022-2024, axodotdev +# SPDX-License-Identifier: MIT or Apache-2.0 +# +# CI that: +# +# * checks for a Git Tag that looks like a release +# * builds artifacts with cargo-dist (archives, installers, hashes) +# * uploads those artifacts to temporary workflow zip +# * on success, uploads the artifacts to a GitHub Release +# +# Note that the GitHub Release will be created with a generated +# title/body based on your changelogs. + +name: Release + +permissions: + contents: write + id-token: write + attestations: write + +# This task will run whenever you push a git tag that looks like a version +# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc. +# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where +# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION +# must be a Cargo-style SemVer Version (must have at least major.minor.patch). +# +# If PACKAGE_NAME is specified, then the announcement will be for that +# package (erroring out if it doesn't have the given version or isn't cargo-dist-able). +# +# If PACKAGE_NAME isn't specified, then the announcement will be for all +# (cargo-dist-able) packages in the workspace with that version (this mode is +# intended for workspaces with only one dist-able package, or with all dist-able +# packages versioned/released in lockstep). +# +# If you push multiple tags at once, separate instances of this workflow will +# spin up, creating an independent announcement for each one. However, GitHub +# will hard limit this to 3 tags per commit, as it will assume more tags is a +# mistake. +# +# If there's a prerelease-style suffix to the version, then the release(s) +# will be marked as a prerelease. +on: + pull_request: + push: + tags: + - '**[0-9]+.[0-9]+.[0-9]+*' + +jobs: + # Run 'cargo dist plan' (or host) to determine what tasks we need to do + plan: + runs-on: "ubuntu-20.04" + outputs: + val: ${{ steps.plan.outputs.manifest }} + tag: ${{ !github.event.pull_request && github.ref_name || '' }} + tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }} + publishing: ${{ !github.event.pull_request }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cargo-dist + # we specify bash to get pipefail; it guards against the `curl` command + # failing. otherwise `sh` won't catch that `curl` returned non-0 + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.16.0/cargo-dist-installer.sh | sh" + # sure would be cool if github gave us proper conditionals... + # so here's a doubly-nested ternary-via-truthiness to try to provide the best possible + # functionality based on whether this is a pull_request, and whether it's from a fork. + # (PRs run on the *source* but secrets are usually on the *target* -- that's *good* + # but also really annoying to build CI around when it needs secrets to work right.) + - id: plan + run: | + cargo dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json + echo "cargo dist ran successfully" + cat plan-dist-manifest.json + echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v4 + with: + name: artifacts-plan-dist-manifest + path: plan-dist-manifest.json + + # Build and packages all the platform-specific things + build-local-artifacts: + name: build-local-artifacts (${{ join(matrix.targets, ', ') }}) + # Let the initial task tell us to not run (currently very blunt) + needs: + - plan + if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }} + strategy: + fail-fast: false + # Target platforms/runners are computed by cargo-dist in create-release. + # Each member of the matrix has the following arguments: + # + # - runner: the github runner + # - dist-args: cli flags to pass to cargo dist + # - install-dist: expression to run to install cargo-dist on the runner + # + # Typically there will be: + # - 1 "global" task that builds universal installers + # - N "local" tasks that build each platform's binaries and platform-specific installers + matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }} + runs-on: ${{ matrix.runner }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json + steps: + - name: enable windows longpaths + run: | + git config --global core.longpaths true + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: swatinem/rust-cache@v2 + with: + key: ${{ join(matrix.targets, '-') }} + cache-provider: ${{ matrix.cache_provider }} + - name: Install cargo-dist + run: ${{ matrix.install_dist }} + # Get the dist-manifest + - name: Fetch local artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - name: Install dependencies + run: | + ${{ matrix.packages_install }} + - name: Build artifacts + run: | + # Actually do builds and make zips and whatnot + cargo dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json + echo "cargo dist ran successfully" + - name: Attest + uses: actions/attest-build-provenance@v1 + with: + subject-path: "target/distrib/*${{ join(matrix.targets, ', ') }}*" + - id: cargo-dist + name: Post-build + # We force bash here just because github makes it really hard to get values up + # to "real" actions without writing to env-vars, and writing to env-vars has + # inconsistent syntax between shell and powershell. + shell: bash + run: | + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v4 + with: + name: artifacts-build-local-${{ join(matrix.targets, '_') }} + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + + # Build and package all the platform-agnostic(ish) things + build-global-artifacts: + needs: + - plan + - build-local-artifacts + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cargo-dist + shell: bash + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.16.0/cargo-dist-installer.sh | sh" + # Get all the local artifacts for the global tasks to use (for e.g. checksums) + - name: Fetch local artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + - id: cargo-dist + shell: bash + run: | + cargo dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json + echo "cargo dist ran successfully" + + # Parse out what we just built and upload it to scratch storage + echo "paths<> "$GITHUB_OUTPUT" + jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + cp dist-manifest.json "$BUILD_MANIFEST_NAME" + - name: "Upload artifacts" + uses: actions/upload-artifact@v4 + with: + name: artifacts-build-global + path: | + ${{ steps.cargo-dist.outputs.paths }} + ${{ env.BUILD_MANIFEST_NAME }} + # Determines if we should publish/announce + host: + needs: + - plan + - build-local-artifacts + - build-global-artifacts + # Only run if we're "publishing", and only if local and global didn't fail (skipped is fine) + if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: "ubuntu-20.04" + outputs: + val: ${{ steps.host.outputs.manifest }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Install cargo-dist + run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.16.0/cargo-dist-installer.sh | sh" + # Fetch artifacts from scratch-storage + - name: Fetch artifacts + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: target/distrib/ + merge-multiple: true + # This is a harmless no-op for GitHub Releases, hosting for that happens in "announce" + - id: host + shell: bash + run: | + cargo dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json + echo "artifacts uploaded and released successfully" + cat dist-manifest.json + echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" + - name: "Upload dist-manifest.json" + uses: actions/upload-artifact@v4 + with: + # Overwrite the previous copy + name: artifacts-dist-manifest + path: dist-manifest.json + + # Create a GitHub Release while uploading all files to it + announce: + needs: + - plan + - host + # use "always() && ..." to allow us to wait for all publish jobs while + # still allowing individual publish jobs to skip themselves (for prereleases). + # "host" however must run to completion, no skipping allowed! + if: ${{ always() && needs.host.result == 'success' }} + runs-on: "ubuntu-20.04" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: "Download GitHub Artifacts" + uses: actions/download-artifact@v4 + with: + pattern: artifacts-* + path: artifacts + merge-multiple: true + - name: Cleanup + run: | + # Remove the granular manifests + rm -f artifacts/*-dist-manifest.json + - name: Create GitHub Release + env: + PRERELEASE_FLAG: "${{ fromJson(needs.host.outputs.val).announcement_is_prerelease && '--prerelease' || '' }}" + ANNOUNCEMENT_TITLE: "${{ fromJson(needs.host.outputs.val).announcement_title }}" + ANNOUNCEMENT_BODY: "${{ fromJson(needs.host.outputs.val).announcement_github_body }}" + run: | + # Write and read notes from a file to avoid quoting breaking things + echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt + + gh release create "${{ needs.plan.outputs.tag }}" --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" $PRERELEASE_FLAG + gh release upload "${{ needs.plan.outputs.tag }}" artifacts/* diff --git a/.gitignore b/.gitignore index 92d8cc1..163ae7a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ rules/ lgc.yaml .logcraft/ # Ignore Wasm components bindings (generated at compile time) -plugins/**/src/bindings.rs \ No newline at end of file +/plugins/**/src/bindings.rs \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5358c9c..a410c79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -244,7 +244,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.36.0", + "object", "rustc-demangle", ] @@ -654,18 +654,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29daf137addc15da6bab6eae2c4a11e274b1d270bf2759508e62f6145e863ef6" +checksum = "0b6b33d7e757a887989eb18b35712b2a67d96171ec3149d1bfb657b29b7b367c" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de619867d5de4c644b7fd9904d6e3295269c93d8a71013df796ab338681222d4" +checksum = "b9acf15cb22be42d07c3b57d7856329cb228b7315d385346149df2566ad5e4aa" dependencies = [ "bumpalo", "cranelift-bforest", @@ -685,33 +685,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f5cf277490037d8dae9513d35e0ee8134670ae4a964a5ed5b198d4249d7c10" +checksum = "e934d301392b73b3f8b0540391fb82465a0f179a3cee7c726482ac4727efcc97" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3e22ecad1123343a3c09ac6ecc532bb5c184b6fcb7888df0ea953727f79924" +checksum = "8afb2a2566b3d54b854dfb288b3b187f6d3d17d6f762c92898207eba302931da" [[package]] name = "cranelift-control" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53ca3ec6d30bce84ccf59c81fead4d16381a3ef0ef75e8403bc1e7385980da09" +checksum = "0100f33b704cdacd01ad66ff41f8c5030d57cbff078e2a4e49ab1822591299fa" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eabb8d36b0ca8906bec93c78ea516741cac2d7e6b266fa7b0ffddcc09004990" +checksum = "a8cfdc315e5d18997093e040a8d234bea1ac1e118a716d3e30f40d449e78207b" dependencies = [ "serde", "serde_derive", @@ -719,9 +719,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44b42630229e49a8cfcae90bdc43c8c4c08f7a7aa4618b67f79265cd2f996dd2" +checksum = "0f74b84f16af2e982b0c0c72233503d9d55cbfe3865dbe807ca28dc6642a28b5" dependencies = [ "cranelift-codegen", "log", @@ -731,15 +731,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "918d1e36361805dfe0b6cdfd5a5ffdb5d03fa796170c5717d2727cbe623b93a0" +checksum = "adf306d3dde705fb94bd48082f01d38c4ededc74293a4c007805f610bf08bc6e" [[package]] name = "cranelift-native" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75aea85a0d7e1800b14ce9d3f53adf8ad4d1ee8a9e23b0269bdc50285e93b9b3" +checksum = "1ea0ebdef7aff4a79bcbc8b6495f31315f16b3bf311152f472eaa8d679352581" dependencies = [ "cranelift-codegen", "libc", @@ -748,9 +748,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.108.1" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac491fd3473944781f0cf9528c90cc899d18ad438da21961a839a3a44d57dfb" +checksum = "d549108a1942065cdbac3bb96c2952afa0e1b9a3beff4b08c4308ac72257576d" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -758,7 +758,7 @@ dependencies = [ "itertools 0.12.1", "log", "smallvec", - "wasmparser 0.207.0", + "wasmparser 0.209.1", "wasmtime-types", ] @@ -916,6 +916,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common", + "subtle", ] [[package]] @@ -1162,6 +1163,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fluent" version = "0.16.1" @@ -1548,6 +1559,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "home" version = "0.5.9" @@ -1568,6 +1588,15 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-auth" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643c9bbf6a4ea8a656d6b4cd53d34f79e3f841ad5203c1a55fb7d761923bc255" +dependencies = [ + "memchr", +] + [[package]] name = "http-body" version = "1.0.0" @@ -1630,6 +1659,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1861,6 +1907,19 @@ dependencies = [ "serde", ] +[[package]] +name = "indicatif" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + [[package]] name = "inlinable_string" version = "0.1.15" @@ -2072,10 +2131,25 @@ dependencies = [ "tokio-util 0.6.10", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest 0.10.7", + "hmac", + "serde", + "serde_json", + "sha2 0.10.8", +] + [[package]] name = "kcl-language-server" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "chrono", @@ -2118,8 +2192,8 @@ dependencies = [ [[package]] name = "kclvm-api" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "futures", @@ -2150,15 +2224,15 @@ dependencies = [ "protoc-bin-vendored", "serde", "serde_json", - "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl?tag=v0.8.7)", + "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl)", "tempfile", "tokio", ] [[package]] name = "kclvm-ast" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "compiler_base_span", "kclvm-error", @@ -2172,8 +2246,8 @@ dependencies = [ [[package]] name = "kclvm-ast-pretty" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "compiler_base_macros", "compiler_base_session", @@ -2186,8 +2260,8 @@ dependencies = [ [[package]] name = "kclvm-compiler" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "ahash 0.7.8", "bit-set", @@ -2206,8 +2280,8 @@ dependencies = [ [[package]] name = "kclvm-config" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "ahash 0.7.8", "anyhow", @@ -2224,32 +2298,39 @@ dependencies = [ "ron", "serde", "serde_json", - "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl?tag=v0.8.7)", + "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl)", "toml 0.5.11", ] [[package]] name = "kclvm-driver" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", + "flate2", "glob", + "indexmap 2.2.6", "kclvm-ast", "kclvm-config", "kclvm-parser", "kclvm-runtime", "kclvm-utils", "notify 6.1.1", + "oci-distribution", + "once_cell", + "parking_lot 0.12.3", "serde", "serde_json", + "tar", + "tokio", "walkdir", ] [[package]] name = "kclvm-error" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "annotate-snippets", "anyhow", @@ -2261,15 +2342,18 @@ dependencies = [ "indexmap 1.9.3", "kclvm-runtime", "kclvm-span", + "kclvm-utils", + "serde", "serde_json", "termize", + "thiserror", "tracing", ] [[package]] name = "kclvm-evaluator" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "generational-arena", @@ -2282,8 +2366,8 @@ dependencies = [ [[package]] name = "kclvm-lexer" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "kclvm-error", "rustc_lexer", @@ -2292,8 +2376,8 @@ dependencies = [ [[package]] name = "kclvm-loader" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "indexmap 1.9.3", @@ -2308,8 +2392,8 @@ dependencies = [ [[package]] name = "kclvm-macros" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "proc-macro2", "quote", @@ -2319,8 +2403,8 @@ dependencies = [ [[package]] name = "kclvm-parser" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "bstr", @@ -2350,12 +2434,13 @@ dependencies = [ [[package]] name = "kclvm-query" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "compiler_base_macros", "compiler_base_session", + "fancy-regex", "indexmap 1.9.3", "kclvm-ast", "kclvm-ast-pretty", @@ -2363,12 +2448,14 @@ dependencies = [ "kclvm-parser", "kclvm-sema", "maplit", + "serde", + "serde_json", ] [[package]] name = "kclvm-runner" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "cc", @@ -2402,8 +2489,8 @@ dependencies = [ [[package]] name = "kclvm-runtime" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "ahash 0.7.8", "base64 0.13.1", @@ -2424,7 +2511,7 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl?tag=v0.8.7)", + "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl)", "sha1", "sha2 0.9.9", "unic-ucd-bidi", @@ -2435,8 +2522,8 @@ dependencies = [ [[package]] name = "kclvm-sema" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "ahash 0.7.8", "anyhow", @@ -2468,8 +2555,8 @@ dependencies = [ [[package]] name = "kclvm-span" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "compiler_base_span", "kclvm-macros", @@ -2479,8 +2566,8 @@ dependencies = [ [[package]] name = "kclvm-tools" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "compiler_base_session", @@ -2504,14 +2591,14 @@ dependencies = [ "regex", "rustc_lexer", "serde_json", - "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl?tag=v0.8.7)", + "serde_yaml 0.9.34+deprecated (git+https://github.com/kcl-lang/kcl)", "walkdir", ] [[package]] name = "kclvm-utils" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "anyhow", "fslock", @@ -2520,8 +2607,8 @@ dependencies = [ [[package]] name = "kclvm-version" -version = "0.8.7" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +version = "0.9.0" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "vergen", ] @@ -2529,7 +2616,7 @@ dependencies = [ [[package]] name = "kclvm_runtime_internal_macros" version = "0.5.0" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "proc-macro2", "quote", @@ -2575,17 +2662,23 @@ dependencies = [ "anyhow", "clap", "console", + "dashmap", + "dialoguer", "figment", "futures", - "inquire", + "indicatif", "kclvm-api", "logcraft-common", "logcraft-runtime", + "openssl", + "rayon", "serde", "serde_json", "serde_yaml_ng", "tokio", "tokio-util 0.7.11", + "tracing", + "tracing-subscriber", "url", "wasmtime", ] @@ -2676,23 +2769,28 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "console", "dashmap", "dialoguer", "futures", "inquire", "kclvm-api", + "kclvm-query", + "kclvm-sema", "logcraft-runtime", "rayon", "reqwest", "serde", "serde_json", "serde_yaml_ng", + "similar", "tempfile", "tokio", "tokio-util 0.7.11", "tracing", "tracing-subscriber", "url", + "uuid", "wasmtime", ] @@ -2710,12 +2808,13 @@ dependencies = [ "tokio", "tokio-native-tls", "tracing", - "wasmparser 0.210.0", + "tracing-subscriber", + "wasmparser 0.211.1", "wasmtime", "wasmtime-wasi", "wasmtime-wasi-http", "wit-component", - "wit-parser 0.210.0", + "wit-parser 0.211.1", ] [[package]] @@ -3007,11 +3106,17 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" -version = "0.33.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "crc32fast", "hashbrown 0.14.5", @@ -3020,12 +3125,39 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.0" +name = "oci-distribution" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "b95a2c51531af0cb93761f66094044ca6ea879320bccd35ab747ff3fcab3f422" dependencies = [ - "memchr", + "bytes", + "chrono", + "futures-util", + "http", + "http-auth", + "jwt", + "lazy_static", + "olpc-cjson", + "regex", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.8", + "thiserror", + "tokio", + "tracing", + "unicase", +] + +[[package]] +name = "olpc-cjson" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a" +dependencies = [ + "serde", + "serde_json", + "unicode-normalization", ] [[package]] @@ -3084,6 +3216,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-src" +version = "300.3.1+3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" +dependencies = [ + "cc", +] + [[package]] name = "openssl-sys" version = "0.9.102" @@ -3092,6 +3233,7 @@ checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", + "openssl-src", "pkg-config", "vcpkg", ] @@ -3334,6 +3476,12 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "postcard" version = "1.0.8" @@ -3408,7 +3556,7 @@ dependencies = [ [[package]] name = "proc_macro_crate" version = "0.1.0" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "proc-macro2", "quote", @@ -3472,7 +3620,7 @@ dependencies = [ [[package]] name = "prost-wkt" version = "0.4.1" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "chrono", "inventory", @@ -3486,7 +3634,7 @@ dependencies = [ [[package]] name = "prost-wkt-build" version = "0.4.1" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "heck 0.4.1", "prost", @@ -3498,7 +3646,7 @@ dependencies = [ [[package]] name = "prost-wkt-types" version = "0.4.1" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "chrono", "prost", @@ -3797,6 +3945,7 @@ dependencies = [ "http-body", "http-body-util", "hyper", + "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", @@ -3807,7 +3956,9 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", "rustls-pemfile", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", @@ -3815,6 +3966,7 @@ dependencies = [ "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tokio-util 0.7.11", "tower-service", "url", @@ -3822,6 +3974,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg", ] @@ -4260,7 +4413,7 @@ dependencies = [ [[package]] name = "serde_yaml" version = "0.9.34+deprecated" -source = "git+https://github.com/kcl-lang/kcl?tag=v0.8.7#55785916fdd2f0f019f6d86db384c50d6084a570" +source = "git+https://github.com/kcl-lang/kcl#6fd3a095760bb0e7349882194a71bf7a32fa45d9" dependencies = [ "indexmap 2.2.6", "itoa", @@ -4386,6 +4539,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" + [[package]] name = "siphasher" version = "0.3.11" @@ -4642,6 +4801,17 @@ dependencies = [ "winx", ] +[[package]] +name = "tar" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.14" @@ -4799,6 +4969,21 @@ dependencies = [ "zerovec", ] +[[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.38.0" @@ -5015,16 +5200,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -5035,15 +5210,12 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", - "serde", - "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", - "tracing-serde", ] [[package]] @@ -5219,6 +5391,15 @@ dependencies = [ "unic-common", ] +[[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-casing" version = "0.1.0" @@ -5231,6 +5412,15 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.11.0" @@ -5431,9 +5621,9 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "wasm-encoder" -version = "0.207.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d996306fb3aeaee0d9157adbe2f670df0236caf19f6728b221e92d0f27b3fe17" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" dependencies = [ "leb128", ] @@ -5447,11 +5637,21 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasm-encoder" +version = "0.211.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e7d931a1120ef357f32b74547646b6fa68ea25e377772b72874b131a9ed70d4" +dependencies = [ + "leb128", + "wasmparser 0.211.1", +] + [[package]] name = "wasm-metadata" -version = "0.210.0" +version = "0.211.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "012729d1294907fcb0866f08460ab95426a6d0b176a599619b84cac7653452b4" +checksum = "7f56bad1c68558c44e7f60be865117dc9d8c3a066bcf3b2232cb9a9858965fd5" dependencies = [ "anyhow", "indexmap 2.2.6", @@ -5459,8 +5659,8 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", ] [[package]] @@ -5478,22 +5678,23 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.207.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19bb9f8ab07616da582ef8adb24c54f1424c7ec876720b7da9db8ec0626c92c" +checksum = "07035cc9a9b41e62d3bb3a3815a66ab87c993c06fe1cf6b2a3f2a18499d937db" dependencies = [ "ahash 0.8.11", "bitflags 2.5.0", "hashbrown 0.14.5", "indexmap 2.2.6", "semver 1.0.23", + "serde", ] [[package]] name = "wasmparser" -version = "0.210.0" +version = "0.211.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7bbcd21e7581619d9f6ca00f8c4f08f1cacfe58bf63f83af57cd0476f1026f5" +checksum = "3189cc8a91f547390e2f043ca3b3e3fe0892f7d581767fd4e4b7f3dc3fe8e561" dependencies = [ "ahash 0.8.11", "bitflags 2.5.0", @@ -5505,19 +5706,19 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.207.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2d8a7b4dabb460208e6b4334d9db5766e84505038b2529e69c3d07ac619115" +checksum = "ceca8ae6eaa8c7c87b33c25c53bdf299f8c2a764aee1179402ff7652ef3a6859" dependencies = [ "anyhow", - "wasmparser 0.207.0", + "wasmparser 0.209.1", ] [[package]] name = "wasmtime" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f92a1370c66a0022e6d92dcc277e2c84f5dece19569670b8ce7db8162560d8b6" +checksum = "786d8b5e7a4d54917c5ebe555b9667337e5f93383f49bddaaeec2eba68093b45" dependencies = [ "addr2line 0.21.0", "anyhow", @@ -5537,7 +5738,7 @@ dependencies = [ "mach2", "memfd", "memoffset", - "object 0.33.0", + "object", "once_cell", "paste", "postcard", @@ -5551,8 +5752,8 @@ dependencies = [ "smallvec", "sptr", "target-lexicon", - "wasm-encoder 0.207.0", - "wasmparser 0.207.0", + "wasm-encoder 0.209.1", + "wasmparser 0.209.1", "wasmtime-asm-macros", "wasmtime-cache", "wasmtime-component-macro", @@ -5571,18 +5772,18 @@ dependencies = [ [[package]] name = "wasmtime-asm-macros" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dee8679c974a7f258c03d60d3c747c426ed219945b6d08cbc77fd2eab15b2d1" +checksum = "d697d99c341d4a9ffb72f3af7a02124d233eeb59aee010f36d88e97cca553d5e" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "wasmtime-cache" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00103ffaf7ee980f4e750fe272b6ada79d9901659892e457c7ca316b16df9ec" +checksum = "916610f9ae9a6c22deb25bba2e6247ba9f00b093d30620875203b91328a1adfa" dependencies = [ "anyhow", "base64 0.21.7", @@ -5600,9 +5801,9 @@ dependencies = [ [[package]] name = "wasmtime-component-macro" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cae30035f1cf97dcc6657c979cf39f99ce6be93583675eddf4aeaa5548509c" +checksum = "b29b462b068e73b5b27fae092a27f47e5937cabf6b26be2779c978698a52feca" dependencies = [ "anyhow", "proc-macro2", @@ -5610,20 +5811,20 @@ dependencies = [ "syn 2.0.66", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser 0.207.0", + "wit-parser 0.209.1", ] [[package]] name = "wasmtime-component-util" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7ae611f08cea620c67330925be28a96115bf01f8f393a6cbdf4856a86087134" +checksum = "f9d2912c53d9054984b380dfbd7579f9c3681b2a73b903a56bd71a1c4f175f1e" [[package]] name = "wasmtime-cranelift" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2909406a6007e28be964067167890bca4574bd48a9ff18f1fa9f4856d89ea40" +checksum = "a3975deafea000457ba84355c7c0fce0372937204f77026510b7b454f28a3a65" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -5635,19 +5836,19 @@ dependencies = [ "cranelift-wasm", "gimli 0.28.1", "log", - "object 0.33.0", + "object", "target-lexicon", "thiserror", - "wasmparser 0.207.0", + "wasmparser 0.209.1", "wasmtime-environ", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-environ" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e227f9ed2f5421473723d6c0352b5986e6e6044fde5410a274a394d726108f" +checksum = "f444e900e848b884d8a8a2949b6f5b92af642a3e663ff8fbe78731143a55be61" dependencies = [ "anyhow", "cpp_demangle", @@ -5655,14 +5856,14 @@ dependencies = [ "gimli 0.28.1", "indexmap 2.2.6", "log", - "object 0.33.0", + "object", "postcard", "rustc-demangle", "serde", "serde_derive", "target-lexicon", - "wasm-encoder 0.207.0", - "wasmparser 0.207.0", + "wasm-encoder 0.209.1", + "wasmparser 0.209.1", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -5670,9 +5871,9 @@ dependencies = [ [[package]] name = "wasmtime-fiber" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42edb392586d07038c1638e854382db916b6ca7845a2e6a7f8dc49e08907acdd" +checksum = "4ded58eb2d1bf0dcd2182d0ccd7055c4b10b50d711514f1d73f61515d0fa829d" dependencies = [ "anyhow", "cc", @@ -5685,11 +5886,11 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b26ef7914af0c0e3ca811bdc32f5f66fbba0fd21e1f8563350e8a7951e3598" +checksum = "9bc54198c6720f098210a85efb3ba8c078d1de4d373cdb6778850a66ae088d11" dependencies = [ - "object 0.33.0", + "object", "once_cell", "rustix", "wasmtime-versioned-export-macros", @@ -5697,9 +5898,9 @@ dependencies = [ [[package]] name = "wasmtime-jit-icache-coherence" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afe088f9b56bb353adaf837bf7e10f1c2e1676719dd5be4cac8e37f2ba1ee5bc" +checksum = "5afe2f0499542f9a4bcfa1b55bfdda803b6ade4e7c93c6b99e0f39dba44b0a91" dependencies = [ "anyhow", "cfg-if 1.0.0", @@ -5709,28 +5910,28 @@ dependencies = [ [[package]] name = "wasmtime-slab" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ff75cafffe47b04b036385ce3710f209153525b0ed19d57b0cf44a22d446460" +checksum = "0a7de1f2bec5bbb35d532e61c85c049dc84ae671df60492f90b954ecf21169e7" [[package]] name = "wasmtime-types" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f2fa462bfea3220711c84e2b549f147e4df89eeb49b8a2a3d89148f6cc4a8b1" +checksum = "412463e9000e14cf6856be48628d2213c20c153e29ffc22b036980c892ea6964" dependencies = [ "cranelift-entity", "serde", "serde_derive", "smallvec", - "wasmparser 0.207.0", + "wasmparser 0.209.1", ] [[package]] name = "wasmtime-versioned-export-macros" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4cedc5bfef3db2a85522ee38564b47ef3b7fc7c92e94cacbce99808e63cdd47" +checksum = "de5a9bc4f44ceeb168e9e8e3be4e0b4beb9095b468479663a9e24c667e36826f" dependencies = [ "proc-macro2", "quote", @@ -5739,9 +5940,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdbbe94245904d4c96c7c5f7b55bad896cc27908644efd9442063c0748b631fc" +checksum = "8abb1301089ed8e0b4840f539cba316a73ac382090f1b25d22d8c8eed8df49c7" dependencies = [ "anyhow", "async-trait", @@ -5770,9 +5971,9 @@ dependencies = [ [[package]] name = "wasmtime-wasi-http" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c05f0c23ba135aab39bd2cfe1d433744522591acec552f44842dbee589b0e2" +checksum = "315cadc284b808cfbd6be9295da4009144c106723f09b421ce6c6d89275cfdb7" dependencies = [ "anyhow", "async-trait", @@ -5793,16 +5994,16 @@ dependencies = [ [[package]] name = "wasmtime-winch" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b27054fed6be4f3800aba5766f7ef435d4220ce290788f021a08d4fa573108" +checksum = "ed4db238a0241df2d15f79ad17b3a37a27f2ea6cb885894d81b42ae107544466" dependencies = [ "anyhow", "cranelift-codegen", "gimli 0.28.1", - "object 0.33.0", + "object", "target-lexicon", - "wasmparser 0.207.0", + "wasmparser 0.209.1", "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", @@ -5810,14 +6011,14 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936a52ce69c28de2aa3b5fb4f2dbbb2966df304f04cccb7aca4ba56d915fda0" +checksum = "70dc077306b38288262e5ba01d4b21532a6987416cdc0aedf04bb06c22a68fdc" dependencies = [ "anyhow", "heck 0.4.1", "indexmap 2.2.6", - "wit-parser 0.207.0", + "wit-parser 0.209.1", ] [[package]] @@ -5884,9 +6085,9 @@ dependencies = [ [[package]] name = "wiggle" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89ea6f74ece6d1cfbd089783006b8eb69a0219ca83cad22068f0d9fa9df3f91" +checksum = "29830e5d01c182d24b94092c697aa7ab0ee97d22e78a2bf40ca91eae6ebca5c2" dependencies = [ "anyhow", "async-trait", @@ -5899,9 +6100,9 @@ dependencies = [ [[package]] name = "wiggle-generate" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36beda94813296ecaf0d91b7ada9da073fd41865ba339bdd3b7764e2e785b8e9" +checksum = "557567f2793508760cd855f7659b7a0b9dc4dbc451f53f1415d6943a15311ade" dependencies = [ "anyhow", "heck 0.4.1", @@ -5914,9 +6115,9 @@ dependencies = [ [[package]] name = "wiggle-macro" -version = "21.0.1" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b47d2b4442ce93106dba5d1a9c59d5f85b5732878bb3d0598d3c93c0d01b16b" +checksum = "cc26129a8aea20b62c961d1b9ab4a3c3b56b10042ed85d004f8678af0f21ba6e" dependencies = [ "proc-macro2", "quote", @@ -5957,9 +6158,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.19.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dc69899ccb2da7daa4df31426dcfd284b104d1a85e1dae35806df0c46187f87" +checksum = "85c6915884e731b2db0d8cf08cb64474cb69221a161675fd3c135f91febc3daa" dependencies = [ "anyhow", "cranelift-codegen", @@ -5967,7 +6168,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.207.0", + "wasmparser 0.209.1", "wasmtime-cranelift", "wasmtime-environ", ] @@ -6232,9 +6433,9 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.210.0" +version = "0.211.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a450bdb5d032acf1fa0865451fa0c6f50e62f2d31eaa8dba967c2e2d068694a4" +checksum = "079a38b7d679867424bf2bcbdd553a2acf364525307e43dfb910fa4a2c6fd9f2" dependencies = [ "anyhow", "bitflags 2.5.0", @@ -6243,17 +6444,17 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.210.0", + "wasm-encoder 0.211.1", "wasm-metadata", - "wasmparser 0.210.0", - "wit-parser 0.210.0", + "wasmparser 0.211.1", + "wit-parser 0.211.1", ] [[package]] name = "wit-parser" -version = "0.207.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c83dab33a9618d86cfe3563cc864deffd08c17efc5db31a3b7cd1edeffe6e1" +checksum = "3e79b9e3c0b6bb589dec46317e645851e0db2734c44e2be5e251b03ff4a51269" dependencies = [ "anyhow", "id-arena", @@ -6264,14 +6465,14 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.207.0", + "wasmparser 0.209.1", ] [[package]] name = "wit-parser" -version = "0.210.0" +version = "0.211.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a965cbd439af19a4b44a54a97ab8957d86f02d01320efc9e31c1d3605c6710" +checksum = "a3cc90c50c7ec8a824b5d2cddddff13b2dc12b7a96bf8684d11474223c2ea22f" dependencies = [ "anyhow", "id-arena", @@ -6282,7 +6483,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.210.0", + "wasmparser 0.211.1", ] [[package]] @@ -6309,6 +6510,17 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index d20904c..af36e9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,3 @@ -# Copyright (c) 2023 LogCraft, SAS. -# SPDX-License-Identifier: MPL-2.0 - [package] name = "lgc" version = { workspace = true } @@ -34,6 +31,10 @@ lto = true codegen-units = 1 strip = "debuginfo" +# The profile that 'cargo dist' will build with +[profile.dist] +inherits = "release" + [dependencies] anyhow.workspace = true futures.workspace = true @@ -44,16 +45,24 @@ wasmtime.workspace = true tokio = { workspace = true, features = ["full"] } tokio-util.workspace = true kclvm-api.workspace = true -inquire.workspace = true +dashmap.workspace = true +rayon.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +console.workspace = true +dialoguer.workspace = true +indicatif = "0.17" serde_json = "1.0" -console = "0.15" clap = { version = "4.5", features = ["derive", "env", "cargo"] } figment = { version = "0.10", features = ["yaml", "env"] } # Local dependencies -logcraft-common = { path = "crates/common" } -logcraft-runtime = { path = "crates/runtime" } +logcraft-common = { path = "crates/common", version = "0.1.0" } +logcraft-runtime = { path = "crates/runtime", version = "0.1.0" } + +[target.x86_64-unknown-linux-gnu.dependencies] +openssl = { version = "0.10", features = ["vendored"] } [workspace] members = ["crates/*"] @@ -70,15 +79,35 @@ reqwest = "0.12.4" futures = "0.3" http = "1.1" inquire = "0.7" -kclvm-api = { git = "https://github.com/kcl-lang/kcl", tag= "v0.8.7", version = "0.8.7" } -wasmtime = "21.0" -wasmtime-wasi = "21.0" -wasmtime-wasi-http = "21.0" +console = "0.15" +dialoguer = "0.11" +rayon = "1.10.0" +dashmap = "5.5" tracing = {version = "0.1", features = ["log"] } -tracing-subscriber = {version = "0.3", features = [ - "ansi", - "env-filter", - "fmt", - "json", - "std", -] } +tracing-subscriber = {version = "0.3", features = ["env-filter", "fmt", "std"] } + +kclvm-api = { git = "https://github.com/kcl-lang/kcl", version = "0.9.0" } +kclvm-query = { git = "https://github.com/kcl-lang/kcl", version = "0.9.0" } +kclvm-sema = { git = "https://github.com/kcl-lang/kcl", version = "0.9.0" } + +# Wasm related +wasmtime = "22.0" +wasmtime-wasi = "22.0" +wasmtime-wasi-http = "22.0" + +# Config for 'cargo dist' +[workspace.metadata.dist] +# The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) +cargo-dist-version = "0.16.0" +# CI backends to support +ci = "github" +# The installers to generate for each app +installers = [] +# Target platforms to build apps for (Rust target-triple syntax) +targets = ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"] +# Publish jobs to run in CI +pr-run-mode = "plan" +# Whether to install an updater program +install-updater = false +# Whether to enable GitHub Attestations +github-attestations = true diff --git a/Dockerfile b/Dockerfile index 9d0428f..fb39565 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ # ----- FROM docker.io/library/rust:1.78-alpine as builder -# TODO: Add `git clone` for kcl github repo (forked version until PR valid and in release) -# # Set `SYSROOT` to a dummy path (default is /usr) because pkg-config-rs *always* -# # links those located in that path dynamically but we want static linking, c.f. -# # https://github.com/rust-lang/pkg-config-rs/blob/54325785816695df031cef3b26b6a9a203bbc01b/src/lib.rs#L613 + +# Set `SYSROOT` to a dummy path (default is /usr) because pkg-config-rs *always* +# links those located in that path dynamically but we want static linking, c.f. +# https://github.com/rust-lang/pkg-config-rs/blob/54325785816695df031cef3b26b6a9a203bbc01b/src/lib.rs#L613 ENV SYSROOT=/dummy # Install dependencies @@ -23,19 +23,20 @@ COPY . /wd RUN cargo build --bin lgc --release # ----- -# FROM alpine -FROM cgr.dev/chainguard/static -ARG version="0.1.0" -ARG release="v0.1.0" +FROM cgr.dev/chainguard/wolfi-base + +ARG description="Easily build Detection-as-Code pipelines for modern security tools (SIEM, EDR, XDR, ...)" LABEL name="lgc" \ maintainer="dev@logcraft.io" \ vendor="LogCraft" \ license="MPL-2.0" \ - version=${version} \ - release=${release} \ summary="Detection-as-Code CLI" \ - description="Easily build Detection-as-Code pipelines for modern security tools (SIEM, EDR, XDR, ...)" + description=${description} +LABEL org.opencontainers.image.description ${description} + +WORKDIR /wd +RUN chown -R nonroot.nonroot /wd/ +USER nonroot COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -COPY --from=builder /wd/target/release/lgc /lgc -ENTRYPOINT ["/lgc"] \ No newline at end of file +COPY --from=builder /wd/target/release/lgc /usr/local/bin/lgc \ No newline at end of file diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 924a65b..cdfaab6 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -28,14 +28,18 @@ inquire.workspace = true tracing.workspace = true tracing-subscriber.workspace = true kclvm-api.workspace = true - -dialoguer = "0.11" +kclvm-query.workspace = true +kclvm-sema.workspace = true +rayon.workspace = true +dashmap.workspace = true +console.workspace = true +dialoguer.workspace = true futures = "0.3" async-trait = "0.1" tempfile = "3.10" -rayon = "1.10.0" -dashmap = "5.5" +uuid = "1.8" +similar = "2.5" # Local dependencies -logcraft-runtime = { path = "../runtime" } \ No newline at end of file +logcraft-runtime = { path = "../runtime" } diff --git a/crates/common/src/configuration.rs b/crates/common/src/configuration.rs index 7aad3c7..43df3c3 100644 --- a/crates/common/src/configuration.rs +++ b/crates/common/src/configuration.rs @@ -3,13 +3,18 @@ use anyhow::bail; use anyhow::Result; -use kclvm_api::{ - gpyrpc::{GetSchemaTypeArgs, ValidateCodeArgs}, - service::KclvmServiceImpl, -}; +use dialoguer::Confirm; +use dialoguer::Input; +use dialoguer::Password; +use dialoguer::Select; +use kclvm_api::{gpyrpc::ValidateCodeArgs, API}; +use kclvm_query::get_schema_type; +use kclvm_query::GetSchemaOption; +use kclvm_sema::ty::TypeKind; use serde::{Deserialize, Serialize}; -use serde_yaml_ng::Number; -use serde_yaml_ng::Value; +use serde_json::json; +use serde_json::Map; +use serde_json::Value; use std::collections::{BTreeMap, BTreeSet}; use std::{ fs::File, @@ -41,27 +46,26 @@ impl ProjectConfiguration { let buffer = File::create(path.unwrap_or(&PathBuf::from_str(LGC_CONFIG_PATH)?))?; serde_yaml_ng::to_writer(BufWriter::new(buffer), &self)?; - println!("changes saved successfully"); Ok(()) } - pub fn environment_names(&self) -> Result> { + pub fn environment_ids(&self) -> Result> { self.environments .iter() - .map(|env| ensure_kebab_case(&env.name)) + .map(|env| ensure_kebab_case(&env.id)) .collect() } - pub fn service_names(&self) -> Result> { + pub fn service_ids(&self) -> Result> { self.services .iter() - .map(|svc| ensure_kebab_case(&svc.name)) + .map(|svc| ensure_kebab_case(&svc.id)) .collect() } - pub fn remove_service(&mut self, name: &String) { + pub fn remove_service(&mut self, id: &String) { self.services.remove(&Service { - name: name.to_owned(), + id: id.to_owned(), ..Default::default() }); } @@ -80,19 +84,19 @@ impl ProjectConfiguration { #[derive(Eq, Serialize, Deserialize, Default, Clone)] pub struct Environment { - pub name: String, + pub id: String, pub services: BTreeSet, } impl PartialEq for Environment { fn eq(&self, other: &Environment) -> bool { - self.name == other.name + self.id == other.id } } impl Hash for Environment { fn hash(&self, state: &mut H) { - self.name.hash(state); + self.id.hash(state); } } @@ -104,26 +108,26 @@ impl PartialOrd for Environment { impl Ord for Environment { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.name.cmp(&other.name) + self.id.cmp(&other.id) } } #[derive(Eq, Serialize, Deserialize, Default, Clone)] pub struct Service { - pub name: String, + pub id: String, pub plugin: String, pub settings: BTreeMap, } impl PartialEq for Service { fn eq(&self, other: &Service) -> bool { - self.name == other.name + self.id == other.id } } impl Hash for Service { fn hash(&self, state: &mut H) { - self.name.hash(state); + self.id.hash(state); } } @@ -135,65 +139,62 @@ impl PartialOrd for Service { impl Ord for Service { fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.name.cmp(&other.name) + self.id.cmp(&other.id) } } impl Service { pub fn configure(&mut self, code: String, default: bool) -> Result<()> { - // Init kcl service - let serv = KclvmServiceImpl::default(); - // Load package configuration schema - let args = GetSchemaTypeArgs { - code: code.clone(), - schema_name: "Configuration".to_string(), - ..Default::default() - }; + let schema = get_schema_type( + "", + Some(&code), + Some("Configuration"), + GetSchemaOption::Definitions, + )?; - // Retrieve schema type - let schema = serv.get_schema_type(&args)?.schema_type_list; + let (attributes, doc) = match schema.get("Configuration") { + Some(schema) => (schema.attrs.clone(), schema.doc.to_string()), + None => { + tracing::info!( + "plugin does not provides configuration schema for dynamic configuration" + ); + return Ok(()); + } + }; - // Check if Configuration schema exists - if schema.is_empty() { - println!("Plugin does not provides schema for dynamic configuration"); - return Ok(()); + if !default { + if doc.is_empty() { + println!("Service configuration: "); + } else { + println!("{doc}:"); + } } - // Ask for arguments - for (p_name, p_type) in &schema[0].properties { + for (attr_name, attr_type) in attributes.into_iter() { if default { - self.settings.insert( - p_name.to_string(), - Value::String( - p_type - .default - .trim_matches(|c| c == '"' || c == '\'') - .to_string(), - ), - ); + let default = attr_type.ty.kind.defaut(&attr_name, attr_type.default)?; + self.settings.insert(attr_name, default); } else { - println!("Configure the service:"); - let msg = p_type.description.as_str(); - let res: Value = match p_type.r#type.as_str() { - "str" => Value::String( - dialoguer::Input::::new() - .with_prompt(msg.trim_matches(|c| c == '"' || c == '\'')) - .interact_text()?, - ), - "bool" => Value::Bool(inquire::prompt_confirmation(msg)?), - "float" => Value::Number(Number::from(inquire::prompt_f32(msg)?)), - "int" => Value::Number(Number::from(inquire::prompt_u32(msg)?)), - _ => { - println!( - "Using default for `{}`, type `{}` is not implemened yet", - p_name, - p_type.r#type.as_str() - ); - serde_json::from_str(p_type.default.as_str())? - } + let name = if let Some(doc) = attr_type.doc { + trim_quotes(&doc) + } else { + attr_name.to_string() }; + + let default = if let Some(default) = self.settings.get(&attr_name) { + Some(trim_quotes(&serde_json::to_string(default)?)) + } else { + attr_type.default + }; + + let sensitive = attr_type + .decorators + .iter() + .any(|decorator| decorator.keywords.contains_key("sensitive")); + + let res = attr_type.ty.kind.prompt(&name, default, sensitive)?; if !res.is_null() { - self.settings.insert(p_name.to_string(), res); + self.settings.insert(attr_name.to_string(), res); } } } @@ -204,7 +205,7 @@ impl Service { } pub fn validate(&self, code: String, data: String) -> Result<()> { - let serv = KclvmServiceImpl::default(); + let kcl_api = API::default(); let args = ValidateCodeArgs { code, @@ -213,7 +214,7 @@ impl Service { ..Default::default() }; - let check = serv.validate_code(&args)?; + let check = kcl_api.validate_code(&args)?; if !check.success { bail!(check.err_message) }; @@ -221,3 +222,139 @@ impl Service { Ok(()) } } + +fn trim_quotes(s: &str) -> String { + s.trim_matches(|c| c == '"' || c == '\'').to_string() +} + +fn match_bool(s: &str) -> bool { + match s { + "True" => true, + "False" => false, + "true" => true, + "false" => false, + _ => false, + } +} + +trait Prompt { + fn prompt(&self, name: &str, default: Option, sensitive: bool) -> Result; + fn defaut(&self, name: &str, default: Option) -> Result; +} + +impl Prompt for TypeKind { + fn defaut(&self, name: &str, default: Option) -> Result { + if default.is_none() { + tracing::warn!("schema does not provides default value for {}", &name); + } + + let default = match self { + TypeKind::Str => Value::String(trim_quotes(&default.unwrap_or_default())), + TypeKind::Bool => Value::Bool(match_bool(&default.unwrap_or("False".to_string()))), + TypeKind::Float => json!(default + .unwrap_or("0.0".to_string()) + .parse::() + .unwrap_or(0.0)), + TypeKind::Int => Value::Number( + default + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0) + .into(), + ), + TypeKind::None | TypeKind::Void => Value::Null, + ty => match serde_json::from_str(default.unwrap_or_default().as_str()) { + Ok(res) => res, + Err(_) => match ty { + TypeKind::List(_) => Value::Array(vec![]), + TypeKind::Dict(_) => Value::Object(Map::new()), + _ => Value::Null, + }, + }, + }; + + Ok(default) + } + + fn prompt(&self, name: &str, default: Option, sensitive: bool) -> Result { + let prompt_theme = dialoguer::theme::ColorfulTheme::default(); + let value = match self { + Self::Str => { + let input = if sensitive { + Password::with_theme(&prompt_theme) + .with_prompt(format!("{} (hidden)", name)) + .interact()? + } else { + Input::::with_theme(&prompt_theme) + .with_prompt(name) + .show_default(default.is_some()) + .default(default.unwrap_or_default()) + .interact_text()? + }; + Value::String(trim_quotes(&input)) + } + Self::Bool => Value::Bool( + Confirm::with_theme(&prompt_theme) + .with_prompt(name) + .show_default(default.is_some()) + .default(match_bool(&default.unwrap_or("False".to_string()))) + .interact()?, + ), + Self::Int => Value::Number( + Input::::with_theme(&prompt_theme) + .with_prompt(name) + .show_default(default.is_some()) + .default( + default + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0), + ) + .interact_text()? + .into(), + ), + Self::Float => json!(Input::::with_theme(&prompt_theme) + .with_prompt(name) + .show_default(default.is_some()) + .default( + default + .unwrap_or("0.0".to_string()) + .parse::() + .unwrap_or(0.0) + ) + .interact_text()?), + Self::Union(types) => { + let options = types + .iter() + .filter_map(|r#type| r#type.kind.prompt(name, default.clone(), sensitive).ok()) + .collect::>(); + + let selection = Select::with_theme(&prompt_theme) + .with_prompt(name) + .items(&options) + .default(0) + .interact()?; + + options[selection].clone() + } + Self::StrLit(val) => json!(val), + Self::BoolLit(val) => json!(val), + Self::FloatLit(val) => json!(val), + Self::IntLit(val) => json!(val), + Self::None | Self::Void => Value::Null, + ty => match serde_json::from_str(default.unwrap_or_default().as_str()) { + Ok(res) => res, + Err(_) => { + tracing::info!("Using default for `{}`", name); + match ty { + TypeKind::List(_) => Value::Array(vec![]), + TypeKind::Dict(_) => Value::Object(Map::new()), + _ => Value::Null, + } + } + }, + }; + + Ok(value) + } +} diff --git a/crates/common/src/detections.rs b/crates/common/src/detections.rs index 56f22f5..26f40fe 100644 --- a/crates/common/src/detections.rs +++ b/crates/common/src/detections.rs @@ -2,17 +2,26 @@ // SPDX-License-Identifier: MPL-2.0 use anyhow::{bail, Result}; +use console::{style, Style}; use dashmap::DashMap; use kclvm_api::gpyrpc::ValidateCodeArgs; use kclvm_api::service::KclvmServiceImpl; -use rayon::iter::{IntoParallelIterator, ParallelIterator}; +use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; -use serde_yaml_ng::Value; +use serde_json::Value; +use similar::{ChangeTag, TextDiff}; +use std::collections::HashMap; use std::fs; use std::path::PathBuf; -use std::{collections::HashMap, sync::Mutex}; +use std::{ + collections::HashSet, + hash::{Hash, Hasher}, +}; -use crate::{configuration::LGC_RULES_DIR, plugins::LGC_PLUGINS_PATH}; +use crate::{ + configuration::{Service, LGC_RULES_DIR}, + plugins::LGC_PLUGINS_PATH, +}; pub const GENERIC_DETECTION: &str = r#" schema Detection: @@ -31,10 +40,14 @@ schema Detection: rules: {str:any} "#; +// Helper types to store detections per plugin or per service +pub type PluginDetections = HashMap>; +pub type ServiceDetections = HashMap>; + #[derive(Serialize, Deserialize, Clone)] pub struct Detection { pub name: String, - pub rules: std::collections::HashMap, + pub rules: HashMap, } impl Detection { @@ -53,32 +66,29 @@ impl Detection { let check = serv.validate_code(&args)?; if !check.success { - eprintln!( - "Failed to verify detection file `{}`: {}", - path, check.err_message + tracing::error!( + "failed to verify detection file `{}`: {}", + path, + check.err_message ); std::process::exit(1); }; - let data = fs::read_to_string(&path)?; - let detection: Self = - serde_yaml_ng::from_str(&data).map_err(|e| anyhow::Error::msg(format!("{e}")))?; - - Ok(detection) + serde_yaml_ng::from_str(&fs::read_to_string(&path)?) + .map_err(|e| anyhow::Error::msg(format!("{e}"))) } } -pub fn map_plugin_detections() -> Result>> { +pub fn map_plugin_detections() -> Result>> { let entries: Vec = fs::read_dir(LGC_RULES_DIR)? .filter_map(|file| file.ok().map(|f| f.path())) .collect(); - let plugins: DashMap> = DashMap::new(); - let detection_names: Mutex> = Mutex::new(Vec::new()); + let plugins: DashMap> = DashMap::new(); // Check plugin existence if !PathBuf::from(LGC_PLUGINS_PATH).exists() { - bail!("Plugin directory `{LGC_PLUGINS_PATH}` does not exist. Have you installed plugins?") + bail!("plugin directory `{LGC_PLUGINS_PATH}` does not exist. Have you installed plugins?") } let plugins_name: Vec = fs::read_dir(LGC_PLUGINS_PATH)? @@ -98,22 +108,9 @@ pub fn map_plugin_detections() -> Result>> .filter_map(|path| match path.extension().and_then(|ext| ext.to_str()) { Some("yml") | Some("yaml") => { match Detection::pre_validate(path.display().to_string()) { - Ok(detection) => { - let mut detection_names_lock = detection_names.lock().unwrap(); - if detection_names_lock.contains(&detection.name) { - eprintln!( - "error: detection duplication - {} appears again in: {}", - &detection.name, - path.display() - ); - std::process::exit(1); - } else { - detection_names_lock.push(detection.name.clone()); - Some((path, detection)) - } - } + Ok(detection) => Some((path, detection)), Err(e) => { - eprintln!("{e}"); + tracing::error!("{e}"); None } } @@ -121,14 +118,21 @@ pub fn map_plugin_detections() -> Result>> _ => None, }) .for_each(|(path, detection)| { - detection.rules.into_iter().for_each(|(plugin, params)| { + detection.rules.into_iter().for_each(|(plugin, content)| { if plugins_name.contains(&plugin) { - plugins - .entry(plugin) - .or_default() - .push((detection.name.clone(), params)) + if !plugins.entry(plugin).or_default().insert(DetectionState { + name: detection.name.clone(), + content, + }) { + tracing::error!( + "detection duplication - {} appears again in: {}", + &detection.name, + path.display() + ); + std::process::exit(1); + }; } else { - eprintln!( + tracing::error!( "referenced plugin `{}` in `{}` does not exist", &plugin, path.display() @@ -139,3 +143,79 @@ pub fn map_plugin_detections() -> Result>> Ok(plugins.into_iter().collect()) } + +#[derive(Eq, Debug, Clone, Serialize, Deserialize)] +pub struct DetectionState { + pub name: String, + pub content: Value, +} + +impl PartialEq for DetectionState { + fn eq(&self, other: &DetectionState) -> bool { + self.name == other.name + } +} + +impl Hash for DetectionState { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +// Return true if there is a change in detections +pub fn compare_detections( + detections: &PluginDetections, + retrieved_detections: &ServiceDetections, + services: &HashMap>, + debug: bool, +) -> ServiceDetections { + let changed: DashMap> = DashMap::new(); + + detections.par_iter().for_each(|(plugin_name, rules)| { + if let Some(services) = services.get(plugin_name) { + for service in services { + if let Some(retrieved) = retrieved_detections.get(&service.id) { + for rule in rules { + if let Some(retrieved_rule) = retrieved.get(rule) { + let retrieved = + serde_json::to_string_pretty(&retrieved_rule.content).unwrap(); + let requested = serde_json::to_string_pretty(&rule.content).unwrap(); + if retrieved != requested { + changed + .entry(service.id.clone()) + .and_modify(|s| { + s.insert(rule.clone()); + }) + .or_insert(HashSet::from([rule.clone()])); + if debug { + println!( + "[~] rule: `{}` will be updated on `{}`:", + style(&rule.name).yellow(), + &service.id + ); + show_diff(&retrieved, &requested); + } + } + } + } + } + } + } + }); + + changed.into_iter().collect() +} + +pub fn show_diff(old: &str, new: &str) { + let diff = TextDiff::from_lines(old, new); + for op in diff.ops() { + for change in diff.iter_changes(op) { + let (sign, style) = match change.tag() { + ChangeTag::Delete => ("| - ", Style::new().red()), + ChangeTag::Insert => ("| + ", Style::new().green()), + ChangeTag::Equal => ("| ", Style::new().dim()), + }; + print!("{}{}", style.apply_to(sign).bold(), style.apply_to(change)); + } + } +} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 9a5def8..4fe3b6e 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -5,4 +5,5 @@ pub mod configuration; pub mod detections; pub mod plugins; +pub mod state; pub mod utils; diff --git a/crates/common/src/parser.rs b/crates/common/src/parser.rs new file mode 100644 index 0000000..63c3675 --- /dev/null +++ b/crates/common/src/parser.rs @@ -0,0 +1,18 @@ +// Copyright (c) 2023 LogCraft, SAS. +// SPDX-License-Identifier: MPL-2.0 + +// TODO: Find a better way to manage configuration. +const LGC_KCL_BASE: &str = r#" +import yaml +import file +import logcraft + +project = logcraft.Project _config +_config = yaml.decode(file.read("lgc.yml")) +"#; + +pub struct Plugin { + name: String, + path: Option, + url: Option +} diff --git a/crates/common/src/plugins/manager.rs b/crates/common/src/plugins/manager.rs index 84276d6..9435752 100644 --- a/crates/common/src/plugins/manager.rs +++ b/crates/common/src/plugins/manager.rs @@ -5,12 +5,14 @@ use anyhow::{anyhow, bail, Result}; use async_trait::async_trait; use logcraft_runtime::{ plugin_component::plugin::Metadata, state::State, Config, Engine, Interfaces, + DEFAULT_EPOCH_TICK_INTERVAL, }; use serde::{Deserialize, Serialize}; use std::{ fmt, fs, io::Write, path::{Path, PathBuf}, + time::Duration, }; use tempfile::NamedTempFile; use wasmtime::{component::Component, Store}; @@ -34,8 +36,8 @@ impl PluginManager { // Setup wasmtime let mut config = Config::default(); if let Err(e) = config.enable_cache(&None) { - tracing::error!(err = ?e, "Failed to load wasm cache"); - bail!("warn: unable load wasmtime cache: {}", e); + tracing::warn!(err = ?e, "failed to load wasm cache"); + bail!("{e}") }; let engine = Engine::builder(&config)?.build(); @@ -50,11 +52,10 @@ impl PluginManager { // Instanciate plugin let path = file.path(); let (instance, _) = self.load_plugin(&path).await?; - // Check if plugin directory exists let plugin_path = PathBuf::from(LGC_PLUGINS_PATH); if !plugin_path.exists() { - fs::create_dir(&plugin_path)?; + fs::create_dir_all(&plugin_path)?; } // Copying file to avoid cross-device link error @@ -78,7 +79,10 @@ impl PluginManager { let mut store = wasmtime::Store::new(&self.engine.inner, State::default()); // TODO: Check for better value - store.set_epoch_deadline(6000); + let deadline = Duration::from_secs(60); + store.set_epoch_deadline( + (deadline.as_micros() / DEFAULT_EPOCH_TICK_INTERVAL.as_micros()) as u64, + ); let (interface, _) = Interfaces::instantiate_async(&mut store, &component, &self.engine.linker).await?; @@ -166,12 +170,19 @@ impl PluginActions for InstanceData { self.interface .logcraft_host_plugin() .call_create(store, config, name, params) - .await? + .await + .map_err(|e| { + anyhow!( + "when calling read for plugin `{}`: {}", + self.metadata.name, + e + ) + })? .map_err(|e| { anyhow!( - "error when calling create for plugin `{}`: {}", + "when calling create for plugin `{}`: {}", self.metadata.name, - e.to_string() + e ) }) } @@ -189,9 +200,9 @@ impl PluginActions for InstanceData { .await? .map_err(|e| { anyhow!( - "error when calling read for plugin `{}`: {}", + "when calling read for plugin `{}`: {}", self.metadata.name, - e.to_string() + e ) }) } @@ -209,9 +220,9 @@ impl PluginActions for InstanceData { .await? .map_err(|e| { anyhow!( - "error when calling update for plugin `{}`: {}", + "when calling update for plugin `{}`: {}", self.metadata.name, - e.to_string() + e ) }) } @@ -229,9 +240,9 @@ impl PluginActions for InstanceData { .await? .map_err(|e| { anyhow!( - "error when calling delete for plugin `{}`: {}", + "when calling delete for plugin `{}`: {}", self.metadata.name, - e.to_string() + e ) }) } @@ -243,9 +254,9 @@ impl PluginActions for InstanceData { .await? .map_err(|e| { anyhow!( - "error when calling ping for plugin `{}`: {}", + "when calling ping for plugin `{}`: {}", self.metadata.name, - e.to_string() + e ) }) } @@ -286,7 +297,7 @@ impl PluginLocation { // copy(path, &plugin_path)?; tokio::fs::read(path) .await - .map_err(|e| anyhow!("error reading plugin file: {}", e)) + .map_err(|e| anyhow!("reading plugin file: {}", e)) } // Self::Remote(url) => { // // Retrieve remote file // let resp = reqwest::get(url.as_str()) diff --git a/crates/common/src/plugins/mod.rs b/crates/common/src/plugins/mod.rs index 2a645c5..6f517a9 100644 --- a/crates/common/src/plugins/mod.rs +++ b/crates/common/src/plugins/mod.rs @@ -9,7 +9,7 @@ pub mod manager; pub use manager::PluginLocation; use url::Url; -pub const LGC_PLUGINS_PATH: &str = ".logcraft"; +pub const LGC_PLUGINS_PATH: &str = ".logcraft/plugins"; #[derive(Serialize, Deserialize, Clone, Default)] pub struct Plugin { @@ -37,14 +37,14 @@ pub fn determine_plugin_location(source: &str) -> Result { Ok(uri) => match uri.scheme() { "http" | "https" => bail!("not implemented yet"), "oci" => bail!("not implemented yet"), - _ => bail!("invalid scheme: {}", uri.scheme()), + _ => bail!("unsupported scheme: {}", uri.scheme()), }, Err(_) => { let path = PathBuf::from_str(source)?; if path.is_file() { Ok(PluginLocation::Local(path)) } else { - bail!("provided path does not target a file") + bail!("invalid plugin location") } } } diff --git a/crates/common/src/state.rs b/crates/common/src/state.rs new file mode 100644 index 0000000..3923b1b --- /dev/null +++ b/crates/common/src/state.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2023 LogCraft, SAS. +// SPDX-License-Identifier: MPL-2.0 + +use crate::detections::{DetectionState, ServiceDetections}; +use anyhow::{anyhow, bail, Result}; +use console::style; +use dashmap::DashMap; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + fs, + io::{BufReader, BufWriter}, + path::PathBuf, +}; +use uuid::Uuid; + +const LGC_STATE_PATH: &str = ".logcraft/state.json"; +const LGC_STATE_VERSION: usize = 1; + +#[derive(Debug, Serialize, Deserialize)] +pub struct State { + /// State unique ID + lineage: Uuid, + /// Serial number of the state file. + /// Increments every time the state file is written. + serial: usize, + /// Version of the state schema + version: usize, + /// Version of LogCraft CLI + lgc_version: String, + /// List of rules to track service_name => (rule_name, rule_settings) + pub services: ServiceDetections, +} + +impl State { + pub fn clean(&mut self) -> Result<()> { + self.services.clear(); + self.write() + } + + pub fn read() -> Result { + let path = PathBuf::from(LGC_STATE_PATH); + if !path.is_file() { + return Ok(Self { + lineage: Uuid::new_v4(), + serial: 0, + version: LGC_STATE_VERSION, + lgc_version: env!("CARGO_PKG_VERSION").to_string(), + services: HashMap::new(), + }); + } + + let f = fs::File::open(path)?; + let reader = BufReader::new(f); + + match serde_json::from_reader(reader) { + Ok(state) => Ok(state), + Err(e) => { + bail!("unable to load state file: {}", e) + } + } + } + + pub fn write(&mut self) -> Result<()> { + let f = fs::File::create(LGC_STATE_PATH)?; + + self.serial += 1; + self.lgc_version = env!("CARGO_PKG_VERSION").to_string(); + + let writer = BufWriter::new(f); + serde_json::to_writer_pretty(writer, self) + .map_err(|e| anyhow!("unable to write state file: {}", e)) + } + + pub fn missing_rules(&self, detections: &ServiceDetections) -> ServiceDetections { + let to_remove: DashMap> = DashMap::new(); + + detections.par_iter().for_each(|(service_id, rules)| { + if let Some(state_rules) = self.services.get(service_id) { + state_rules.difference(rules).for_each(|rule| { + to_remove + .entry(service_id.clone()) + .and_modify(|s| { + s.insert(rule.clone()); + }) + .or_insert(HashSet::from([rule.clone()])); + println!( + "[-] rule: `{}` will be deleted from `{}`", + style(&rule.name).red(), + &service_id + ); + }); + } + }); + + to_remove.into_iter().collect() + } +} diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs index 1e4d2ad..15b394d 100644 --- a/crates/common/src/utils.rs +++ b/crates/common/src/utils.rs @@ -3,9 +3,8 @@ use anyhow::{bail, Result}; -pub fn ensure_kebab_case(name: &str) -> Result { - let s = name.to_ascii_lowercase(); - let mut chars = s.chars(); +pub fn ensure_kebab_case(name: &str) -> Result<&str> { + let mut chars = name.chars(); // Validate the first character: it must be an alphanumeric lower-case character match chars.next() { @@ -43,5 +42,5 @@ pub fn ensure_kebab_case(name: &str) -> Result { } } - Ok(s) + Ok(name) } diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 8cb264b..9348ee3 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -21,6 +21,7 @@ tokio.workspace = true futures.workspace = true http.workspace = true tracing.workspace = true +tracing-subscriber.workspace = true wasmtime.workspace = true wasmtime-wasi.workspace = true @@ -29,11 +30,11 @@ wasmtime-wasi-http.workspace = true crossbeam-channel = "0.5" async-trait = "0.1" -wit-component = "0.210" -wit-parser = "0.210" -wasmparser = "0.210" +wit-component = "0.211" +wit-parser = "0.211" +wasmparser = "0.211" # Temp hyper = { version = "1.0.1", features = ["full"] } tokio-native-tls = "0.3.1" -http-body-util = "0.1.1" \ No newline at end of file +http-body-util = "0.1.1" diff --git a/crates/runtime/src/engine.rs b/crates/runtime/src/engine.rs index f3edccc..9245577 100644 --- a/crates/runtime/src/engine.rs +++ b/crates/runtime/src/engine.rs @@ -8,14 +8,10 @@ use std::{path::PathBuf, time::Duration}; use wasmtime::component::Linker; use wasmtime::{InstanceAllocationStrategy, PoolingAllocationConfig}; -/// The default [`EngineBuilder::epoch_tick_interval`]. -pub const DEFAULT_EPOCH_TICK_INTERVAL: Duration = Duration::from_millis(10); - use crate::state::State; +use crate::DEFAULT_EPOCH_TICK_INTERVAL; const MB: u64 = 1 << 20; -const GB: u64 = 1 << 30; -const WASM_PAGE_SIZE: u64 = 64 * 1024; /// Global configuration for `EngineBuilder`. /// @@ -70,20 +66,17 @@ impl Default for Config { // globals, memories, etc. Instance allocations are relatively small and are largely inconsequential // compared to other runtime state, but a number needs to be chosen here so a relatively large threshold // of 10MB is arbitrarily chosen. It should be unlikely that any reasonably-sized module hits this limit. - .max_component_instance_size(MB as usize) + // Huge size maximum as bare Python component are 30MB+. + .max_component_instance_size(50 * MB as usize) .max_core_instances_per_component(200) .max_tables_per_component(20) - .table_elements(30_000) + .table_elements(1_000) // The number of memories an instance can have effectively limits the number of inner components // a composed component can have (since each inner component has its own memory). We default to 32 for now, and // we'll see how often this limit gets reached. - .max_memories_per_component(32) + .max_memories_per_component(20) .total_memories(1_000) .total_tables(2_000) - // Nothing is lost from allowing the maximum size of memory for - // all instance as it's still limited through other the normal - // `StoreLimitsAsync` accounting method too. - .memory_pages(4 * GB / WASM_PAGE_SIZE) // These numbers are completely arbitrary at something above 0. .linear_memory_keep_resident((2 * MB) as usize) .table_keep_resident((MB / 2) as usize); diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index b10a617..f20ed83 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -3,8 +3,13 @@ mod engine; pub mod state; +use std::time::Duration; + pub use engine::{Config, Engine}; +/// The default [`EngineBuilder::epoch_tick_interval`]. +pub const DEFAULT_EPOCH_TICK_INTERVAL: Duration = Duration::from_millis(10); + wasmtime::component::bindgen!({ path: "../../wit", async: true diff --git a/crates/runtime/src/state.rs b/crates/runtime/src/state.rs index e629614..961f014 100644 --- a/crates/runtime/src/state.rs +++ b/crates/runtime/src/state.rs @@ -60,16 +60,16 @@ impl WasiHttpView for State { request: hyper::Request, config: OutgoingRequestConfig, ) -> wasmtime_wasi_http::HttpResult { - Ok(default_send_request_test(request, config)) + Ok(default_send_request(request, config)) } } -pub fn default_send_request_test( +pub fn default_send_request( request: hyper::Request, config: OutgoingRequestConfig, ) -> HostFutureIncomingResponse { let handle = wasmtime_wasi::runtime::spawn(async move { - Ok(default_send_request_handler_test(request, config).await) + Ok(default_send_request_handler(request, config).await) }); HostFutureIncomingResponse::pending(handle) } @@ -82,7 +82,7 @@ pub(crate) fn dns_error(rcode: String, info_code: u16) -> ErrorCode { } // ! Quick fix to allow invalid certificate (for selfsigned certificates) -pub async fn default_send_request_handler_test( +pub async fn default_send_request_handler( mut request: hyper::Request, OutgoingRequestConfig { use_tls, @@ -141,7 +141,9 @@ pub async fn default_send_request_handler_test( let mut parts = authority.split(':'); let host = parts.next().unwrap_or(&authority); - let stream = connector.connect(host, tcp_stream).await.unwrap(); + let stream = connector.connect(host, tcp_stream).await.map_err(|e| { + ErrorCode::InternalError(Some(format!("initializing tls stream: {}", e))) + })?; let stream = TokioIo::new(stream); diff --git a/licenserc.toml b/licenserc.toml new file mode 100644 index 0000000..ae8a896 --- /dev/null +++ b/licenserc.toml @@ -0,0 +1,33 @@ +# Copyright (c) 2023 LogCraft, SAS. +# SPDX-License-Identifier: MPL-2.0 + +inlineHeader = """ +Copyright (c) ${inceptionYear} ${copyrightOwner}. +SPDX-License-Identifier: MPL-2.0 +""" + +# On enabled, check the license header matches exactly with whitespace. +# Otherwise, strip the header in one line and check. +# default: true +strictCheck = true + +excludes = [ + # Plugins submodule + "plugins/**", + + # Uneeded + "Dockerfile", + + # Generated files + ".github/workflows/**", + + "Cargo.*" +] + +[git] +attrs = 'auto' +ignore = 'auto' + +[properties] +inceptionYear = 2023 +copyrightOwner = "LogCraft, SAS" \ No newline at end of file diff --git a/src/commands/deploy.rs b/src/commands/deploy.rs index 0d91877..aa4a128 100644 --- a/src/commands/deploy.rs +++ b/src/commands/deploy.rs @@ -1,88 +1,99 @@ // Copyright (c) 2023 LogCraft, SAS. // SPDX-License-Identifier: MPL-2.0 +use std::collections::{HashMap, HashSet}; + use anyhow::{anyhow, bail, Result}; use clap::Parser; use console::style; -use inquire::Select; +use dialoguer::{theme::ColorfulTheme, Confirm, Select}; use logcraft_common::{ configuration::{Environment, ProjectConfiguration, Service}, - detections::map_plugin_detections, + detections::{compare_detections, map_plugin_detections, DetectionState, ServiceDetections}, plugins::manager::{PluginActions, PluginManager}, + state::State, }; +use serde_json::Value; use tokio::task::JoinSet; /// Prepare working directory for other lgcli commands #[derive(Parser, Debug, Default)] #[clap( - about = "Deploy divergent rules to remote systems", + about = "Deploy rules changes to remote systems", allow_hyphen_values = true )] pub struct DeployCommand { /// Deploy to this target environment - #[clap(short, long)] - pub env_name: Option, + pub env_id: Option, /// Deploy to this target service - #[clap(short, long, conflicts_with = "env_name")] - pub service_name: Option, + #[clap(short, long)] + pub service_id: Option, + + /// Skip interactive approval of changes deployment + #[clap(long)] + pub auto_approve: bool, } impl DeployCommand { pub async fn run(self, config: &ProjectConfiguration) -> Result<()> { // Load all detections let detections = map_plugin_detections()?; - if detections.is_empty() { - bail!("no detections found"); - } + + // Prompt theme + let prompt_theme = ColorfulTheme::default(); // Retrieve services depending on targeted environment or service - let services = if let Some(service_name) = self.service_name { - let service = config + let mut services: HashMap> = HashMap::new(); + if let Some(svc_id) = self.service_id { + let svc = config .services .get(&Service { - name: service_name.clone(), + id: svc_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("service {} not found", &service_name))?; - vec![service] + .ok_or_else(|| anyhow!("service `{}` not found", &svc_id))?; + + services.insert(svc.plugin.clone(), vec![svc]); } else { - // Prompt if env_name and service_name not set - let name = self.env_name.unwrap_or_else(|| { - Select::new( - "Select the environment to use:", - config.environment_names().unwrap(), - ) - .prompt() - .unwrap() - // .or_else(|| bail!()) - .to_owned() - }); + let env_id = match self.env_id { + Some(id) => id, + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } + }; let env = config .environments .get(&Environment { - name: name.clone(), + id: env_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("environement `{}` not found", &name))?; + .ok_or_else(|| anyhow!("environment `{}` not found", &env_id))?; - // Retrieve environment services config .services .iter() - .filter(|svc| env.services.contains(&svc.name)) - .collect() + .filter(|svc| env.services.contains(&svc.id)) + .for_each(|svc| { + services.entry(svc.plugin.clone()).or_default().push(svc); + }) }; // Load plugins let plugin_manager = PluginManager::new()?; let mut set = JoinSet::new(); - for plugin_name in detections.keys() { - let plugin_name = plugin_name.to_string(); + for plugin_id in detections.keys() { + let plugin_id = plugin_id.to_string(); let plugin_manager = plugin_manager.clone(); - set.spawn(async move { plugin_manager.load_plugin(plugin_name).await }); + set.spawn(async move { plugin_manager.load_plugin(plugin_id).await }); } // Call get schema and retrieve all detections @@ -93,53 +104,177 @@ impl DeployCommand { // Safe unwrap as we load plugins with detection HashMap. let (plugin, rules) = detections.get_key_value(&meta.name).unwrap(); - for svc in services.iter().filter(|svc| &svc.plugin == plugin) { - let service_config = serde_json::to_string(&svc.settings)?; - for (rule_name, rule) in rules { - let rule = serde_json::to_string(rule)?; - match instance - .read(&mut store, &service_config, rule_name, &rule) - .await? + let mut has_diff = false; + if let Some(plugin_services) = services.get(plugin) { + let mut returned_rules: ServiceDetections = HashMap::new(); + let mut missing_rules: HashMap> = HashMap::new(); + + for svc in plugin_services { + let service_config = serde_json::to_string(&svc.settings)?; + for rule in rules { + let requested_rule = serde_json::to_string(&rule.content)?; + if let Some(resp) = instance + .read(&mut store, &service_config, &rule.name, &requested_rule) + .await? + { + let content: Value = serde_json::from_str(&resp)?; + returned_rules + .entry(svc.id.clone()) + .and_modify(|rules| { + rules.insert(DetectionState { + name: rule.name.clone(), + content: content.clone(), + }); + }) + .or_insert(HashSet::from([DetectionState { + name: rule.name.clone(), + content, + }])); + } else { + has_diff = true; + missing_rules + .entry(svc.id.clone()) + .and_modify(|rules| { + rules.insert(rule); + }) + .or_insert(HashSet::from([rule])); + if !self.auto_approve { + println!( + "[+] rule: `{}` will be created on `{}`", + style(&rule.name).green(), + &svc.id + ) + } + } + } + } + + let mut state = State::read()?; + let to_remove = state.missing_rules(&returned_rules); + let changed = + compare_detections(&detections, &returned_rules, &services, !self.auto_approve); + + if !changed.is_empty() || has_diff || !to_remove.is_empty() { + if self.auto_approve + || Confirm::with_theme(&prompt_theme) + .with_prompt("Do you want to deploy these changes?") + .interact()? { - Some(res) => { - if instance - .update(&mut store, &service_config, rule_name, &rule) - .await? - .is_some() - { - if res.is_empty() { - println!( - "rule: `{}` unchanged on `{}`", - style(&rule_name).dim(), - svc.name - ); - } else { - println!( - "rule: `{}` updated on `{}`", - style(&rule_name).yellow(), - svc.name - ); + for svc in plugin_services { + let service_config = serde_json::to_string(&svc.settings)?; + let state_service = state.services.entry(svc.id.clone()).or_default(); + + // Create + if let Some(missing_rules) = missing_rules.get(&svc.id) { + for &rule in missing_rules { + let rule_content = serde_json::to_string(&rule.content)?; + match instance + .create( + &mut store, + &service_config, + &rule.name, + &rule_content, + ) + .await + { + Ok(_) => { + state_service.insert(rule.clone()); + println!( + "[+] rule: `{}` created on `{}`", + style(&rule.name).green(), + svc.id + ) + } + Err(e) => { + state.write()?; + bail!( + "on update for `{}` in `{}`: {}", + style(&rule.name).red(), + svc.id, + e + ); + } + } } } - } - None => { - if instance - .create(&mut store, &service_config, rule_name, &rule) - .await? - .is_some() - { - println!( - "rule: `{}` created on `{}`", - style(&rule_name).green(), - svc.name - ); + + // Update + if let Some(changed_rules) = changed.get(&svc.id) { + for rule in rules.intersection(changed_rules) { + let rule_content = serde_json::to_string(&rule.content)?; + match instance + .update( + &mut store, + &service_config, + &rule.name, + &rule_content, + ) + .await + { + Ok(_) => { + state_service.replace(rule.clone()); + println!( + "[~] rule: `{}` updated on `{}`", + style(&rule.name).yellow(), + svc.id + ) + } + Err(e) => { + state.write()?; + bail!( + "on update for `{}` in `{}`: {}", + style(&rule.name).red(), + svc.id, + e + ); + } + } + } + } + + // Delete + if let Some(rules) = to_remove.get(&svc.id) { + for rule in rules { + let rule_content = serde_json::to_string(&rule.content)?; + match instance + .delete( + &mut store, + &service_config, + &rule.name, + &rule_content, + ) + .await + { + Ok(_) => { + state_service.remove(rule); + println!( + "[-] rule: `{}` deleted from `{}`", + style(&rule.name).red(), + svc.id + ); + } + Err(e) => { + state.write()?; + bail!( + "on deletion for `{}` in `{}`: {}", + style(&rule.name).red(), + svc.id, + e + ); + } + } + } } } + state.write()?; + } else { + bail!("action aborted") } + } else { + tracing::info!("no differences found"); } } } - Ok(()) } } diff --git a/src/commands/destroy.rs b/src/commands/destroy.rs index 7ed19dd..b2bea4e 100644 --- a/src/commands/destroy.rs +++ b/src/commands/destroy.rs @@ -4,84 +4,93 @@ use anyhow::{anyhow, bail, Result}; use clap::Parser; use console::style; -use inquire::Select; +use dialoguer::{theme::ColorfulTheme, Confirm, Select}; use logcraft_common::{ configuration::{Environment, ProjectConfiguration, Service}, - detections::map_plugin_detections, plugins::manager::{PluginActions, PluginManager}, + state::State, }; +use std::collections::HashMap; use tokio::task::JoinSet; -/// Prepare working directory for other lgcli commands #[derive(Parser, Debug, Default)] #[clap( - about = "Remove local detection rules from remote systems", + about = "Remove deployed detection rules from remote systems", allow_hyphen_values = true )] pub struct DestroyCommand { /// Destroy from this environment - #[clap(short, long)] - pub env_name: Option, + pub env_id: Option, /// Destroy from this service - #[clap(short, long, conflicts_with = "env_name")] - pub service_name: Option, + #[clap(short, long)] + pub service_id: Option, + + /// Skip interactive approval of rules destruction + #[clap(long)] + pub auto_approve: bool, } impl DestroyCommand { pub async fn run(self, config: &ProjectConfiguration) -> Result<()> { // Load all detections - let detections = map_plugin_detections()?; - if detections.is_empty() { - bail!("no detections found"); - } + let mut state = State::read()?; - // Retrieve services depending on targeted environment or service - let services = if let Some(service_name) = self.service_name { - let service = config + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Retrieve services + let mut services: HashMap> = HashMap::new(); + if let Some(svc_id) = self.service_id { + let svc = config .services .get(&Service { - name: service_name.clone(), + id: svc_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("service `{}` not found", &service_name))?; - vec![service] + .ok_or_else(|| anyhow!("service `{}` not found", &svc_id))?; + + services.insert(svc.plugin.clone(), vec![svc]); } else { - // Prompt if env_name and service_name not set - let name = self.env_name.unwrap_or_else(|| { - Select::new( - "Select the environment to use:", - config.environment_names().unwrap(), - ) - .prompt() - .unwrap() - .to_owned() - }); + let env_id = match self.env_id { + Some(id) => id, + // None => Select::new("Select the environment to use:", config.service_ids()?).prompt()? + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } + }; let env = config .environments .get(&Environment { - name: name.clone(), + id: env_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("environement `{}` not found", &name))?; + .ok_or_else(|| anyhow!("environment `{}` not found", &env_id))?; - // Retrieve environment services config .services .iter() - .filter(|svc| env.services.contains(&svc.name)) - .collect() + .filter(|svc| env.services.contains(&svc.id)) + .for_each(|svc| { + services.entry(svc.plugin.clone()).or_default().push(svc); + }) }; // Load plugins let plugin_manager = PluginManager::new()?; let mut set = JoinSet::new(); - for plugin_name in detections.keys() { - let plugin_name = plugin_name.to_string(); + for plugin_id in services.keys() { + let plugin_id = plugin_id.to_string(); let plugin_manager = plugin_manager.clone(); - set.spawn(async move { plugin_manager.load_plugin(plugin_name).await }); + set.spawn(async move { plugin_manager.load_plugin(plugin_id).await }); } // Call get schema and retrieve all detections @@ -90,31 +99,100 @@ impl DestroyCommand { let meta = &instance.metadata; // Safe unwrap as we load plugins with detection HashMap. - let (plugin, rules) = detections.get_key_value(&meta.name).unwrap(); + let services = services.get(&meta.name).unwrap(); + let mut has_diff = false; - for svc in services.iter().filter(|svc| &svc.plugin == plugin) { + for svc in services { let service_config = serde_json::to_string(&svc.settings)?; - for (rule_name, rule) in rules { - let rule = serde_json::to_string(rule)?; - match instance - .delete(&mut store, &service_config, rule_name, &rule) - .await? - { - Some(_) => println!( - "rule: `{}` deleted on `{}`", - style(&rule_name).red(), - svc.name - ), - None => println!( - "rule: `{}` ignored on `{}`, rule not found.", - style(&rule_name).dim(), - svc.name - ), + if let Some(rules) = state.services.get(&svc.id) { + for rule_state in rules { + let requested_rule = serde_json::to_string(&rule_state.content)?; + if instance + .read( + &mut store, + &service_config, + &rule_state.name, + &requested_rule, + ) + .await? + .is_some() + { + has_diff = true; + if !self.auto_approve { + println!( + "[-] rule: `{}` will be deleted from `{}`", + style(&rule_state.name).red(), + &svc.id + ) + } + } + } + } + } + + // Destroy rules + if has_diff { + if self.auto_approve + || Confirm::with_theme(&prompt_theme) + .with_prompt("Do you want to deploy these changes?") + .interact()? + { + for svc in services { + let service_config = serde_json::to_string(&svc.settings)?; + if let Some(service) = state.services.get_mut(&svc.id) { + // Collect rules to avoid borrowing issues during iteration + let rules: Vec<_> = service.iter().cloned().collect(); + + for rule_state in rules { + let rule_content = serde_json::to_string(&rule_state.content)?; + match instance + .delete( + &mut store, + &service_config, + &rule_state.name, + &rule_content, + ) + .await + { + Ok(Some(_)) => { + println!( + "[-] rule: `{}` deleted from `{}`", + style(&rule_state.name).red(), + svc.id + ); + service.remove(&rule_state); + } + Ok(None) => { + println!( + "[!] rule: `{}` not found on `{}` - ignoring", + style(&rule_state.name).dim(), + svc.id + ); + service.remove(&rule_state); + } + Err(e) => { + state.write()?; + bail!( + "on deletion for `{}` in `{}`: {}", + style(&rule_state.name).red(), + svc.id, + e + ); + } + } + } + state.services.remove(&svc.id); + } } + } else { + bail!("action aborted") } + } else { + tracing::info!("no differences found"); + return Ok(()); } } - Ok(()) + state.write() } } diff --git a/src/commands/diff.rs b/src/commands/diff.rs index f0e644e..cf8a72d 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -1,15 +1,21 @@ // Copyright (c) 2023 LogCraft, SAS. // SPDX-License-Identifier: MPL-2.0 -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use clap::Parser; use console::style; -use inquire::Select; +use dialoguer::{theme::ColorfulTheme, Select}; use logcraft_common::{ configuration::{Environment, ProjectConfiguration, Service}, - detections::map_plugin_detections, + detections::{ + compare_detections, map_plugin_detections, DetectionState, PluginDetections, + ServiceDetections, + }, plugins::manager::{PluginActions, PluginManager}, + state::State, }; +use serde_json::Value; +use std::collections::{HashMap, HashSet}; use tokio::task::JoinSet; /// Prepare working directory for other lgcli commands @@ -20,70 +26,78 @@ use tokio::task::JoinSet; )] pub struct DiffCommand { /// Show differences from this target environment - #[clap(short, long)] - pub env_name: Option, + pub env_id: Option, /// Show differences from this target service - #[clap(short, long, conflicts_with = "env_name")] - pub service_name: Option, + #[clap(short, long)] + pub service_id: Option, } impl DiffCommand { pub async fn run(self, config: &ProjectConfiguration) -> Result<()> { // Load all detections - let detections = map_plugin_detections()?; - if detections.is_empty() { - bail!("no detections found"); - } + let detections: PluginDetections = map_plugin_detections()?; - // Retrieve services depending on targeted environment or service - let services = if let Some(service_name) = self.service_name { - let service = config + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Retrieve services + let mut services: HashMap> = HashMap::new(); + if let Some(svc_id) = self.service_id { + let svc = config .services .get(&Service { - name: service_name.clone(), + id: svc_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("service `{}` not found", &service_name))?; - vec![service] + .ok_or_else(|| anyhow!("service `{}` not found", &svc_id))?; + + services.insert(svc.plugin.clone(), vec![svc]); } else { - // Prompt if env_name and service_name not set - let name = self.env_name.unwrap_or_else(|| { - Select::new( - "Select the environment to use:", - config.environment_names().unwrap(), - ) - .prompt() - .unwrap() - .to_owned() - }); + let env_id = match self.env_id { + Some(id) => id, + // None => Select::new("Select the environment to use:", config.service_ids()?).prompt()? + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } + }; let env = config .environments .get(&Environment { - name: name.clone(), + id: env_id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("environement `{}` not found", &name))?; + .ok_or_else(|| anyhow!("environment `{}` not found", &env_id))?; - // Retrieve environment services config .services .iter() - .filter(|svc| env.services.contains(&svc.name)) - .collect() + .filter(|svc| env.services.contains(&svc.id)) + .for_each(|svc| { + services.entry(svc.plugin.clone()).or_default().push(svc); + }) }; // Load plugins let plugin_manager = PluginManager::new()?; let mut set = JoinSet::new(); - for plugin_name in detections.keys() { - let plugin_name = plugin_name.to_string(); + for plugin_id in detections.keys() { + let plugin_id = plugin_id.to_string(); let plugin_manager = plugin_manager.clone(); - set.spawn(async move { plugin_manager.load_plugin(plugin_name).await }); + set.spawn(async move { plugin_manager.load_plugin(plugin_id).await }); } + let mut returned_rules: ServiceDetections = HashMap::new(); + let mut has_diff = false; + // Call get schema and retrieve all detections while let Some(plugin) = set.join_next().await { let (instance, mut store) = plugin??; @@ -92,40 +106,52 @@ impl DiffCommand { // Safe unwrap as we load plugins with detection HashMap. let (plugin, rules) = detections.get_key_value(&meta.name).unwrap(); - for svc in services.iter().filter(|svc| &svc.plugin == plugin) { - let service_config = serde_json::to_string(&svc.settings)?; - for (rule_name, rule) in rules { - let rule = serde_json::to_string(rule)?; - match instance - .read(&mut store, &service_config, rule_name, &rule) - .await? - { - Some(res) => { - println!("Res: {}", &res); - if res.is_empty() { - println!( - "rule: `{}` will be unchanged on `{}`", - style(&rule_name).dim(), - svc.name - ); - } else { - println!( - "rule: `{}` will be updated on `{}`", - style(&rule_name).yellow(), - svc.name - ); - } + if let Some(services) = services.get(plugin) { + for svc in services { + let service_config = serde_json::to_string(&svc.settings)?; + for rule_state in rules { + let requested_rule = serde_json::to_string(&rule_state.content)?; + if let Some(rule) = instance + .read( + &mut store, + &service_config, + &rule_state.name, + &requested_rule, + ) + .await? + { + let content: Value = serde_json::from_str(&rule)?; + returned_rules + .entry(svc.id.clone()) + .and_modify(|rules| { + rules.insert(DetectionState { + name: rule_state.name.clone(), + content: content.clone(), + }); + }) + .or_insert(HashSet::from([DetectionState { + name: rule_state.name.clone(), + content, + }])); + } else { + has_diff = true; + println!( + "[+] rule: `{}` will be created on `{}`", + style(&rule_state.name).green(), + &svc.id + ) } - None => println!( - "rule: `{}` will be created on `{}`", - style(&rule_name).green(), - svc.name - ), } } } } + let changes = compare_detections(&detections, &returned_rules, &services, true).is_empty(); + + if State::read()?.missing_rules(&returned_rules).is_empty() && changes && !has_diff { + tracing::info!("no differences found"); + } + Ok(()) } } diff --git a/src/commands/environments.rs b/src/commands/environments.rs index 0ec4e19..6c2f3fe 100644 --- a/src/commands/environments.rs +++ b/src/commands/environments.rs @@ -3,7 +3,8 @@ use anyhow::{anyhow, bail, Ok, Result}; use clap::{Parser, Subcommand}; -use inquire::{Select, Text}; +use console::style; +use dialoguer::{theme::ColorfulTheme, Input, Select}; use logcraft_common::{ configuration::{Environment, ProjectConfiguration}, utils::ensure_kebab_case, @@ -42,29 +43,34 @@ impl EnvironmentsCommands { #[derive(Parser)] pub struct AddEnvironment { - /// Name of the environment to create - pub name: Option, + /// ID of the environment to create + pub id: Option, } impl AddEnvironment { pub async fn run(self, config: &mut ProjectConfiguration) -> Result<()> { - // Prompt name if not set - let name = match self.name { - Some(name) => name, - None => Text::new("Environment name:").prompt()?, + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Prompt id if not set + let id = match self.id { + Some(id) => id, + None => Input::::with_theme(&prompt_theme) + .with_prompt("Environment id:") + .interact_text()?, }; // Naming contraints check - ensure_kebab_case(&name)?; + let id = ensure_kebab_case(&id)?.to_string(); // Add new environment if it does not exists let env = Environment { - name, + id, ..Default::default() }; if config.environments.contains(&env) { - bail!("error: environment `{}` already exists", &env.name) + bail!("environment `{}` already exists", &env.id) } else { config.environments.insert(env); } @@ -80,7 +86,11 @@ impl ListEnvironments { pub fn run(self, config: &ProjectConfiguration) -> Result<()> { // Retrieve configuration for plugin in &config.environments { - println!("`{}` `{}` service(s)", &plugin.name, plugin.services.len()); + println!( + "`{}` {} service(s)", + style(&plugin.id).bold(), + plugin.services.len() + ); } Ok(()) @@ -89,8 +99,8 @@ impl ListEnvironments { #[derive(Parser)] pub struct RemoveEnvironment { - /// Name of the environment to remove - pub name: Option, + /// ID of the environment to remove + pub id: Option, } impl RemoveEnvironment { @@ -99,26 +109,32 @@ impl RemoveEnvironment { bail!("no environments defined") } - // Prompt name if not set - let name = match self.name { - Some(name) => name, - None => Select::new( - "Select the project to uninstall:", - config.environment_names()?, - ) - .prompt()? - .to_owned(), + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Prompt id if not set + let id = match self.id { + Some(id) => id, + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment to remove:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } }; - // Because hash is computed from name, + // Because hash is computed from id, // best method discovered to prevent immutable borrow to get &Plugin for deletion let fake = Environment { - name, + id, ..Default::default() }; if !config.environments.remove(&fake) { - bail!("environment `{}` does not exists", &fake.name) + bail!("environment `{}` does not exists", &fake.id) }; config.save_config(None) @@ -127,13 +143,12 @@ impl RemoveEnvironment { #[derive(Parser)] pub struct LinkEnvironment { - /// Name of the environment - #[clap(short, long)] - pub env_name: Option, + /// ID of the environment + pub env_id: Option, - /// Name of the service to link to this environment + /// ID of the service to link to this environment #[clap(short, long)] - pub service_name: Option, + pub service_id: Option, } impl LinkEnvironment { @@ -142,40 +157,46 @@ impl LinkEnvironment { bail!("no environments defined") } - // Prompt name if not set - let name = self.env_name.unwrap_or_else(|| { - Select::new( - "Select the environment:", - config.environment_names().unwrap(), - ) - .prompt() - .unwrap() - .to_owned() - }); + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Prompt id if not set + let id = match self.env_id { + Some(id) => id, + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } + }; // Retrieve environment let mut env = config .environments .get(&Environment { - name: name.clone(), + id: id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("environment `{}` does not exist", &name))? + .ok_or_else(|| anyhow!("environment `{}` does not exist", &id))? .clone(); // Retrieve selected services - let services: Vec = config.service_names()?; - let service_name = match self.service_name { - Some(name) => { - if !services.contains(&name) { - bail!("service `{}` does not exist", &name) + let services: Vec<&str> = config.service_ids()?; + let service_id = match &self.service_id { + Some(id) => { + if !services.contains(&id.as_str()) { + bail!("service `{}` does not exist", &id) } - name + id.to_string() } None => { let env_services: Vec<_> = services .iter() - .filter(|&svc_name| !env.services.contains(svc_name)) + .filter(|&&svc_id| !env.services.contains(svc_id)) .cloned() .collect(); @@ -183,29 +204,30 @@ impl LinkEnvironment { bail!("no available service to link to this environment") } - Select::new("Service to link:", env_services).prompt()? + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Service to link:") + .items(&env_services) + .default(0) + .interact()?; + env_services[selection].to_string() } }; - env.services.insert(service_name.to_string()); + env.services.insert(service_id.to_string()); config.environments.replace(env); - println!( - "service `{}` linked to environement `{}`", - service_name, name - ); + tracing::info!("service `{}` linked to environement `{}`", service_id, id); config.save_config(None) } } #[derive(Parser)] pub struct UnlinkEnvironment { - /// Name of the environment - #[clap(short, long)] - pub env_name: Option, + /// ID of the environment + pub env_id: Option, - /// Name of the service to unlink from this environment + /// ID of the service to unlink from this environment #[clap(short, long)] - pub service_name: Option, + pub service_id: Option, } impl UnlinkEnvironment { @@ -214,44 +236,50 @@ impl UnlinkEnvironment { bail!("no environments defined") } - // Prompt name if not set - let name = self.env_name.unwrap_or_else(|| { - Select::new( - "Select the environment:", - config.environment_names().unwrap(), - ) - .prompt() - .unwrap() - .to_owned() - }); + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + + // Prompt id if not set + let id = match self.env_id { + Some(id) => id, + None => { + let environment = config.environment_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the environment:") + .items(&environment) + .default(0) + .interact()?; + environment[selection].to_string() + } + }; // Retrieve environment let mut env = config .environments .get(&Environment { - name: name.clone(), + id: id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("environment `{}` does not exist", &name))? + .ok_or_else(|| anyhow!("environment `{}` does not exist", &id))? .clone(); // Retrieve selected services - let services: Vec = config.service_names()?; - let service_name = match self.service_name { - Some(name) => { - if !env.services.contains(&name) { + let services: Vec<&str> = config.service_ids()?; + let service_id = match self.service_id { + Some(id) => { + if !env.services.contains(&id) { bail!( "service `{}` is not linked to environment `{}`", - &name, - &env.name + &id, + &env.id ) } - name + id } None => { let env_services: Vec<_> = services .iter() - .filter(|&svc_name| env.services.contains(svc_name)) + .filter(|&&svc_id| env.services.contains(svc_id)) .cloned() .collect(); @@ -259,15 +287,21 @@ impl UnlinkEnvironment { bail!("no available service to unlink from this environment") } - Select::new("Service to unlink:", env_services).prompt()? + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Service to link:") + .items(&env_services) + .default(0) + .interact()?; + env_services[selection].to_string() } }; - env.services.remove(&service_name); + env.services.remove(&service_id); config.environments.replace(env); - println!( + tracing::info!( "service `{}` unlinked from environement `{}`", - service_name, name + service_id, + id ); config.save_config(None) } diff --git a/src/commands/init.rs b/src/commands/init.rs index 01d2540..f4589fb 100644 --- a/src/commands/init.rs +++ b/src/commands/init.rs @@ -34,8 +34,8 @@ impl InitCommand { let rules_dir = &project_path.join(LGC_RULES_DIR); if Path::new(rules_dir).exists() { - println!( - "warn: rules folder already exists in `{}`", + tracing::warn!( + "rules folder already exists in `{}`", &project_path.canonicalize()?.display() ) } @@ -48,7 +48,7 @@ impl InitCommand { let full_path = &project_path.join(LGC_CONFIG_PATH); if File::create_new(full_path).is_err() { bail!( - "error: `{}` already exists in `{}`", + "`{}` already exists in `{}`", LGC_CONFIG_PATH, &project_path.canonicalize()?.display() ) @@ -56,7 +56,7 @@ impl InitCommand { ProjectConfiguration::default().save_config(Some(full_path))?; - println!( + tracing::info!( "LogCraft configuration initialized in `{}`", &project_path.canonicalize()?.display() ); diff --git a/src/commands/plugins.rs b/src/commands/plugins.rs index ece7266..e7cdd9e 100644 --- a/src/commands/plugins.rs +++ b/src/commands/plugins.rs @@ -3,7 +3,8 @@ use anyhow::{anyhow, bail, Result}; use clap::{Parser, Subcommand}; -use inquire::{Confirm, Select, Text}; +use console::style; +use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; use logcraft_common::{ configuration::ProjectConfiguration, plugins::{ @@ -17,7 +18,7 @@ use std::path::PathBuf; /// Manage plugins #[derive(Subcommand)] pub enum PluginsCommands { - /// Install plugin from archive + /// Install plugin #[clap(alias = "i")] Install(InstallPlugin), @@ -27,10 +28,10 @@ pub enum PluginsCommands { /// Remove plugin Uninstall(UninstallPlugin), - /// Update plugin from source + /// Update plugin Update(UpdatePlugin), - /// Get plugin configuration informations + /// Get plugin configuration schema Schema(PluginSchema), } @@ -48,24 +49,24 @@ impl PluginsCommands { #[derive(Parser)] pub struct InstallPlugin { - /// Source to fecth plugin from in URI format - (file:// | http(s):// ) + /// Location of the plugin pub source: Option, - - /// Version of plugin to fetch - #[clap(default_value = "latest")] - pub version: String, - - /// Overwrite plugin if exists - #[clap(short, long)] - pub force: bool, + // /// Version of plugin to fetch + // #[clap(default_value = "latest")] + // pub version: String, } impl InstallPlugin { pub async fn run(self, config: &mut ProjectConfiguration) -> Result<()> { + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Prompt source if not set let source = match self.source { Some(source) => source, - None => Text::new("Plugin source:").prompt()?, + None => Input::::with_theme(&prompt_theme) + .with_prompt("Plugin source:") + .interact_text()?, }; // Determine the plugin location @@ -107,7 +108,11 @@ impl ListPlugin { // Iterate and print plugin information config.plugins.iter().for_each(|(name, plugin)| { - println!("`{}` (`{}`)", name, plugin.version); + println!( + "- `{}` (`{}`)", + style(name).bold(), + style(&plugin.version).bold() + ); }); Ok(()) @@ -116,7 +121,7 @@ impl ListPlugin { #[derive(Parser)] pub struct UninstallPlugin { - /// Local name of LogCraft plugin. + /// Name of the plugin. pub name: Option, /// Force plugin removal, including all associated services and environments @@ -130,13 +135,20 @@ impl UninstallPlugin { bail!("no plugin installed") } + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + let name = match self.name { Some(name) => name, - None => Select::new( - "Select the plugin to uninstall:", - config.plugins.keys().cloned().collect(), - ) - .prompt()?, + None => { + let plugins = config.plugins.keys().cloned().collect::>(); + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the plugin to uninstall:") + .items(&plugins) + .default(0) + .interact()?; + plugins[selection].clone() + } }; if config.plugins.remove(&name).is_none() { @@ -147,25 +159,25 @@ impl UninstallPlugin { .services .iter() .filter(|svc| svc.plugin == name) - .map(|svc| svc.name.clone()) + .map(|svc| svc.id.clone()) .collect::>(); // If removal is not forced, check if plugin is used in any service if !self.force && !services.is_empty() - && !Confirm::new(&format!( - "This plugin is used in `{}` services(s), remove with occurences ?", - services.join(",") - )) - .with_default(false) - .prompt()? + && !Confirm::with_theme(&prompt_theme) + .with_prompt(&format!( + "This plugin is used in `{}` services(s), force removal ?", + style(services.join(", ")).red() + )) + .interact()? { bail!("action aborted") } - for svc_name in services { - config.remove_service(&svc_name); - config.unlink_environments(&svc_name) + for svc_id in services { + config.remove_service(&svc_id); + config.unlink_environments(&svc_id) } cleanup_plugin(&name)?; @@ -175,7 +187,7 @@ impl UninstallPlugin { #[derive(Parser)] pub struct PluginSchema { - /// Local name of LogCraft plugin. + /// Name of the plugin. pub name: Option, } @@ -185,12 +197,21 @@ impl PluginSchema { bail!("no plugin installed") } + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Prompt name if not set let name = match self.name { Some(name) => name, - None => Select::new("Select the plugin:", config.plugins.keys().collect()) - .prompt()? - .to_owned(), + None => { + let plugins = config.plugins.keys().cloned().collect::>(); + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the plugin:") + .items(&plugins) + .default(0) + .interact()?; + plugins[selection].clone() + } }; // Load plugin @@ -206,7 +227,7 @@ impl PluginSchema { #[derive(Parser)] pub struct UpdatePlugin { - /// Local name of LogCraft plugin. + /// Name of the plugin. pub name: Option, } @@ -216,12 +237,21 @@ impl UpdatePlugin { bail!("no plugin installed") } + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Prompt name if not set let name = match self.name { Some(name) => name, - None => Select::new("Select the plugin:", config.plugins.keys().collect()) - .prompt()? - .to_owned(), + None => { + let plugins = config.plugins.keys().cloned().collect::>(); + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the plugin:") + .items(&plugins) + .default(0) + .interact()?; + plugins[selection].clone() + } }; let plugin = config @@ -237,13 +267,13 @@ impl UpdatePlugin { // ! Not needed for now - Update isn't available for Local source. // // Load plugin // let meta = PluginManager::new()?.install_plugin(&plugin.source).await?; - // println!( + // tracing::info!( // "`{}` plugin loaded with version: `{}`", - // &meta.name, + // &meta.id, // &meta.version // ); - // config.plugins.insert(meta.name, Plugin { + // config.plugins.insert(meta.id, Plugin { // source: plugin.source, // version: meta.version, // description: meta.description, diff --git a/src/commands/services.rs b/src/commands/services.rs index 383f4d0..890f5b6 100644 --- a/src/commands/services.rs +++ b/src/commands/services.rs @@ -3,13 +3,15 @@ use anyhow::{anyhow, bail, Result}; use clap::{Parser, Subcommand}; -use inquire::{Confirm, Select, Text}; +use console::style; +use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; +use indicatif::{ProgressBar, ProgressStyle}; use logcraft_common::{ configuration::{ProjectConfiguration, Service}, plugins::manager::{PluginActions, PluginManager}, utils, }; -use std::collections::HashMap; +use std::{collections::HashMap, time::Duration}; use tokio::task::JoinSet; /// Manage backend services @@ -45,33 +47,37 @@ impl ServicesCommands { #[derive(Parser)] pub struct AddService { - /// Name of the service to create - pub name: Option, + /// ID of the service to create + pub id: Option, /// Name of the plugin used by this service #[clap(short, long)] pub plugin_name: Option, - /// Enable prompt for plugin settings + /// Interactive service configuration #[clap(long)] pub configure: bool, - - /// Enable prompt for plugin settings - #[clap(long)] - pub insecure: bool, } impl AddService { pub async fn run(self, config: &mut ProjectConfiguration) -> Result<()> { + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Choose plugin if not set let plugins: Vec<&str> = config.plugins.keys().map(|k| k.as_str()).collect(); let plugin_name = match &self.plugin_name { - Some(name) => name, + Some(id) => id, None => { if plugins.is_empty() { - bail!("the configuration does not have any plugin") + bail!("no plugin installed") } - Select::new("Select the plugin to use:", plugins.clone()).prompt()? + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the plugin to use:") + .items(&plugins) + .default(0) + .interact()?; + plugins[selection] } }; @@ -79,28 +85,30 @@ impl AddService { bail!("plugin `{}` does not exists", &plugin_name) } - // Prompt name if not set - let name = match self.name { - Some(name) => name, - None => Text::new("Service name:").prompt()?, + // Prompt id if not set + let id = match self.id { + Some(id) => id, + // None => Text::new("Service id:").prompt()?, + None => Input::::with_theme(&prompt_theme) + .with_prompt("Service id:") + .interact_text()?, }; // Naming contraints check - let name = utils::ensure_kebab_case(&name)?; + let id = utils::ensure_kebab_case(&id)?; let mut service = Service { - name: name.clone(), + id: id.to_string(), plugin: plugin_name.to_string(), ..Default::default() }; if config.services.contains(&service) - && !Confirm::new("This service already exists, overwrite ?") - .with_default(false) - .prompt()? + && !Confirm::with_theme(&prompt_theme) + .with_prompt("This service already exists, overwrite ?") + .interact()? { - println!("action aborted"); - return Ok(()); + bail!("action aborted") } // Load plugin @@ -110,16 +118,16 @@ impl AddService { service.configure(instance.settings(&mut store).await?, !self.configure)?; config.services.insert(service); - println!("service `{}` created", &name); + tracing::info!("service `{}` created", &id); config.save_config(None) } } #[derive(Parser)] pub struct ListServices { - /// Name of the environment. + /// ID of the environment. #[clap(short, long)] - pub env_name: Option, + pub env_id: Option, } impl ListServices { @@ -129,7 +137,11 @@ impl ListServices { } for svc in &config.services { - println!("`{}` (`{}`)", &svc.name, svc.plugin); + println!( + "- `{}` (`{}`)", + style(&svc.id).bold(), + style(&svc.plugin).bold() + ); } Ok(()) } @@ -137,8 +149,8 @@ impl ListServices { #[derive(Parser)] pub struct RemoveService { - /// Name of the service to remove. - pub name: Option, + /// ID of the service to remove. + pub id: Option, /// Force service removal and its references #[clap(short, long, default_value = "false")] @@ -151,40 +163,49 @@ impl RemoveService { bail!("no services defined") } + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Choose service if not set - let name = match self.name { - Some(name) => name, - None => Select::new("Select the service:", config.service_names()?) - .prompt()? - .to_owned(), + let id = match self.id { + Some(id) => id, + None => { + let services = config.service_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the service:") + .items(&services) + .default(0) + .interact()?; + services[selection].to_string() + } }; // Check if service exists - if !config.services.iter().any(|svc| svc.name == name) { - bail!("service `{}` does not exists", &name) + if !config.services.iter().any(|svc| svc.id == id) { + bail!("service `{}` does not exists", &id) } // If removal is not forced, check if service is used in any environment if !self.force // check if service is used in any environment - && config.environments.iter().any(|env| env.services.contains(&name)) - && !Confirm::new("This service is used in some environment(s), remove it along with its references ?").with_default(false).prompt()? - { - bail!("action aborted") - } + && config.environments.iter().any(|env| env.services.contains(&id)) + && !Confirm::with_theme(&prompt_theme).with_prompt("This service is used in some environment(s), force removal ?").interact()? + { + bail!("action aborted") + } // remove all occurences of service in environments - config.unlink_environments(&name); + config.unlink_environments(&id); - config.remove_service(&name); + config.remove_service(&id); config.save_config(None) } } #[derive(Parser)] pub struct ConfigureService { - /// name of the service to configure - pub name: Option, + /// id of the service to configure + pub id: Option, } impl ConfigureService { @@ -193,21 +214,30 @@ impl ConfigureService { bail!("no services defined") } + // Prompt theme + let prompt_theme = ColorfulTheme::default(); + // Choose service if not set - let name = self.name.unwrap_or_else(|| { - Select::new("Select the service:", config.service_names().unwrap()) - .prompt() - .unwrap() - .to_owned() - }); + let id = match self.id { + Some(id) => id, + None => { + let services = config.service_ids()?; + let selection = Select::with_theme(&prompt_theme) + .with_prompt("Select the service:") + .items(&services) + .default(0) + .interact()?; + services[selection].to_string() + } + }; let mut service = config .services .take(&Service { - name: name.clone(), + id: id.clone(), ..Default::default() }) - .ok_or_else(|| anyhow!("service `{}` does not exist", &name))?; + .ok_or_else(|| anyhow!("service `{}` does not exist", &id))?; // Load plugin let (instance, mut store) = PluginManager::new()?.load_plugin(&service.plugin).await?; @@ -216,16 +246,18 @@ impl ConfigureService { service.configure(instance.settings(&mut store).await?, false)?; config.services.insert(service); - println!("service `{}` configured", &name); + tracing::info!("service `{}` configured", &id); config.save_config(None) } } +pub const SPINNER: &[&str; 4] = &["-", "\\", "|", "/"]; + #[derive(Parser)] pub struct PingService; impl PingService { - pub async fn run(self, config: &mut ProjectConfiguration) -> Result<()> { + pub async fn run(self, config: &ProjectConfiguration) -> Result<()> { if config.services.is_empty() { bail!("no services defined") } @@ -258,12 +290,25 @@ impl PingService { .ok_or_else(|| anyhow!("plugin `{}` instance not found", &meta.name))? .iter() { - let config = &serde_json::to_string(&svc.settings)?; + let spinner = ProgressBar::new_spinner(); + spinner.enable_steady_tick(Duration::from_millis(130)); + spinner.set_style( + ProgressStyle::with_template("{spinner:.bold.dim} {msg}") + .unwrap() + .tick_strings(SPINNER), + ); + spinner.set_message(svc.id.clone()); + let config = &serde_json::to_string(&svc.settings)?; if let Err(e) = instance.ping(&mut store, config).await { - println!("{}... {}", &svc.name, e); + spinner.finish_with_message(format!( + "{} ... {}", + style(&svc.id).bold().red(), + e + )); } else { - println!("{}... OK", &svc.name); + spinner + .finish_with_message(format!("{} ... OK", style(&svc.id).bold().green())); } } } diff --git a/src/commands/validate.rs b/src/commands/validate.rs index 16c4b68..f33c953 100644 --- a/src/commands/validate.rs +++ b/src/commands/validate.rs @@ -11,7 +11,6 @@ use logcraft_common::{ detections::map_plugin_detections, plugins::manager::{PluginActions, PluginManager}, }; - /// Validate configuration #[derive(Parser, Debug, Default)] #[clap(about = "Validate local detection rules", allow_hyphen_values = true)] @@ -56,25 +55,25 @@ impl ValidateCommand { let check = serv.validate_code(&args)?; if !check.success { has_err = true; - eprintln!("`{}`", check.err_message); + tracing::error!("`{}`", check.err_message); } } // Check rules args.code = instance.schema(&mut store).await?; args.schema = String::from("Rule"); - for (_, rule) in rules { - args.data = serde_yaml_ng::to_string(rule)?; + for detection in rules { + args.data = serde_yaml_ng::to_string(&detection.content)?; let check = serv.validate_code(&args)?; if !check.success { has_err = true; - eprintln!("`{}`", check.err_message); + tracing::error!("`{}`", check.err_message); } } } if !has_err { - println!("all good, no problems identified"); + tracing::info!("all good, no problems identified"); } Ok(())