├── .deepsource.toml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── pull_request_template.md └── workflows │ ├── build-push-edge-debug.yaml │ ├── build-push-edge-kafka.yaml │ ├── build-push-edge.yaml │ ├── build.yaml │ ├── cla.yaml │ ├── coverage.yaml │ ├── integration-test.yaml │ └── release.yml ├── .gitignore ├── CITATION.cff ├── CNAME ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Cross.toml ├── Dockerfile ├── Dockerfile.debug ├── Dockerfile.dev ├── Dockerfile.kafka ├── LICENSE ├── Makefile ├── README.md ├── USERS.md ├── about.hbs ├── about.toml ├── build.rs ├── docker-compose-distributed-test-with-kafka.yaml ├── docker-compose-distributed-test.yaml ├── docker-compose-local.yaml ├── docker-compose-test-with-kafka.yaml ├── docker-compose-test.yaml ├── helm-reindex.sh ├── helm-releases ├── parseable-0.0.1.tgz ├── parseable-0.0.2.tgz ├── parseable-0.0.5.tgz ├── parseable-0.0.6.tgz ├── parseable-0.0.7.tgz ├── parseable-0.0.8.tgz ├── parseable-0.1.0.tgz ├── parseable-0.1.1.tgz ├── parseable-0.2.0.tgz ├── parseable-0.2.1.tgz ├── parseable-0.2.2.tgz ├── parseable-0.3.0.tgz ├── parseable-0.3.1.tgz ├── parseable-0.4.0.tgz ├── parseable-0.4.1.tgz ├── parseable-0.4.2.tgz ├── parseable-0.4.3.tgz ├── parseable-0.4.4.tgz ├── parseable-0.4.5.tgz ├── parseable-0.5.0.tgz ├── parseable-0.5.1.tgz ├── parseable-0.6.0.tgz ├── parseable-0.6.1.tgz ├── parseable-0.6.2.tgz ├── parseable-0.7.0.tgz ├── parseable-0.7.1.tgz ├── parseable-0.7.2.tgz ├── parseable-0.7.3.tgz ├── parseable-0.8.0.tgz ├── parseable-0.8.1.tgz ├── parseable-0.9.0.tgz ├── parseable-1.0.0.tgz ├── parseable-1.1.0.tgz ├── parseable-1.2.0.tgz ├── parseable-1.3.0.tgz ├── parseable-1.3.1.tgz ├── parseable-1.4.0.tgz ├── parseable-1.4.1.tgz ├── parseable-1.5.0.tgz ├── parseable-1.5.1.tgz ├── parseable-1.5.2.tgz ├── parseable-1.5.3.tgz ├── parseable-1.5.4.tgz ├── parseable-1.5.5.tgz ├── parseable-1.6.0.tgz ├── parseable-1.6.1.tgz ├── parseable-1.6.2.tgz ├── parseable-1.6.3.tgz ├── parseable-1.6.4.tgz ├── parseable-1.6.5.tgz ├── parseable-1.6.6.tgz ├── parseable-1.6.7.tgz ├── parseable-1.6.8.tgz ├── parseable-1.7.0.tgz ├── parseable-1.7.1.tgz ├── parseable-1.7.2.tgz ├── parseable-1.7.3.tgz ├── parseable-1.7.5.tgz ├── parseable-2.0.0.tgz ├── parseable-2.1.0.tgz ├── parseable-2.3.0.tgz ├── parseable-2.3.1.tgz └── parseable-2.3.2.tgz ├── helm ├── Chart.lock ├── Chart.yaml ├── README.md ├── charts │ ├── fluent-bit-0.48.1.tgz │ └── vector-0.20.1.tgz ├── templates │ ├── _helpers.tpl │ ├── _helpers_config_logstream.txt │ ├── data-pvc.yaml │ ├── ingestor-service.yaml │ ├── ingestor-statefulset.yaml │ ├── logstream-configmap.yaml │ ├── logstream-job.yaml │ ├── querier-service.yaml │ ├── querier-statefulset.yaml │ ├── service-monitor.yaml │ ├── serviceaccount.yaml │ ├── stage-pvc.yaml │ ├── standalone-deployment.yaml │ └── standalone-service.yaml └── values.yaml ├── index.yaml ├── parseable-ingest-haproxy.cfg ├── resources └── formats.json ├── scripts ├── Dockerfile ├── download.ps1 ├── download.sh └── kafka_log_stream_generator.py ├── src ├── about.rs ├── alerts │ ├── alerts_utils.rs │ ├── mod.rs │ └── target.rs ├── analytics.rs ├── audit.rs ├── banner.rs ├── catalog │ ├── column.rs │ ├── manifest.rs │ ├── mod.rs │ └── snapshot.rs ├── cli.rs ├── connectors │ ├── common │ │ ├── mod.rs │ │ ├── processor.rs │ │ └── shutdown.rs │ ├── kafka │ │ ├── config.rs │ │ ├── consumer.rs │ │ ├── metrics.rs │ │ ├── mod.rs │ │ ├── partition_stream.rs │ │ ├── processor.rs │ │ ├── rebalance_listener.rs │ │ ├── sink.rs │ │ └── state.rs │ └── mod.rs ├── correlation.rs ├── enterprise │ ├── mod.rs │ └── utils.rs ├── event │ ├── format │ │ ├── json.rs │ │ ├── known_schema.rs │ │ └── mod.rs │ └── mod.rs ├── handlers │ ├── airplane.rs │ ├── http │ │ ├── about.rs │ │ ├── alerts.rs │ │ ├── audit.rs │ │ ├── cluster │ │ │ ├── mod.rs │ │ │ └── utils.rs │ │ ├── correlation.rs │ │ ├── health_check.rs │ │ ├── ingest.rs │ │ ├── kinesis.rs │ │ ├── llm.rs │ │ ├── logstream.rs │ │ ├── middleware.rs │ │ ├── mod.rs │ │ ├── modal │ │ │ ├── ingest │ │ │ │ ├── ingestor_ingest.rs │ │ │ │ ├── ingestor_logstream.rs │ │ │ │ ├── ingestor_rbac.rs │ │ │ │ ├── ingestor_role.rs │ │ │ │ └── mod.rs │ │ │ ├── ingest_server.rs │ │ │ ├── mod.rs │ │ │ ├── query │ │ │ │ ├── mod.rs │ │ │ │ ├── querier_ingest.rs │ │ │ │ ├── querier_logstream.rs │ │ │ │ ├── querier_rbac.rs │ │ │ │ └── querier_role.rs │ │ │ ├── query_server.rs │ │ │ ├── server.rs │ │ │ ├── ssl_acceptor.rs │ │ │ └── utils │ │ │ │ ├── ingest_utils.rs │ │ │ │ ├── logstream_utils.rs │ │ │ │ ├── mod.rs │ │ │ │ └── rbac_utils.rs │ │ ├── oidc.rs │ │ ├── prism_home.rs │ │ ├── prism_logstream.rs │ │ ├── query.rs │ │ ├── rbac.rs │ │ ├── role.rs │ │ └── users │ │ │ ├── dashboards.rs │ │ │ ├── filters.rs │ │ │ └── mod.rs │ ├── livetail.rs │ └── mod.rs ├── hottier.rs ├── lib.rs ├── livetail.rs ├── logstream │ └── mod.rs ├── main.rs ├── metadata.rs ├── metrics │ ├── mod.rs │ ├── prom_utils.rs │ └── storage.rs ├── migration │ ├── metadata_migration.rs │ ├── mod.rs │ ├── schema_migration.rs │ └── stream_metadata_migration.rs ├── oidc.rs ├── option.rs ├── otel.rs ├── otel │ ├── logs.rs │ ├── metrics.rs │ ├── otel_utils.rs │ └── traces.rs ├── parseable │ ├── mod.rs │ ├── staging │ │ ├── mod.rs │ │ ├── reader.rs │ │ └── writer.rs │ └── streams.rs ├── prism │ ├── home │ │ └── mod.rs │ ├── logstream │ │ └── mod.rs │ └── mod.rs ├── query │ ├── filter_optimizer.rs │ ├── listing_table_builder.rs │ ├── mod.rs │ └── stream_schema_provider.rs ├── rbac │ ├── map.rs │ ├── mod.rs │ ├── role.rs │ ├── user.rs │ └── utils.rs ├── response.rs ├── static_schema.rs ├── stats.rs ├── storage │ ├── azure_blob.rs │ ├── localfs.rs │ ├── metrics_layer.rs │ ├── mod.rs │ ├── object_storage.rs │ ├── retention.rs │ ├── s3.rs │ └── store_metadata.rs ├── sync.rs ├── users │ ├── dashboards.rs │ ├── filters.rs │ └── mod.rs ├── utils │ ├── actix.rs │ ├── arrow │ │ ├── batch_adapter.rs │ │ ├── flight.rs │ │ └── mod.rs │ ├── header_parsing.rs │ ├── human_size.rs │ ├── json │ │ ├── flatten.rs │ │ └── mod.rs │ ├── mod.rs │ ├── time.rs │ ├── uid.rs │ └── update.rs └── validator.rs └── systemd ├── README.md ├── parseable.local.service └── parseable.s3.service /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "rust" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | msrv = "1.63.0" -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | data 3 | staging -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report for Parseable. 4 | labels: bug 5 | --- 6 | 10 | 11 | `/about` output: 12 | 16 | ```json 17 | { 18 | "version": "...", 19 | ... 20 | } 21 | ``` 22 | 23 | Description: 24 | 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Fixes #XXXX. 4 | 5 | 6 | 7 | ### Description 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | This PR has: 18 | - [ ] been tested to ensure log ingestion and log query works. 19 | - [ ] added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader. 20 | - [ ] added documentation for new or modified features or behaviors. 21 | -------------------------------------------------------------------------------- /.github/workflows/build-push-edge-debug.yaml: -------------------------------------------------------------------------------- 1 | name: Build and push edge debug tag 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'helm/**' 10 | - 'assets/**' 11 | - '**.md' 12 | 13 | jobs: 14 | docker: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v3 22 | with: 23 | image: tonistiigi/binfmt:qemu-v8.1.5 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@v3 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | - name: Extract metadata (tags, labels) for Docker 35 | id: meta 36 | uses: docker/metadata-action@v5 37 | with: 38 | images: parseable/parseable 39 | 40 | - name: Build and push 41 | uses: docker/build-push-action@v6 42 | with: 43 | context: . 44 | file: ./Dockerfile.debug 45 | push: true 46 | tags: parseable/parseable:edge-debug 47 | platforms: linux/amd64,linux/arm64 48 | -------------------------------------------------------------------------------- /.github/workflows/build-push-edge-kafka.yaml: -------------------------------------------------------------------------------- 1 | name: Build and push edge kafka tag 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'helm/**' 10 | - 'assets/**' 11 | - '**.md' 12 | 13 | jobs: 14 | docker: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v3 22 | with: 23 | image: tonistiigi/binfmt:qemu-v8.1.5 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | - name: Extract metadata (tags, labels) for Docker 35 | id: meta 36 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 37 | with: 38 | images: parseable/parseable 39 | 40 | - name: Build and push x86_64 41 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 42 | with: 43 | context: . 44 | file: ./Dockerfile.kafka 45 | push: true 46 | tags: parseable/parseable:edge-kafka 47 | platforms: linux/amd64 48 | build-args: | 49 | LIB_DIR=x86_64-linux-gnu 50 | 51 | - name: Build and push aarch64 52 | uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc 53 | with: 54 | context: . 55 | file: ./Dockerfile.kafka 56 | push: true 57 | tags: parseable/parseable:edge-kafka 58 | platforms: linux/arm64 59 | build-args: | 60 | LIB_DIR=aarch64-linux-gnu 61 | -------------------------------------------------------------------------------- /.github/workflows/build-push-edge.yaml: -------------------------------------------------------------------------------- 1 | name: Build and push edge tag 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths-ignore: 8 | - 'docs/**' 9 | - 'helm/**' 10 | - 'assets/**' 11 | - '**.md' 12 | 13 | jobs: 14 | docker: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v3 22 | with: 23 | image: tonistiigi/binfmt:qemu-v8.1.5 24 | 25 | - name: Set up Docker Buildx 26 | uses: docker/setup-buildx-action@v3 27 | 28 | - name: Login to Docker Hub 29 | uses: docker/login-action@v3 30 | with: 31 | username: ${{ secrets.DOCKERHUB_USERNAME }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | 34 | - name: Extract metadata (tags, labels) for Docker 35 | id: meta 36 | uses: docker/metadata-action@v5 37 | with: 38 | images: parseable/parseable 39 | 40 | - name: Build and push 41 | uses: docker/build-push-action@v6 42 | with: 43 | context: . 44 | file: ./Dockerfile 45 | push: true 46 | tags: parseable/parseable:edge 47 | platforms: linux/amd64,linux/arm64 48 | -------------------------------------------------------------------------------- /.github/workflows/cla.yaml: -------------------------------------------------------------------------------- 1 | name: "CLA Assistant" 2 | on: 3 | issue_comment: 4 | types: [created] 5 | pull_request_target: 6 | types: [opened,closed,synchronize] 7 | 8 | # explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings 9 | permissions: 10 | actions: write 11 | contents: write 12 | pull-requests: write 13 | statuses: write 14 | 15 | jobs: 16 | CLAAssistant: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: "CLA Assistant" 20 | if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' 21 | uses: contributor-assistant/github-action@v2.3.0 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | # the below token should have repo scope and must be manually added by you in the repository's secret 25 | # This token is required only if you have configured to store the signatures in a remote repository/organization 26 | PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 27 | with: 28 | remote-organization-name: 'parseablehq' # enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) 29 | remote-repository-name: '.github' # enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) 30 | path-to-signatures: 'signatures/version1/cla.json' 31 | path-to-document: 'https://github.com/parseablehq/.github/blob/main/CLA.md' # e.g. a CLA or a DCO document 32 | # branch should not be protected 33 | branch: 'main' 34 | allowlist: dependabot[bot],deepsource-autofix[bot],deepsourcebot 35 | 36 | # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken 37 | #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) 38 | #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) 39 | #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' 40 | #signed-commit-message: 'For example: $contributorName has signed the CLA in $owner/$repo#$pullRequestNo' 41 | #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign' 42 | #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' 43 | #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' 44 | #- if you don't want this bot to automatically lock the pull request after merging (default - true) 45 | lock-pullrequest-aftermerge: false 46 | #use-dco-flag: true - If you are using DCO instead of CLA 47 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | paths-ignore: 4 | - "docs/**" 5 | - "helm/**" 6 | - "assets/**" 7 | - "**.md" 8 | 9 | name: Lint, Test and Coverage Report 10 | jobs: 11 | coverage: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@stable 16 | with: 17 | components: clippy 18 | 19 | - uses: Swatinem/rust-cache@v2 20 | with: 21 | shared-key: ${{ runner.os }}-cargo 22 | 23 | - uses: taiki-e/install-action@v2 24 | with: 25 | tool: cargo-hack, cargo-llvm-cov, nextest 26 | 27 | - name: Install System Dependencies 28 | run: | 29 | sudo apt-get update 30 | sudo apt-get install -y \ 31 | libsasl2-dev \ 32 | libssl-dev \ 33 | pkg-config \ 34 | build-essential 35 | if: runner.os == 'Linux' 36 | 37 | - name: Find and fix librdkafka CMakeLists.txt 38 | run: | 39 | # Download the package first so it's in the registry 40 | cargo fetch 41 | 42 | # Find the rdkafka-sys package directory 43 | RDKAFKA_SYS_DIR=$(find ~/.cargo/registry/src -name "rdkafka-sys-*" -type d | head -n 1) 44 | echo "Found rdkafka-sys at: $RDKAFKA_SYS_DIR" 45 | 46 | # Find the librdkafka CMakeLists.txt file 47 | CMAKE_FILE="$RDKAFKA_SYS_DIR/librdkafka/CMakeLists.txt" 48 | 49 | if [ -f "$CMAKE_FILE" ]; then 50 | echo "Found CMakeLists.txt at: $CMAKE_FILE" 51 | 52 | # Make a backup of the original file 53 | cp "$CMAKE_FILE" "$CMAKE_FILE.bak" 54 | 55 | # Replace the minimum required version 56 | sed -i 's/cmake_minimum_required(VERSION 3.2)/cmake_minimum_required(VERSION 3.5)/' "$CMAKE_FILE" 57 | 58 | echo "Modified CMakeLists.txt - before and after comparison:" 59 | diff "$CMAKE_FILE.bak" "$CMAKE_FILE" || true 60 | else 61 | echo "Could not find librdkafka CMakeLists.txt file!" 62 | exit 1 63 | fi 64 | 65 | - name: Check with clippy 66 | run: cargo hack clippy --verbose --each-feature --no-dev-deps -- -D warnings 67 | 68 | - name: Test default feature set 69 | run: cargo hack llvm-cov --no-report nextest 70 | 71 | - name: Test kafka feature 72 | run: cargo hack --features kafka llvm-cov --no-report nextest --filter-expr 'test(kafka)' 73 | 74 | - name: Generate coverage report 75 | run: cargo llvm-cov report --lcov --output-path coverage.lcov 76 | 77 | - name: Upload Coverage Report 78 | uses: coverallsapp/github-action@v2.2.3 79 | with: 80 | github-token: ${{ secrets.GITHUB_TOKEN }} 81 | file: ./coverage.lcov 82 | parallel: true 83 | flag-name: run-${{ matrix.os }}-cargo 84 | 85 | - name: Finish Coverage Report 86 | uses: coverallsapp/github-action@v2.2.3 87 | with: 88 | github-token: ${{ secrets.GITHUB_TOKEN }} 89 | parallel-finished: true 90 | -------------------------------------------------------------------------------- /.github/workflows/integration-test.yaml: -------------------------------------------------------------------------------- 1 | name: Integration Tests with Quest 2 | on: 3 | pull_request: 4 | paths-ignore: 5 | - "docs/**" 6 | - "helm/**" 7 | - "assets/**" 8 | - "**.md" 9 | 10 | jobs: 11 | 12 | docker-compose-test: 13 | name: Quest Smoke and Load Tests for Standalone deployments 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Start compose 19 | run: docker compose -f docker-compose-test.yaml up --build --exit-code-from quest 20 | - name: Stop compose 21 | if: always() 22 | run: docker compose -f docker-compose-test.yaml down -v 23 | 24 | docker-compose-distributed-test: 25 | name: Quest Smoke and Load Tests for Distributed deployments 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Start compose 31 | run: docker compose -f docker-compose-distributed-test.yaml up --build --exit-code-from quest 32 | - name: Stop compose 33 | if: always() 34 | run: docker compose -f docker-compose-distributed-test.yaml down -v 35 | 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | data* 3 | staging/* 4 | limitcache 5 | examples 6 | cert.pem 7 | key.pem 8 | .env* 9 | .vscode 10 | helm-releases/.DS_Store 11 | .DS_Store 12 | env-file 13 | parseable/* 14 | parseable_* 15 | parseable-env-secret 16 | cache 17 | .idea 18 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | message: "If you use this software, please cite it as below." 3 | authors: 4 | - family-names: "Maintainers" 5 | given-names: "Parseable" 6 | title: "parseable" 7 | date-released: 2023-03-30 8 | url: "https://github.com/parseablehq/parseable" 9 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | charts.parseable.com 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | 3 | This document outlines how you can contribute to Parseable. 4 | 5 | Thank you for considering to contribute to Parseable. The goal of this document is to provide everything you need to start your contribution. We encourage all contributions, including but not limited to: 6 | - Code patches, bug fixes. 7 | - Tutorials or blog posts. 8 | - Improving the documentation. 9 | - Submitting [bug reports](https://github.com/parseablehq/parseable/issues/new). 10 | 11 | ### Prerequisites 12 | 13 | - Your PR has an associated issue. Find an [existing issue](https://github.com/parseablehq/parseable/issues) or open a new issue. 14 | - You've discussed the with [Parseable community](https://logg.ing/community). 15 | - You're familiar with GitHub Pull Requests(PR) workflow. 16 | - You've read the [Parseable documentation](https://www.parseable.com/docs). 17 | 18 | ### Contribution workflow 19 | 20 | - Fork the [Parseable repository](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) in your own GitHub account. 21 | - [Create a new branch](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-and-deleting-branches-within-your-repository). 22 | - Review the Development Workflow section that describes the steps to maintain the repository. 23 | - Make your changes on your branch. 24 | - Submit the branch as a Pull Request pointing to the main branch of the Parseable repository. A maintainer should comment and/or review your Pull Request within a few hours. 25 | - You’ll be asked to review & sign the [Parseable Contributor License Agreement (CLA)](https://github.com/parseablehq/.github/blob/main/CLA.md) on the GitHub PR. Please ensure that you review the document. Once you’re ready, please sign the CLA by adding a comment I have read the CLA Document and I hereby sign the CLA in your Pull Request. 26 | - Please ensure that the commits in your Pull Request are made by the same GitHub user (which was used to create the PR and add the comment). 27 | 28 | ### Development workflow 29 | 30 | We recommend Linux or macOS as the development platform for Parseable. 31 | 32 | #### Setup and run Parseable 33 | 34 | Parseable needs Rust 1.77.1 or above. Use the below command to build and run Parseable binary with local mode. 35 | 36 | ```sh 37 | cargo run --release local-store 38 | ``` 39 | 40 | We recommend using the --release flag to test the full performance of Parseable. 41 | 42 | #### Running Tests 43 | 44 | ```sh 45 | cargo test 46 | ``` 47 | 48 | This command will be triggered to each PR as a requirement for merging it. 49 | 50 | If you get a "Too many open files" error you might want to increase the open file limit using this command: 51 | 52 | ```sh 53 | ulimit -Sn 3000 54 | ``` 55 | 56 | If you get a OpenSSL related error while building Parseable, you might need to install the dependencies using this command: 57 | 58 | ```sh 59 | sudo apt install build-essential 60 | sudo apt-get install libssl-dev 61 | sudo apt-get install pkg-config 62 | ``` 63 | 64 | ### Git Guidelines 65 | 66 | - The PR title should be accurate and descriptive of the changes. 67 | - The draft PRs are recommended when you want to show that you are working on something and make your work visible. Convert your PR as a draft if your changes are a work in progress. Maintainers will review the PR once you mark your PR as ready for review. 68 | - The branch related to the PR must be up-to-date with main before merging. We use Bors to automatically enforce this requirement without the PR author having to rebase manually. 69 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "parseable" 3 | version = "2.3.2" 4 | authors = ["Parseable Team "] 5 | edition = "2021" 6 | rust-version = "1.83.0" 7 | categories = ["logs", "observability", "metrics", "traces"] 8 | build = "build.rs" 9 | 10 | [dependencies] 11 | # Arrow and DataFusion ecosystem 12 | arrow = "54.0.0" 13 | arrow-array = "54.0.0" 14 | arrow-flight = { version = "54.0.0", features = ["tls"] } 15 | arrow-ipc = { version = "54.0.0", features = ["zstd"] } 16 | arrow-json = "54.0.0" 17 | arrow-schema = { version = "54.0.0", features = ["serde"] } 18 | arrow-select = "54.0.0" 19 | datafusion = "45.0.0" 20 | object_store = { version = "0.11.2", features = ["cloud", "aws", "azure"] } 21 | parquet = "54.0.0" 22 | 23 | # Web server and HTTP-related 24 | actix-cors = "0.7.0" 25 | actix-web = { version = "4.9.0", features = ["rustls-0_22"] } 26 | actix-web-httpauth = "0.8" 27 | actix-web-prometheus = { version = "0.1" } 28 | actix-web-static-files = "4.0" 29 | http = "0.2.7" 30 | http-auth-basic = "0.3.3" 31 | tonic = { version = "0.12.3", features = ["tls", "transport", "gzip", "zstd"] } 32 | tonic-web = "0.12.3" 33 | tower-http = { version = "0.6.1", features = ["cors"] } 34 | url = "2.4.0" 35 | 36 | # Connectors dependencies 37 | rdkafka = { version = "0.37", optional = true, features = ["cmake-build", "tracing", "libz-static"] } 38 | sasl2-sys = { version = "0.1.22", optional = true, features = ["vendored"] } 39 | 40 | # Authentication and Security 41 | argon2 = "0.5.0" 42 | base64 = "0.22.0" 43 | cookie = "0.18.1" 44 | hex = "0.4" 45 | openid = { version = "0.15.0", default-features = false, features = ["rustls"] } 46 | rustls = "0.22.4" 47 | rustls-pemfile = "2.1.2" 48 | sha2 = "0.10.8" 49 | 50 | # Serialization and Data Formats 51 | byteorder = "1.4.3" 52 | serde = { version = "1.0", features = ["rc", "derive"] } 53 | serde_json = "1.0" 54 | serde_repr = "0.1.17" 55 | 56 | # Async and Runtime 57 | async-trait = "0.1" 58 | futures = "0.3" 59 | futures-util = "0.3" 60 | tokio = { version = "^1.43", default-features = false, features = [ 61 | "sync", 62 | "macros", 63 | "fs", 64 | "rt-multi-thread", 65 | ] } 66 | tokio-stream = { version = "0.1", features = ["fs"] } 67 | tokio-util = { version = "0.7" } 68 | 69 | # Logging and Metrics 70 | opentelemetry-proto = { git = "https://github.com/parseablehq/opentelemetry-rust", branch = "fix-metrics-u64-serialization" } 71 | prometheus = { version = "0.13", features = ["process"] } 72 | prometheus-parse = "0.2.5" 73 | tracing = "0.1" 74 | tracing-subscriber = { version = "0.3", features = ["env-filter", "time"] } 75 | 76 | # Time and Date 77 | chrono = "0.4" 78 | chrono-humanize = "0.2" 79 | humantime = "2.1.0" 80 | humantime-serde = "1.1" 81 | 82 | # File System and I/O 83 | fs_extra = "1.3" 84 | path-clean = "1.0.1" 85 | relative-path = { version = "1.7", features = ["serde"] } 86 | 87 | # CLI and System 88 | clap = { version = "4.5", default-features = false, features = [ 89 | "std", 90 | "color", 91 | "help", 92 | "derive", 93 | "env", 94 | "cargo", 95 | "error-context", 96 | ] } 97 | crossterm = "0.28.1" 98 | hostname = "0.4.0" 99 | human-size = "0.4" 100 | num_cpus = "1.15" 101 | sysinfo = "0.33.1" 102 | uptime_lib = "0.3.0" 103 | 104 | # Utility Libraries 105 | anyhow = { version = "1.0", features = ["backtrace"] } 106 | bytes = "1.4" 107 | clokwerk = "0.4" 108 | derive_more = { version = "1", features = ["full"] } 109 | itertools = "0.14" 110 | once_cell = "1.20" 111 | rand = "0.8.5" 112 | regex = "1.7.3" 113 | reqwest = { version = "0.11.27", default-features = false, features = [ 114 | "rustls-tls", 115 | "json", 116 | "gzip", 117 | "brotli", 118 | ] } # cannot update cause rustls is not latest `see rustls` 119 | semver = "1.0" 120 | static-files = "0.2" 121 | thiserror = "2.0" 122 | ulid = { version = "1.0", features = ["serde"] } 123 | xxhash-rust = { version = "0.8", features = ["xxh3"] } 124 | futures-core = "0.3.31" 125 | 126 | [build-dependencies] 127 | cargo_toml = "0.21" 128 | sha1_smol = { version = "1.0", features = ["std"] } 129 | static-files = "0.2" 130 | ureq = "2.12" 131 | url = "2.5" 132 | vergen-gitcl = { version = "1.0", features = ["build", "cargo", "rustc", "si"] } 133 | zip = { version = "2.3", default-features = false, features = ["deflate"] } 134 | anyhow = "1.0" 135 | 136 | [dev-dependencies] 137 | rstest = "0.23.0" 138 | arrow = "54.0.0" 139 | temp-dir = "0.1.14" 140 | 141 | [package.metadata.parseable_ui] 142 | assets-url = "https://parseable-prism-build.s3.us-east-2.amazonaws.com/v2.3.2/build.zip" 143 | assets-sha1 = "35cfa3ab692ab0debf6666e5e2e1876fa7de4a02" 144 | 145 | [features] 146 | debug = [] 147 | kafka = ["rdkafka", "rdkafka/ssl-vendored", "rdkafka/ssl", "rdkafka/sasl", "sasl2-sys", "sasl2-sys/vendored"] 148 | 149 | [profile.release-lto] 150 | inherits = "release" 151 | lto = "fat" 152 | codegen-units = 1 153 | -------------------------------------------------------------------------------- /Cross.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | image = "ghcr.io/cross-rs/aarch64-unknown-linux-gnu@sha256:1e2a0291f92a4372cbc22d8994e735473045383f1ce7fa44a16c234ba00187f4" 3 | 4 | [target.x86_64-unknown-linux-gnu] 5 | image = "ghcr.io/cross-rs/x86_64-unknown-linux-gnu@sha256:bf05360bb9d6d4947eed60532ac7a0d7e8fae8f214e9abb801d5941c8fe4918d" 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Parseable Server (C) 2022 - 2024 Parseable, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU Affero General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Affero General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Affero General Public License 14 | # along with this program. If not, see . 15 | 16 | # build stage 17 | FROM rust:1.84.0-bookworm AS builder 18 | 19 | LABEL org.opencontainers.image.title="Parseable" 20 | LABEL maintainer="Parseable Team " 21 | LABEL org.opencontainers.image.vendor="Parseable Inc" 22 | LABEL org.opencontainers.image.licenses="AGPL-3.0" 23 | 24 | WORKDIR /parseable 25 | COPY . . 26 | RUN cargo build --release 27 | 28 | # final stage 29 | FROM gcr.io/distroless/cc-debian12:latest 30 | 31 | WORKDIR /parseable 32 | 33 | # Copy the static shell into base image. 34 | COPY --from=builder /parseable/target/release/parseable /usr/bin/parseable 35 | 36 | CMD ["/usr/bin/parseable"] 37 | -------------------------------------------------------------------------------- /Dockerfile.debug: -------------------------------------------------------------------------------- 1 | # Parseable Server (C) 2022 - 2024 Parseable, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU Affero General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Affero General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Affero General Public License 14 | # along with this program. If not, see . 15 | 16 | # build stage 17 | FROM rust:1.84.0-bookworm AS builder 18 | 19 | LABEL org.opencontainers.image.title="Parseable" 20 | LABEL maintainer="Parseable Team " 21 | LABEL org.opencontainers.image.vendor="Parseable Inc" 22 | LABEL org.opencontainers.image.licenses="AGPL-3.0" 23 | 24 | WORKDIR /parseable 25 | 26 | # Cache dependencies 27 | COPY Cargo.toml Cargo.lock build.rs ./ 28 | RUN mkdir src && echo "fn main() {}" > src/main.rs && cargo build && rm -rf src 29 | 30 | # Build the actual binary 31 | COPY src ./src 32 | COPY resources ./resources 33 | RUN cargo build 34 | 35 | # final stage 36 | FROM docker.io/debian:bookworm-slim 37 | 38 | RUN apt update && apt install -y curl 39 | 40 | WORKDIR /parseable 41 | 42 | # Copy the static binary into the final image 43 | COPY --from=builder /parseable/target/debug/parseable /usr/bin/parseable 44 | 45 | CMD ["/usr/bin/parseable"] 46 | -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | # Parseable Server (C) 2022 - 2024 Parseable, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU Affero General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Affero General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Affero General Public License 14 | # along with this program. If not, see . 15 | 16 | # build stage 17 | FROM rust:1.84.0-bookworm AS builder 18 | 19 | LABEL org.opencontainers.image.title="Parseable" 20 | LABEL maintainer="Parseable Team " 21 | LABEL org.opencontainers.image.vendor="Parseable Inc" 22 | LABEL org.opencontainers.image.licenses="AGPL-3.0" 23 | 24 | WORKDIR /parseable 25 | COPY . . 26 | 27 | ## Ensure that you build the console assests https://github.com/parseablehq/console?tab=readme-ov-file#trophy-development-and-contributing 28 | ## Then create a dist folder in the root of the this directory and copy the contents of the console/dist folder into the dist folder 29 | ENV LOCAL_ASSETS_PATH=/parseable/dist 30 | RUN cargo build --release 31 | 32 | # final stage 33 | FROM gcr.io/distroless/cc-debian12:latest 34 | 35 | WORKDIR /parseable 36 | 37 | # Copy the Parseable binary from builder 38 | COPY --from=builder /parseable/target/release/parseable /usr/bin/parseable 39 | 40 | CMD ["/usr/bin/parseable"] 41 | -------------------------------------------------------------------------------- /Dockerfile.kafka: -------------------------------------------------------------------------------- 1 | # Parseable Server (C) 2022 - 2024 Parseable, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU Affero General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Affero General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Affero General Public License 14 | # along with this program. If not, see . 15 | 16 | # build stage 17 | FROM rust:1.84.0-bookworm AS builder 18 | 19 | LABEL org.opencontainers.image.title="Parseable" 20 | LABEL maintainer="Parseable Team " 21 | LABEL org.opencontainers.image.vendor="Parseable Inc" 22 | LABEL org.opencontainers.image.licenses="AGPL-3.0" 23 | 24 | RUN apt-get update && \ 25 | apt-get install --no-install-recommends -y \ 26 | cmake \ 27 | clang \ 28 | librdkafka-dev \ 29 | ca-certificates \ 30 | build-essential \ 31 | libsasl2-dev \ 32 | libssl-dev && \ 33 | rm -rf /var/lib/apt/lists/* 34 | 35 | WORKDIR /parseable 36 | COPY Cargo.toml Cargo.lock build.rs ./ 37 | 38 | # Fix librdkafka CMakeLists.txt before building 39 | RUN mkdir -p src && echo "fn main() {}" > src/main.rs && \ 40 | # Download the package so it's in the cargo registry 41 | cargo fetch && \ 42 | # Find rdkafka-sys directory 43 | RDKAFKA_SYS_DIR=$(find /usr/local/cargo/registry/src -name "rdkafka-sys-*" -type d | head -n 1) && \ 44 | echo "Found rdkafka-sys at: $RDKAFKA_SYS_DIR" && \ 45 | # Find the CMakeLists.txt file 46 | CMAKE_FILE="$RDKAFKA_SYS_DIR/librdkafka/CMakeLists.txt" && \ 47 | if [ -f "$CMAKE_FILE" ]; then \ 48 | echo "Found CMakeLists.txt at: $CMAKE_FILE" && \ 49 | # Replace the minimum required version 50 | sed -i 's/cmake_minimum_required(VERSION 3.2)/cmake_minimum_required(VERSION 3.5)/' "$CMAKE_FILE" && \ 51 | echo "Modified CMakeLists.txt to use CMake 3.5 minimum version"; \ 52 | else \ 53 | echo "Could not find librdkafka CMakeLists.txt file!" && \ 54 | exit 1; \ 55 | fi 56 | 57 | # Now build dependencies with the fixed CMakeLists.txt 58 | RUN cargo build --release --features kafka && \ 59 | rm -rf src 60 | 61 | # Copy the actual source code 62 | COPY src ./src 63 | COPY resources ./resources 64 | 65 | # Build the actual binary with kafka feature 66 | RUN cargo build --release --features kafka 67 | 68 | # final stage 69 | FROM gcr.io/distroless/cc-debian12:latest 70 | 71 | # Copy only the libraries that binary needs since kafka is statically linked 72 | ARG LIB_DIR 73 | COPY --from=builder /usr/lib/${LIB_DIR}/libsasl2.so.2 /usr/lib/${LIB_DIR}/ 74 | COPY --from=builder /usr/lib/${LIB_DIR}/libssl.so.3 /usr/lib/${LIB_DIR}/ 75 | COPY --from=builder /usr/lib/${LIB_DIR}/libcrypto.so.3 /usr/lib/${LIB_DIR}/ 76 | 77 | WORKDIR /parseable 78 | 79 | # Copy the Parseable binary from builder 80 | COPY --from=builder /parseable/target/release/parseable /usr/bin/parseable 81 | 82 | # Copy CA certificates 83 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 84 | 85 | 86 | CMD ["/usr/bin/parseable"] 87 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Run cargo fmt 2 | 3 | fmt: 4 | cd server/src/ && cargo fmt 5 | 6 | # Run server 7 | run: 8 | cd server && cargo run 9 | 10 | # Helm template 11 | template: 12 | helm template parseable \ 13 | helm/parseable \ 14 | -f helm/parseable/values.yaml 15 | 16 | # Helm Upgrade 17 | upgrade: 18 | helm upgrade --install \ 19 | parseable --namespace parseable \ 20 | --create-namespace \ 21 | helm/parseable \ 22 | -f helm/parseable/values.yaml 23 | -------------------------------------------------------------------------------- /USERS.md: -------------------------------------------------------------------------------- 1 | # Who is using Parseable? 2 | 3 | The following document is a list of users and adopters who use Parseable. The users themselves directly maintain the list. You can add your organization by editing this file directly. 4 | 5 | If you're using Parseable in your organization, please add your company name to this list. It really helps the project gain momentum and credibility. It's a small contribution to the project with a big impact. 6 | 7 | --- 8 | 9 | | Organization | Contact | Description of Use | 10 | | ------------ | ------- | ------------------ | 11 | | [HireXL](https://www.hirexl.in/) | [@A4abs](https://github.com/A4abs) | Frontend application logging | 12 | | [Elfsquad](https://elfsquad.io) | [Stan van Rooy](https://github.com/stanvanrooy) | Centralized application/infrastructure logging | 13 | -------------------------------------------------------------------------------- /about.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 36 | 37 | 38 | 39 |
40 |
41 |

Third Party Licenses used in Parseable

42 |

This page lists the licenses of the projects used in Parseable.

43 |
44 | 45 |

Overview of licenses:

46 |
    47 | {{#each overview}} 48 |
  • {{name}} ({{count}})
  • 49 | {{/each}} 50 |
51 | 52 |

All license text:

53 |
    54 | {{#each licenses}} 55 |
  • 56 |

    {{name}}

    57 |

    Used by:

    58 | 63 |
    {{text}}
    64 |
  • 65 | {{/each}} 66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /about.toml: -------------------------------------------------------------------------------- 1 | accepted = [ 2 | "Apache-2.0", 3 | "MIT", 4 | "BSD-3-Clause", 5 | "ISC", 6 | "MPL-2.0", 7 | "NOASSERTION", 8 | "AGPL-3.0", 9 | "GPL-3.0", 10 | "BSD-2-Clause", 11 | "BSL-1.0", 12 | "OpenSSL", 13 | "Unicode-DFS-2016", 14 | ] 15 | 16 | workarounds = [ 17 | "ring", 18 | "rustls", 19 | ] 20 | -------------------------------------------------------------------------------- /docker-compose-distributed-test.yaml: -------------------------------------------------------------------------------- 1 | networks: 2 | parseable-internal: 3 | 4 | services: 5 | # minio 6 | minio: 7 | image: minio/minio:RELEASE.2025-02-03T21-03-04Z 8 | entrypoint: 9 | - sh 10 | - -euc 11 | - | 12 | mkdir -p /tmp/minio/parseable && \ 13 | minio server /tmp/minio 14 | environment: 15 | - MINIO_ROOT_USER=parseable 16 | - MINIO_ROOT_PASSWORD=supersecret 17 | - MINIO_UPDATE=off 18 | ports: 19 | - "9000:9000" 20 | healthcheck: 21 | test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] 22 | interval: 15s 23 | timeout: 20s 24 | retries: 5 25 | networks: 26 | - parseable-internal 27 | # query server 28 | parseable-query: 29 | build: 30 | context: . 31 | dockerfile: Dockerfile.debug 32 | platform: linux/amd64 33 | command: [ "parseable", "s3-store" ] 34 | ports: 35 | - "8000:8000" 36 | environment: 37 | - P_S3_URL=http://minio:9000 38 | - P_S3_ACCESS_KEY=parseable 39 | - P_S3_SECRET_KEY=supersecret 40 | - P_S3_REGION=us-east-1 41 | - P_S3_BUCKET=parseable 42 | - P_STAGING_DIR=/tmp/data 43 | - P_USERNAME=parseableadmin 44 | - P_PASSWORD=parseableadmin 45 | - P_CHECK_UPDATE=false 46 | - P_PARQUET_COMPRESSION_ALGO=snappy 47 | - P_MODE=query 48 | - RUST_LOG=warn 49 | networks: 50 | - parseable-internal 51 | healthcheck: 52 | test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/liveness" ] 53 | interval: 15s 54 | timeout: 20s 55 | retries: 5 56 | depends_on: 57 | minio: 58 | condition: service_healthy 59 | deploy: 60 | restart_policy: 61 | condition: on-failure 62 | delay: 20s 63 | max_attempts: 3 64 | # ingest server one 65 | parseable-ingest-one: 66 | build: 67 | context: . 68 | dockerfile: Dockerfile.debug 69 | platform: linux/amd64 70 | command: [ "parseable", "s3-store", ] 71 | ports: 72 | - "8000" 73 | environment: 74 | - P_S3_URL=http://minio:9000 75 | - P_S3_ACCESS_KEY=parseable 76 | - P_S3_SECRET_KEY=supersecret 77 | - P_S3_REGION=us-east-1 78 | - P_S3_BUCKET=parseable 79 | - P_STAGING_DIR=/tmp/data 80 | - P_USERNAME=parseableadmin 81 | - P_PASSWORD=parseableadmin 82 | - P_CHECK_UPDATE=false 83 | - P_PARQUET_COMPRESSION_ALGO=snappy 84 | - P_MODE=ingest 85 | - P_INGESTOR_ENDPOINT=parseable-ingest-one:8000 86 | - RUST_LOG=warn 87 | networks: 88 | - parseable-internal 89 | healthcheck: 90 | test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/liveness" ] 91 | interval: 15s 92 | timeout: 20s 93 | retries: 5 94 | depends_on: 95 | parseable-query: 96 | condition: service_healthy 97 | minio: 98 | condition: service_healthy 99 | deploy: 100 | restart_policy: 101 | condition: on-failure 102 | delay: 20s 103 | max_attempts: 3 104 | 105 | quest: 106 | platform: linux/amd64 107 | image: ghcr.io/parseablehq/quest:main 108 | pull_policy: always 109 | command: 110 | [ 111 | "load", 112 | "http://parseable-query:8000", 113 | "parseableadmin", 114 | "parseableadmin", 115 | "20", 116 | "10", 117 | "5m", 118 | "minio:9000", 119 | "parseable", 120 | "supersecret", 121 | "parseable", 122 | "http://parseable-ingest-one:8000", 123 | "parseableadmin", 124 | "parseableadmin", 125 | ] 126 | networks: 127 | - parseable-internal 128 | depends_on: 129 | parseable-query: 130 | condition: service_healthy 131 | parseable-ingest-one: 132 | condition: service_healthy 133 | minio: 134 | condition: service_healthy 135 | deploy: 136 | restart_policy: 137 | condition: on-failure 138 | delay: 20s 139 | max_attempts: 3 140 | -------------------------------------------------------------------------------- /docker-compose-local.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | kafka: 3 | image: docker.io/bitnami/kafka:3.9 4 | ports: 5 | - "9092:9092" 6 | - "29092:29092" 7 | volumes: 8 | - "kafka_data:/bitnami" 9 | environment: 10 | # KRaft settings 11 | - KAFKA_CFG_NODE_ID=0 12 | - KAFKA_CFG_PROCESS_ROLES=controller,broker 13 | - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka:9093 14 | # Listeners for internal and external communication 15 | - KAFKA_CFG_LISTENERS=PLAINTEXT://0.0.0.0:9092,PLAINTEXT_INTERNAL://0.0.0.0:29092,CONTROLLER://:9093 16 | - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092,PLAINTEXT_INTERNAL://kafka:29092 17 | - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_INTERNAL:PLAINTEXT,CONTROLLER:PLAINTEXT 18 | - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER 19 | - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT_INTERNAL 20 | 21 | kafka-ui: 22 | platform: linux/amd64 23 | image: provectuslabs/kafka-ui:latest 24 | ports: 25 | - "8080:8080" 26 | depends_on: 27 | - kafka 28 | environment: 29 | KAFKA_CLUSTERS_0_NAME: local 30 | KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:29092 31 | KAFKA_CLUSTERS_0_METRICS_PORT: 9101 32 | DYNAMIC_CONFIG_ENABLED: "true" 33 | deploy: 34 | restart_policy: 35 | condition: on-failure 36 | delay: 20s 37 | max_attempts: 3 38 | 39 | volumes: 40 | kafka_data: 41 | driver: local 42 | -------------------------------------------------------------------------------- /docker-compose-test.yaml: -------------------------------------------------------------------------------- 1 | networks: 2 | parseable-internal: 3 | 4 | services: 5 | minio: 6 | image: minio/minio:RELEASE.2025-02-03T21-03-04Z 7 | entrypoint: 8 | - sh 9 | - -euc 10 | - | 11 | mkdir -p /tmp/minio/parseable && \ 12 | minio server /tmp/minio 13 | environment: 14 | - MINIO_ROOT_USER=parseable 15 | - MINIO_ROOT_PASSWORD=supersecret 16 | - MINIO_UPDATE=off 17 | ports: 18 | - "9000:9000" 19 | healthcheck: 20 | test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ] 21 | interval: 15s 22 | timeout: 20s 23 | retries: 5 24 | networks: 25 | - parseable-internal 26 | 27 | parseable: 28 | build: 29 | context: . 30 | dockerfile: Dockerfile.debug 31 | platform: linux/amd64 32 | command: [ "parseable", "s3-store", ] 33 | ports: 34 | - "8000:8000" 35 | environment: 36 | - P_S3_URL=http://minio:9000 37 | - P_S3_ACCESS_KEY=parseable 38 | - P_S3_SECRET_KEY=supersecret 39 | - P_S3_REGION=us-east-1 40 | - P_S3_BUCKET=parseable 41 | - P_STAGING_DIR=/tmp/data 42 | - P_USERNAME=parseableadmin 43 | - P_PASSWORD=parseableadmin 44 | - P_CHECK_UPDATE=false 45 | - P_PARQUET_COMPRESSION_ALGO=snappy 46 | - RUST_LOG=warn 47 | depends_on: 48 | minio: 49 | condition: service_healthy 50 | healthcheck: 51 | test: [ "CMD", "curl", "-f", "http://localhost:8000/api/v1/liveness" ] 52 | interval: 15s 53 | timeout: 20s 54 | retries: 5 55 | networks: 56 | - parseable-internal 57 | deploy: 58 | restart_policy: 59 | condition: on-failure 60 | delay: 20s 61 | max_attempts: 3 62 | 63 | quest: 64 | image: ghcr.io/parseablehq/quest:main 65 | platform: linux/amd64 66 | pull_policy: always 67 | command: [ 68 | "load", 69 | "http://parseable:8000", 70 | "parseableadmin", 71 | "parseableadmin", 72 | "20", 73 | "10", 74 | "5m", 75 | "minio:9000", 76 | "parseable", 77 | "supersecret", 78 | "parseable" 79 | ] 80 | depends_on: 81 | parseable: 82 | condition: service_healthy 83 | networks: 84 | - parseable-internal 85 | deploy: 86 | restart_policy: 87 | condition: on-failure 88 | delay: 20s 89 | max_attempts: 3 90 | -------------------------------------------------------------------------------- /helm-reindex.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | helm package helm -d helm-releases/ 4 | 5 | helm repo index --merge index.yaml --url https://charts.parseable.com . 6 | -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.5.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.5.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.6.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.6.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.7.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.7.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.0.8.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.0.8.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.1.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.1.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.1.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.2.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.2.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.2.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.2.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.2.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.3.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.3.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.3.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.3.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.3.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.4.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.4.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.4.5.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.4.5.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.5.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.5.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.5.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.5.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.6.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.6.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.6.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.6.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.6.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.6.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.7.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.7.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.7.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.7.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.7.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.7.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.7.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.7.3.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.8.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.8.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.8.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.8.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-0.9.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-0.9.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.0.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.1.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.2.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.2.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.3.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.3.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.3.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.3.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.4.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.4.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.4.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.4.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.3.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.4.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.4.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.5.5.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.5.5.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.3.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.4.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.4.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.5.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.5.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.6.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.6.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.7.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.7.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.6.8.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.6.8.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.7.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.7.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.7.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.7.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.7.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.7.2.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.7.3.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.7.3.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-1.7.5.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-1.7.5.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-2.0.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-2.0.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-2.1.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-2.1.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-2.3.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-2.3.0.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-2.3.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-2.3.1.tgz -------------------------------------------------------------------------------- /helm-releases/parseable-2.3.2.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm-releases/parseable-2.3.2.tgz -------------------------------------------------------------------------------- /helm/Chart.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | - name: vector 3 | repository: https://helm.vector.dev 4 | version: 0.20.1 5 | - name: fluent-bit 6 | repository: https://fluent.github.io/helm-charts 7 | version: 0.25.0 8 | digest: sha256:5657989e00b94e31ddcb44d0c925f8d30849c46851879a27708b50cd1af6ee33 9 | generated: "2023-04-24T18:46:09.186176+05:30" 10 | -------------------------------------------------------------------------------- /helm/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: parseable 3 | description: Helm chart for Parseable - Fast Observability on S3 4 | type: application 5 | version: 2.3.2 6 | appVersion: "v2.3.2" 7 | icon: "https://raw.githubusercontent.com/parseablehq/.github/main/images/new-logo.svg" 8 | 9 | maintainers: 10 | - name: Parseable Team 11 | email: hi@parseable.com 12 | url: https://parseable.com 13 | 14 | dependencies: 15 | - name: vector 16 | version: 0.20.1 17 | repository: https://helm.vector.dev 18 | condition: vector.enabled 19 | - name: fluent-bit 20 | version: 0.48.0 21 | repository: https://fluent.github.io/helm-charts 22 | condition: fluent-bit.enabled 23 | -------------------------------------------------------------------------------- /helm/README.md: -------------------------------------------------------------------------------- 1 | # Parseable Helm Chart 2 | 3 | Refer the Parseable Helm Chart [documentation ↗︎](https://www.parseable.io/docs/installation/kubernetes-helm) 4 | -------------------------------------------------------------------------------- /helm/charts/fluent-bit-0.48.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm/charts/fluent-bit-0.48.1.tgz -------------------------------------------------------------------------------- /helm/charts/vector-0.20.1.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parseablehq/parseable/b93ae42510e2632f870485a1116404753ad9d54b/helm/charts/vector-0.20.1.tgz -------------------------------------------------------------------------------- /helm/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* 2 | Expand the name of the chart. 3 | */}} 4 | {{- define "parseable.name" -}} 5 | {{- default .Chart.Name .Values.parseable.nameOverride | trunc 63 | trimSuffix "-" }} 6 | {{- end }} 7 | 8 | {{/* 9 | Create a default fully qualified app name. 10 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 11 | If release name contains chart name it will be used as a full name. 12 | */}} 13 | {{- define "parseable.fullname" -}} 14 | {{- if .Values.parseable.fullnameOverride }} 15 | {{- .Values.parseable.fullnameOverride | trunc 63 | trimSuffix "-" }} 16 | {{- else }} 17 | {{- $name := default .Chart.Name .Values.parseable.nameOverride }} 18 | {{- if contains $name .Release.Name }} 19 | {{- .Release.Name | trunc 63 | trimSuffix "-" }} 20 | {{- else }} 21 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} 22 | {{- end }} 23 | {{- end }} 24 | {{- end }} 25 | 26 | {{/* 27 | Create chart name and version as used by the chart label. 28 | */}} 29 | {{- define "parseable.chart" -}} 30 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} 31 | {{- end }} 32 | 33 | {{/* 34 | Common labels 35 | */}} 36 | {{- define "parseable.labels" -}} 37 | helm.sh/chart: {{ include "parseable.chart" . }} 38 | {{ include "parseable.labelsSelector" . }} 39 | {{- if .Chart.AppVersion }} 40 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 41 | {{- end }} 42 | app.kubernetes.io/managed-by: {{ .Release.Service }} 43 | {{- end }} 44 | 45 | {{/* 46 | Ingestor Labels 47 | */}} 48 | {{- define "parseable.ingestorLabels" -}} 49 | helm.sh/chart: {{ include "parseable.chart" . }} 50 | {{ include "parseable.ingestorLabelsSelector" . }} 51 | {{- if .Chart.AppVersion }} 52 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 53 | {{- end }} 54 | app.kubernetes.io/managed-by: {{ .Release.Service }} 55 | {{- end }} 56 | 57 | {{/* 58 | Querier Labels 59 | */}} 60 | {{- define "parseable.querierLabels" -}} 61 | helm.sh/chart: {{ include "parseable.chart" . }} 62 | {{ include "parseable.querierLabelsSelector" . }} 63 | {{- if .Chart.AppVersion }} 64 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 65 | {{- end }} 66 | app.kubernetes.io/managed-by: {{ .Release.Service }} 67 | {{- end }} 68 | 69 | {{/* 70 | Selector labels 71 | */}} 72 | {{- define "parseable.labelsSelector" -}} 73 | app.kubernetes.io/name: {{ include "parseable.name" . }} 74 | app.kubernetes.io/instance: {{ .Release.Name }} 75 | {{- end }} 76 | 77 | {{/* 78 | Ingestor Labels Selector for ingestor statefulset 79 | */}} 80 | {{- define "parseable.ingestorLabelsSelector" -}} 81 | app.kubernetes.io/name: {{ include "parseable.name" . }} 82 | app.kubernetes.io/instance: {{ .Release.Name }} 83 | app.parseable.com/type: ingestor 84 | {{- end }} 85 | 86 | {{/* 87 | Querier Labels Selector for querier deployment 88 | */}} 89 | {{- define "parseable.querierLabelsSelector" -}} 90 | app.kubernetes.io/name: {{ include "parseable.name" . }} 91 | app.kubernetes.io/instance: {{ .Release.Name }} 92 | app.parseable.com/type: querier 93 | {{- end }} 94 | 95 | {{/* 96 | Create the name of the service account to use 97 | */}} 98 | {{- define "parseable.serviceAccountName" -}} 99 | {{- if .Values.parseable.serviceAccount.create }} 100 | {{- default (include "parseable.fullname" .) .Values.parseable.serviceAccount.name }} 101 | {{- else }} 102 | {{- default "default" .Values.parseable.serviceAccount.name }} 103 | {{- end }} 104 | {{- end }} 105 | -------------------------------------------------------------------------------- /helm/templates/_helpers_config_logstream.txt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e ; # Have script exit in the event of a failed command. 3 | IFS=$' \t\r\n' 4 | 5 | createStream() { 6 | STREAM=$1 7 | echo; 8 | echo \"Creating the log stream $STREAM\"; 9 | 10 | {{ if .Values.parseable.highAvailability.enabled }} 11 | response=$(curl -sS --header 'Content-Type: application/json' -u "$P_USERNAME":"$P_PASSWORD" -w 'HTTPSTATUS:%{http_code}' --location --request PUT "http://{{ include "parseable.fullname" . }}-querier-service.{{ .Release.Namespace }}:{{ $.Values.parseable.service.port }}/api/v1/logstream/$STREAM"); 12 | {{ else }} 13 | response=$(curl -sS --header 'Content-Type: application/json' -u "$P_USERNAME":"$P_PASSWORD" -w 'HTTPSTATUS:%{http_code}' --location --request PUT "http://{{ include "parseable.fullname" . }}.{{ .Release.Namespace }}:{{ $.Values.parseable.service.port }}/api/v1/logstream/$STREAM"); 14 | {{ end }} 15 | 16 | HTTP_BODY=$(echo $response | sed -e 's/HTTPSTATUS\:.*//g') 17 | HTTP_STATUS=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') 18 | 19 | echo \"API response:\" 20 | echo \"HTTP status code: $HTTP_STATUS\" 21 | echo \"HTTP response message: $HTTP_BODY\" 22 | } 23 | 24 | setRetention() { 25 | STREAM=$1 26 | ACTION=$2 27 | DURATION=$3 28 | 29 | echo; 30 | echo \"Setting the retention for $STREAM\"; 31 | 32 | {{ if .Values.parseable.highAvailability.enabled }} 33 | response=$(curl -sS --header 'Content-Type: application/json' -u "$P_USERNAME":"$P_PASSWORD" -w 'HTTPSTATUS:%{http_code}' --location --request PUT "http://{{ include "parseable.fullname" . }}-querier-service.{{ .Release.Namespace }}:{{ $.Values.parseable.service.port }}/api/v1/logstream/$STREAM/retention" --data "[{\"description\":\"$ACTION logs after $DURATION\",\"action\":\"$ACTION\",\"duration\":\"$DURATION\"}]"); 34 | {{ else }} 35 | response=$(curl -sS --header 'Content-Type: application/json' -u "$P_USERNAME":"$P_PASSWORD" -w 'HTTPSTATUS:%{http_code}' --location --request PUT "http://{{ include "parseable.fullname" . }}.{{ .Release.Namespace }}:{{ $.Values.parseable.service.port }}/api/v1/logstream/$STREAM/retention" --data "[{\"description\":\"$ACTION logs after $DURATION\",\"action\":\"$ACTION\",\"duration\":\"$DURATION\"}]"); 36 | {{ end }} 37 | 38 | HTTP_BODY=$(echo $response | sed -e 's/HTTPSTATUS\:.*//g') 39 | HTTP_STATUS=$(echo $response | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') 40 | 41 | echo \"API response:\" 42 | echo \"HTTP status code: $HTTP_STATUS\" 43 | echo \"HTTP response message: $HTTP_BODY\" 44 | } 45 | 46 | {{ if .Values.parseable.logstream }} 47 | # Create the logstream 48 | {{- range .Values.parseable.logstream }} 49 | createStream {{.name}} 50 | setRetention {{.name}} {{.retention.action}} {{.retention.duration}} 51 | {{- end }} 52 | {{- end }} 53 | -------------------------------------------------------------------------------- /helm/templates/data-pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.parseable.persistence.data.enabled }} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ include "parseable.fullname" . }}-data-pvc 6 | labels: 7 | {{- include "parseable.labels" . | nindent 4 }} 8 | spec: 9 | accessModes: 10 | - {{ .Values.parseable.persistence.data.accessMode | quote }} 11 | resources: 12 | requests: 13 | storage: {{ .Values.parseable.persistence.data.size | quote }} 14 | {{- if .Values.parseable.persistence.data.storageClass }} 15 | storageClassName: "{{ .Values.parseable.persistence.data.storageClass }}" 16 | {{- else }} 17 | storageClassName: "" 18 | {{- end }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /helm/templates/ingestor-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Values.parseable.highAvailability.enabled true }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "parseable.fullname" . }}-ingestor-service 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "parseable.labelsSelector" . | nindent 4 }} 9 | spec: 10 | type: {{ $.Values.parseable.highAvailability.ingestor.service.type }} 11 | ports: 12 | - port: {{ $.Values.parseable.highAvailability.ingestor.service.port }} 13 | targetPort: {{ .Values.parseable.highAvailability.ingestor.port }} 14 | protocol: TCP 15 | selector: 16 | {{- include "parseable.ingestorLabelsSelector" . | nindent 4 }} 17 | {{- end }} 18 | -------------------------------------------------------------------------------- /helm/templates/logstream-configmap.yaml: -------------------------------------------------------------------------------- 1 | {{- if not (empty .Values.parseable.logstream) }} 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ include "parseable.fullname" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "parseable.labels" . | nindent 4 }} 9 | data: 10 | config-logstream: |- 11 | {{- include (print $.Template.BasePath "/_helpers_config_logstream.txt") . | nindent 4 }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm/templates/logstream-job.yaml: -------------------------------------------------------------------------------- 1 | {{- if not (empty .Values.parseable.logstream) }} 2 | apiVersion: batch/v1 3 | kind: Job 4 | metadata: 5 | name: {{ include "parseable.fullname" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "parseable.labels" . | nindent 4 }} 9 | spec: 10 | template: 11 | metadata: 12 | {{- with .Values.parseable.podAnnotations }} 13 | annotations: 14 | "helm.sh/hook": post-install,post-upgrade 15 | {{- toYaml . | nindent 8 }} 16 | {{- end }} 17 | labels: 18 | {{- include "parseable.labelsSelector" . | nindent 8 }} 19 | spec: 20 | restartPolicy: OnFailure 21 | securityContext: 22 | {{- toYaml .Values.parseable.podSecurityContext | nindent 8 }} 23 | volumes: 24 | - name: parseable-init 25 | projected: 26 | sources: 27 | - configMap: 28 | name: {{ include "parseable.fullname" . }} 29 | containers: 30 | - name: config-logstream 31 | volumeMounts: 32 | - name: parseable-init 33 | mountPath: /config 34 | securityContext: 35 | {{- toYaml .Values.parseable.securityContext | nindent 12 }} 36 | env: 37 | {{- if .Values.parseable.local }} 38 | {{- range $secret := .Values.parseable.localModeSecret }} 39 | {{- range $key := $secret.keys }} 40 | {{- $envPrefix := $secret.prefix | default "" | upper }} 41 | {{- $envKey := $key | upper | replace "." "_" | replace "-" "_" }} 42 | - name: {{ $envPrefix }}{{ $envKey }} 43 | valueFrom: 44 | secretKeyRef: 45 | name: {{ $secret.name }} 46 | key: {{ $key }} 47 | {{- end }} 48 | {{- end }} 49 | {{- else}} 50 | {{- range $secret := .Values.parseable.s3ModeSecret }} 51 | {{- range $key := $secret.keys }} 52 | {{- $envPrefix := $secret.prefix | default "" | upper }} 53 | {{- $envKey := $key | upper | replace "." "_" | replace "-" "_" }} 54 | - name: {{ $envPrefix }}{{ $envKey }} 55 | valueFrom: 56 | secretKeyRef: 57 | name: {{ $secret.name }} 58 | key: {{ $key }} 59 | {{- end }} 60 | {{- end }} 61 | {{- end }} 62 | image: curlimages/curl:8.00.0 63 | command: [ "/bin/sh", "/config/config-logstream" ] 64 | backoffLimit: 20 65 | {{- end}} 66 | -------------------------------------------------------------------------------- /helm/templates/querier-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Values.parseable.highAvailability.enabled true }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "parseable.fullname" . }}-querier-service 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "parseable.labelsSelector" . | nindent 4 }} 9 | spec: 10 | type: {{ $.Values.parseable.service.type }} 11 | ports: 12 | - port: {{ $.Values.parseable.service.port }} 13 | targetPort: 8000 14 | protocol: TCP 15 | selector: 16 | {{- include "parseable.querierLabelsSelector" . | nindent 4 }} 17 | {{- end }} 18 | -------------------------------------------------------------------------------- /helm/templates/service-monitor.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.parseable.metrics.serviceMonitor.enabled }} 2 | apiVersion: monitoring.coreos.com/v1 3 | kind: ServiceMonitor 4 | metadata: 5 | name: {{ include "parseable.fullname" . }} 6 | namespace: {{ default .Release.Namespace .Values.parseable.metrics.serviceMonitor.namespace | quote }} 7 | labels: 8 | {{- with .Values.parseable.metrics.serviceMonitor.labels }} 9 | {{- toYaml . | nindent 4 }} 10 | {{- end }} 11 | {{- include "parseable.labels" . | nindent 4 }} 12 | spec: 13 | {{ if .Values.parseable.metrics.serviceMonitor.spec.jobLabel }} 14 | jobLabel: {{ .Values.parseable.metrics.serviceMonitor.spec.jobLabel | quote }} 15 | {{- end }} 16 | {{ if .Values.parseable.metrics.serviceMonitor.spec.targetLabels }} 17 | targetLabels: 18 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.targetLabels | nindent 4 }} 19 | {{- end }} 20 | {{ if .Values.parseable.metrics.serviceMonitor.spec.podTargetLabels }} 21 | podTargetLabels: 22 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.podTargetLabels | nindent 4 }} 23 | {{- end }} 24 | {{ if .Values.parseable.metrics.serviceMonitor.spec.endpoints }} 25 | endpoints: 26 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.endpoints | nindent 4 }} 27 | {{- end }} 28 | {{ if .Values.parseable.metrics.serviceMonitor.spec.selector }} 29 | selector: 30 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.selector | nindent 4 }} 31 | {{- end }} 32 | {{ if .Values.parseable.metrics.serviceMonitor.spec.namespaceSelector }} 33 | namespaceSelector: 34 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.namespaceSelector | nindent 4 }} 35 | {{- end }} 36 | {{ if .Values.parseable.metrics.serviceMonitor.spec.sampleLimit }} 37 | sampleLimit: {{ .Values.parseable.metrics.serviceMonitor.spec.sampleLimit }} 38 | {{- end }} 39 | {{ if .Values.parseable.metrics.serviceMonitor.spec.scrapeProtocols }} 40 | scrapeProtocols: 41 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.scrapeProtocols | nindent 4 }} 42 | {{- end }} 43 | {{ if .Values.parseable.metrics.serviceMonitor.spec.targetLimit }} 44 | targetLimit: {{ .Values.parseable.metrics.serviceMonitor.spec.targetLimit }} 45 | {{- end }} 46 | {{ if .Values.parseable.metrics.serviceMonitor.spec.labelLimit }} 47 | labelLimit: {{ .Values.parseable.metrics.serviceMonitor.spec.labelLimit }} 48 | {{- end }} 49 | {{ if .Values.parseable.metrics.serviceMonitor.spec.labelNameLengthLimit }} 50 | labelNameLengthLimit: {{ .Values.parseable.metrics.serviceMonitor.spec.labelNameLengthLimit }} 51 | {{- end }} 52 | {{ if .Values.parseable.metrics.serviceMonitor.spec.labelValueLengthLimit }} 53 | labelValueLengthLimit: {{ .Values.parseable.metrics.serviceMonitor.spec.labelValueLengthLimit }} 54 | {{- end }} 55 | {{ if .Values.parseable.metrics.serviceMonitor.spec.keepDroppedTargets }} 56 | keepDroppedTargets: {{ .Values.parseable.metrics.serviceMonitor.spec.keepDroppedTargets }} 57 | {{- end }} 58 | {{ if .Values.parseable.metrics.serviceMonitor.spec.attachMetadata }} 59 | attachMetadata: 60 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.attachMetadata | nindent 4 }} 61 | {{- end }} 62 | {{ if .Values.parseable.metrics.serviceMonitor.spec.scrapeClass }} 63 | scrapeClass: {{ .Values.parseable.metrics.serviceMonitor.spec.scrapeClass | quote }} 64 | {{- end }} 65 | {{ if .Values.parseable.metrics.serviceMonitor.spec.bodySizeLimit }} 66 | bodySizeLimit: 67 | {{- toYaml .Values.parseable.metrics.serviceMonitor.spec.bodySizeLimit | nindent 4 }} 68 | {{- end }} 69 | {{- end }} 70 | -------------------------------------------------------------------------------- /helm/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if $.Values.parseable.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "parseable.serviceAccountName" . }} 6 | labels: 7 | {{- include "parseable.labels" . | nindent 4 }} 8 | {{- with $.Values.parseable.serviceAccount.annotations }} 9 | annotations: 10 | {{- toYaml . | nindent 4 }} 11 | {{- end }} 12 | {{- end }} 13 | -------------------------------------------------------------------------------- /helm/templates/stage-pvc.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.parseable.persistence.staging.enabled }} 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: {{ include "parseable.fullname" . }}-staging-pvc 6 | labels: 7 | {{- include "parseable.labels" . | nindent 4 }} 8 | spec: 9 | accessModes: 10 | - {{ .Values.parseable.persistence.staging.accessMode | quote }} 11 | resources: 12 | requests: 13 | storage: {{ .Values.parseable.persistence.staging.size | quote }} 14 | {{- if .Values.parseable.persistence.staging.storageClass }} 15 | storageClassName: "{{ .Values.parseable.persistence.staging.storageClass }}" 16 | {{- else }} 17 | storageClassName: "" 18 | {{- end }} 19 | {{- end }} 20 | -------------------------------------------------------------------------------- /helm/templates/standalone-service.yaml: -------------------------------------------------------------------------------- 1 | {{- if eq .Values.parseable.highAvailability.enabled false }} 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: {{ include "parseable.fullname" . }} 6 | namespace: {{ .Release.Namespace }} 7 | labels: 8 | {{- include "parseable.labelsSelector" . | nindent 4 }} 9 | spec: 10 | type: {{ $.Values.parseable.service.type }} 11 | ports: 12 | - port: {{ $.Values.parseable.service.port }} 13 | targetPort: 8000 14 | protocol: TCP 15 | selector: 16 | {{- include "parseable.labelsSelector" . | nindent 4 }} 17 | {{- end }} 18 | -------------------------------------------------------------------------------- /parseable-ingest-haproxy.cfg: -------------------------------------------------------------------------------- 1 | global 2 | log stdout format raw local0 3 | maxconn 60000 4 | daemon 5 | 6 | defaults 7 | log global 8 | mode http 9 | option httplog 10 | option dontlognull 11 | timeout connect 5000 12 | timeout client 50000 13 | timeout server 50000 14 | 15 | frontend stats 16 | bind *:9001 17 | stats enable 18 | stats uri / 19 | stats refresh 30s 20 | stats admin if TRUE 21 | 22 | frontend ingestion_frontend 23 | bind *:8001 24 | mode http 25 | default_backend ingestion_backend 26 | 27 | backend ingestion_backend 28 | mode http 29 | balance roundrobin 30 | option forwardfor 31 | 32 | # Health check configuration 33 | option httpchk GET /api/v1/liveness 34 | http-check expect status 200 35 | 36 | # Backend servers 37 | server ingest1 parseable-ingest-one:8000 check inter 5s rise 2 fall 3 38 | server ingest2 parseable-ingest-two:8000 check inter 5s rise 2 fall 3 39 | 40 | # Session persistence 41 | hash-type consistent 42 | 43 | # Retry configuration 44 | retries 3 45 | option redispatch 46 | -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | # Parseable Server (C) 2022 - 2024 Parseable, Inc. 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU Affero General Public License as 5 | # published by the Free Software Foundation, either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU Affero General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Affero General Public License 14 | # along with this program. If not, see . 15 | 16 | FROM python:3.13-slim-bookworm 17 | 18 | RUN apt-get update && apt-get install -y --no-install-recommends \ 19 | gcc \ 20 | librdkafka-dev \ 21 | && rm -rf /var/lib/apt/lists/* 22 | 23 | RUN pip install confluent-kafka 24 | RUN pip install faker 25 | 26 | WORKDIR /app 27 | COPY kafka_log_stream_generator.py /app/ 28 | 29 | CMD ["python", "/app/kafka_log_stream_generator.py"] 30 | -------------------------------------------------------------------------------- /scripts/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/zsh 2 | 3 | # supported CPU architectures and operating systems 4 | SUPPORTED_ARCH=("x86_64" "arm64") 5 | SUPPORTED_OS=("linux" "darwin") 6 | DOWNLOAD_BASE_URL="cdn.parseable.com/" 7 | 8 | # Get the system's CPU architecture and operating system 9 | CPU_ARCH=$(uname -m) 10 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') 11 | 12 | printf "\n=========================\n" 13 | printf "Detected CPU architecture: %s\n" "$CPU_ARCH" 14 | printf "Detected operating system: %s\n" "$OS" 15 | 16 | SHELL_NAME=$(basename $SHELL) 17 | RC_FILE=".${SHELL_NAME}rc" 18 | RC_FILE_PATH="${HOME}/${RC_FILE}" 19 | INSTALL_DIR="${HOME}/.parseable" 20 | BIN_DIR="${INSTALL_DIR}/bin" 21 | BIN_NAME="${BIN_DIR}/parseable" 22 | 23 | # Check if the CPU architecture is supported 24 | if ! echo "${SUPPORTED_ARCH[@]}" | grep -q "\\b${CPU_ARCH}\\b"; then 25 | echo "Error: Unsupported CPU architecture (${CPU_ARCH})." 26 | exit 1 27 | fi 28 | # Check if the OS is supported 29 | if ! echo "${SUPPORTED_OS[@]}" | grep -q "\\b${OS}\\b"; then 30 | echo "Error: Unsupported operating system (${OS})." 31 | exit 1 32 | fi 33 | 34 | # Get the latest release information using GitHub API 35 | release=$(curl -s "https://api.github.com/repos/parseablehq/parseable/releases/latest") 36 | # find the release tag 37 | release_tag=$(echo "$release" | grep -o "\"tag_name\":\s*\"[^\"]*\"" | cut -d '"' -f 4) 38 | printf "Found latest release version: $release_tag\n" 39 | 40 | download_url=${DOWNLOAD_BASE_URL}${CPU_ARCH}-${OS}.${release_tag} 41 | 42 | if [[ -d ${INSTALL_DIR} ]]; then 43 | printf "A Previous version of parseable already exists. Run 'parseable --version' to check the version." 44 | printf "or consider removing that before new installation\n" 45 | exit 1 46 | else 47 | mkdir -p ${BIN_DIR} 48 | fi 49 | 50 | # Download the binary using curl or wget 51 | printf "Downloading Parseable version $release_tag, for OS: $OS, CPU architecture: $CPU_ARCH\n\n" 52 | if command -v curl &>/dev/null; then 53 | curl -L -o "${BIN_NAME}" "$download_url" 54 | elif command -v wget &>/dev/null; then 55 | wget -O "${BIN_NAME}" "$download_url" 56 | else 57 | echo "Error: Neither curl nor wget found. Please install either curl or wget." 58 | exit 1 59 | fi 60 | 61 | printf "Parseable Server was successfully installed at: ${BIN_NAME}\n" 62 | 63 | chmod +x "${BIN_NAME}" 64 | 65 | printf "Adding parseable to the path\n" 66 | PATH_STR="export PATH=${BIN_DIR}"':$PATH' 67 | echo ${PATH_STR} >> ${RC_FILE_PATH} 68 | 69 | echo "parseable was added to the path. Please refresh the environment by sourcing the ${RC_FILE_PATH}" 70 | -------------------------------------------------------------------------------- /src/banner.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | */ 19 | 20 | use crossterm::style::Stylize; 21 | 22 | use crate::about; 23 | use crate::utils::uid::Uid; 24 | use crate::{parseable::Parseable, storage::StorageMetadata}; 25 | 26 | pub async fn print(config: &Parseable, meta: &StorageMetadata) { 27 | print_ascii_art(); 28 | let scheme = config.options.get_scheme(); 29 | status_info(config, &scheme, meta.deployment_id); 30 | storage_info(config).await; 31 | about::print(&config.options, meta).await; 32 | println!(); 33 | } 34 | 35 | fn print_ascii_art() { 36 | let ascii_name = r#" 37 | `7MM"""Mq. *MM `7MM 38 | MM `MM. MM MM 39 | MM ,M9 ,6"Yb. `7Mb,od8 ,pP"Ybd .gP"Ya ,6"Yb. MM,dMMb. MM .gP"Ya 40 | MMmmdM9 8) MM MM' "' 8I `" ,M' Yb 8) MM MM `Mb MM ,M' Yb 41 | MM ,pm9MM MM `YMMMa. 8M"""""" ,pm9MM MM M8 MM 8M"""""" 42 | MM 8M MM MM L. I8 YM. , 8M MM MM. ,M9 MM YM. , 43 | .JMML. `Moo9^Yo..JMML. M9mmmP' `Mbmmd' `Moo9^Yo. P^YbmdP' .JMML. `Mbmmd' 44 | "#; 45 | 46 | eprint!("{ascii_name}"); 47 | } 48 | 49 | fn status_info(config: &Parseable, scheme: &str, id: Uid) { 50 | let address = format!( 51 | "\"{}://{}\" ({}), \":{}\" (livetail), \":{}\" (flight protocol)", 52 | scheme, 53 | config.options.address, 54 | scheme.to_ascii_uppercase(), 55 | config.options.grpc_port, 56 | config.options.flight_port 57 | ); 58 | 59 | let mut credentials = 60 | String::from("\"As set in P_USERNAME and P_PASSWORD environment variables\""); 61 | 62 | if config.options.is_default_creds() { 63 | credentials = "\"Using default creds admin, admin. Please set credentials with P_USERNAME and P_PASSWORD.\"".red().to_string(); 64 | } 65 | 66 | let llm_status = match &config.options.open_ai_key { 67 | Some(_) => "OpenAI Configured".green(), 68 | None => "Not Configured".grey(), 69 | }; 70 | 71 | eprintln!( 72 | " 73 | Welcome to Parseable Server! Deployment UID: \"{}\"", 74 | id.to_string(), 75 | ); 76 | 77 | eprintln!( 78 | " 79 | {} 80 | Address: {} 81 | Credentials: {} 82 | Server Mode: \"{}\" 83 | LLM Status: \"{}\"", 84 | "Server:".to_string().bold(), 85 | address, 86 | credentials, 87 | config.get_server_mode_string(), 88 | llm_status 89 | ); 90 | } 91 | 92 | /// Prints information about the `ObjectStorage`. 93 | /// - Mode (`Local drive`, `S3 bucket`) 94 | /// - Staging (temporary landing point for incoming events) 95 | /// - Store (path where the data is stored and its latency) 96 | async fn storage_info(config: &Parseable) { 97 | let storage = config.storage(); 98 | let latency = storage.get_object_store().get_latency().await; 99 | 100 | eprintln!( 101 | " 102 | {} 103 | Storage Mode: \"{}\" 104 | Staging Path: \"{}\"", 105 | "Storage:".to_string().bold(), 106 | config.get_storage_mode_string(), 107 | config.options.staging_dir().to_string_lossy(), 108 | ); 109 | 110 | if let Some(path) = &config.options.hot_tier_storage_path { 111 | eprintln!( 112 | "\ 113 | {:8}Hot Tier: \"Enabled, Path: {}\"", 114 | "", 115 | path.display(), 116 | ); 117 | } 118 | 119 | eprintln!( 120 | "\ 121 | {:8}Store: \"{}\", (latency: {:?})", 122 | "", 123 | storage.get_endpoint(), 124 | latency 125 | ); 126 | } 127 | -------------------------------------------------------------------------------- /src/catalog/snapshot.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::ops::Bound; 20 | 21 | use chrono::{DateTime, Utc}; 22 | 23 | use crate::query::PartialTimeFilter; 24 | 25 | pub const CURRENT_SNAPSHOT_VERSION: &str = "v2"; 26 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 27 | pub struct Snapshot { 28 | pub version: String, 29 | pub manifest_list: Vec, 30 | } 31 | 32 | impl Default for Snapshot { 33 | fn default() -> Self { 34 | Self { 35 | version: CURRENT_SNAPSHOT_VERSION.to_string(), 36 | manifest_list: Vec::default(), 37 | } 38 | } 39 | } 40 | 41 | impl super::Snapshot for Snapshot { 42 | fn manifests(&self, time_predicates: &[PartialTimeFilter]) -> Vec { 43 | let mut manifests = self.manifest_list.clone(); 44 | for predicate in time_predicates { 45 | match predicate { 46 | PartialTimeFilter::Low(Bound::Included(time)) => manifests.retain(|item| { 47 | let time = time.and_utc(); 48 | item.time_upper_bound >= time 49 | }), 50 | PartialTimeFilter::Low(Bound::Excluded(time)) => manifests.retain(|item| { 51 | let time = time.and_utc(); 52 | item.time_upper_bound > time 53 | }), 54 | PartialTimeFilter::High(Bound::Included(time)) => manifests.retain(|item| { 55 | let time = time.and_utc(); 56 | item.time_lower_bound <= time 57 | }), 58 | PartialTimeFilter::High(Bound::Excluded(time)) => manifests.retain(|item| { 59 | let time = time.and_utc(); 60 | item.time_lower_bound < time 61 | }), 62 | PartialTimeFilter::Eq(time) => manifests.retain(|item| { 63 | let time = time.and_utc(); 64 | item.time_lower_bound <= time && time <= item.time_upper_bound 65 | }), 66 | _ => (), 67 | } 68 | } 69 | 70 | manifests 71 | } 72 | } 73 | 74 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] 75 | pub struct ManifestItem { 76 | pub manifest_path: String, 77 | pub time_lower_bound: DateTime, 78 | pub time_upper_bound: DateTime, 79 | pub events_ingested: u64, 80 | pub ingestion_size: u64, 81 | pub storage_size: u64, 82 | } 83 | -------------------------------------------------------------------------------- /src/connectors/common/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use clap::ValueEnum; 20 | use rdkafka::error::{KafkaError, RDKafkaErrorCode}; 21 | use std::str::FromStr; 22 | use thiserror::Error; 23 | use tokio::runtime; 24 | use tokio::runtime::Builder; 25 | 26 | pub mod processor; 27 | pub mod shutdown; 28 | 29 | #[derive(Debug, Error)] 30 | pub enum ConnectorError { 31 | #[error("Kafka error: {0}")] 32 | Kafka(KafkaError), 33 | 34 | #[error("Connection error: {0}")] 35 | Connection(String), 36 | 37 | #[error("Fatal error: {0}")] 38 | Fatal(String), 39 | 40 | #[error("Processing error: {0}")] 41 | Processing(#[from] anyhow::Error), 42 | 43 | #[error("State error: {0}")] 44 | State(String), 45 | 46 | #[error("Authentication error: {0}")] 47 | Auth(String), 48 | } 49 | 50 | impl From for ConnectorError { 51 | fn from(error: KafkaError) -> Self { 52 | if let Some(code) = error.rdkafka_error_code() { 53 | match code { 54 | RDKafkaErrorCode::BrokerTransportFailure 55 | | RDKafkaErrorCode::NetworkException 56 | | RDKafkaErrorCode::AllBrokersDown => ConnectorError::Connection(error.to_string()), 57 | 58 | RDKafkaErrorCode::Fatal | RDKafkaErrorCode::CriticalSystemResource => { 59 | ConnectorError::Fatal(error.to_string()) 60 | } 61 | 62 | RDKafkaErrorCode::Authentication | RDKafkaErrorCode::SaslAuthenticationFailed => { 63 | ConnectorError::Auth(error.to_string()) 64 | } 65 | 66 | _ => ConnectorError::Kafka(error), 67 | } 68 | } else { 69 | ConnectorError::Kafka(error) 70 | } 71 | } 72 | } 73 | impl ConnectorError { 74 | pub fn is_fatal(&self) -> bool { 75 | matches!( 76 | self, 77 | ConnectorError::Fatal(_) | ConnectorError::Auth(_) | ConnectorError::State(_) 78 | ) 79 | } 80 | } 81 | 82 | #[derive(ValueEnum, Default, Clone, Debug, PartialEq, Eq, Hash)] 83 | pub enum BadData { 84 | #[default] 85 | Fail, 86 | Drop, 87 | Dlt, //TODO: Implement Dead Letter Topic support when needed 88 | } 89 | 90 | impl FromStr for BadData { 91 | type Err = String; 92 | 93 | fn from_str(s: &str) -> Result { 94 | match s.to_lowercase().as_str() { 95 | "drop" => Ok(BadData::Drop), 96 | "fail" => Ok(BadData::Fail), 97 | "dlt" => Ok(BadData::Dlt), 98 | _ => Err(format!("Invalid bad data policy: {}", s)), 99 | } 100 | } 101 | } 102 | 103 | pub fn build_runtime(worker_threads: usize, thread_name: &str) -> anyhow::Result { 104 | Builder::new_multi_thread() 105 | .enable_all() 106 | .thread_name(thread_name) 107 | .worker_threads(worker_threads) 108 | .max_blocking_threads(worker_threads) 109 | .build() 110 | .map_err(|e| anyhow::anyhow!(e)) 111 | } 112 | -------------------------------------------------------------------------------- /src/connectors/common/processor.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use async_trait::async_trait; 20 | 21 | #[async_trait] 22 | pub trait Processor: Send + Sync + Sized + 'static { 23 | async fn process(&self, records: IN) -> anyhow::Result; 24 | 25 | #[allow(unused_variables)] 26 | async fn post_stream(&self) -> anyhow::Result<()> { 27 | Ok(()) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/connectors/common/shutdown.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use tokio::sync::mpsc; 20 | use tokio_util::sync::CancellationToken; 21 | use tracing::{info, warn}; 22 | 23 | #[derive(Debug)] 24 | pub struct Shutdown { 25 | cancel_token: CancellationToken, 26 | shutdown_complete_tx: mpsc::Sender<()>, 27 | shutdown_complete_rx: Option>, 28 | } 29 | 30 | impl Shutdown { 31 | pub fn start(&self) { 32 | self.cancel_token.cancel(); 33 | } 34 | 35 | pub async fn recv(&self) { 36 | self.cancel_token.cancelled().await; 37 | } 38 | 39 | pub async fn signal_listener(&self) { 40 | let ctrl_c_signal = tokio::signal::ctrl_c(); 41 | #[cfg(unix)] 42 | let mut sigterm_signal = 43 | tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap(); 44 | #[cfg(unix)] 45 | tokio::select! { 46 | _ = ctrl_c_signal => {}, 47 | _ = sigterm_signal.recv() => {} 48 | } 49 | #[cfg(windows)] 50 | let _ = ctrl_c_signal.await; 51 | 52 | warn!("Shutdown signal received!"); 53 | self.start(); 54 | } 55 | 56 | pub async fn complete(self) { 57 | drop(self.shutdown_complete_tx); 58 | self.shutdown_complete_rx.unwrap().recv().await; 59 | info!("Shutdown complete!") 60 | } 61 | } 62 | 63 | impl Default for Shutdown { 64 | fn default() -> Self { 65 | let cancel_token = CancellationToken::new(); 66 | let (shutdown_complete_tx, shutdown_complete_rx) = mpsc::channel(1); 67 | Self { 68 | cancel_token, 69 | shutdown_complete_tx, 70 | shutdown_complete_rx: Some(shutdown_complete_rx), 71 | } 72 | } 73 | } 74 | 75 | impl Clone for Shutdown { 76 | fn clone(&self) -> Self { 77 | Self { 78 | cancel_token: self.cancel_token.clone(), 79 | shutdown_complete_tx: self.shutdown_complete_tx.clone(), 80 | shutdown_complete_rx: None, 81 | } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use std::sync::{Arc, Mutex}; 88 | 89 | use super::*; 90 | use tokio::time::Duration; 91 | 92 | #[tokio::test] 93 | async fn test_shutdown_recv() { 94 | let shutdown = Shutdown::default(); 95 | let shutdown_clone = shutdown.clone(); 96 | // receive shutdown task 97 | let task = tokio::spawn(async move { 98 | shutdown_clone.recv().await; 99 | 1 100 | }); 101 | // start shutdown task after 200 ms 102 | tokio::spawn(async move { 103 | tokio::time::sleep(Duration::from_millis(200)).await; 104 | shutdown.start(); 105 | }); 106 | // if shutdown is not received within 5 seconds, fail test 107 | let check_value = tokio::select! { 108 | _ = tokio::time::sleep(Duration::from_secs(5)) => panic!("Shutdown not received within 5 seconds"), 109 | v = task => v.unwrap(), 110 | }; 111 | assert_eq!(check_value, 1); 112 | } 113 | 114 | #[tokio::test] 115 | async fn test_shutdown_wait_for_complete() { 116 | let shutdown = Shutdown::default(); 117 | let shutdown_clone = shutdown.clone(); 118 | let check_value: Arc> = Arc::new(Mutex::new(false)); 119 | let check_value_clone = Arc::clone(&check_value); 120 | // receive shutdown task 121 | tokio::spawn(async move { 122 | shutdown_clone.recv().await; 123 | tokio::time::sleep(Duration::from_millis(200)).await; 124 | let mut check: std::sync::MutexGuard<'_, bool> = check_value_clone.lock().unwrap(); 125 | *check = true; 126 | }); 127 | shutdown.start(); 128 | shutdown.complete().await; 129 | let check = check_value.lock().unwrap(); 130 | assert!(*check, "shutdown did not successfully wait for complete"); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/connectors/kafka/partition_stream.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use crate::connectors::kafka::{ConsumerRecord, TopicPartition}; 20 | use std::sync::Arc; 21 | use tokio::sync::{mpsc, Notify}; 22 | use tokio_stream::wrappers::ReceiverStream; 23 | use tracing::{error, info}; 24 | 25 | #[derive(Clone)] 26 | pub struct PartitionStreamSender { 27 | inner: mpsc::Sender, 28 | notify: Arc, 29 | } 30 | 31 | impl PartitionStreamSender { 32 | fn new(inner: mpsc::Sender, notify: Arc) -> Self { 33 | Self { inner, notify } 34 | } 35 | 36 | pub fn terminate(&self) { 37 | self.notify.notify_waiters(); 38 | } 39 | 40 | pub async fn send(&self, consumer_record: ConsumerRecord) { 41 | if let Err(e) = self.inner.send(consumer_record).await { 42 | error!("Failed to send message to partition stream: {:?}", e); 43 | } 44 | } 45 | 46 | pub fn sender(&self) -> mpsc::Sender { 47 | self.inner.clone() 48 | } 49 | } 50 | 51 | pub struct PartitionStreamReceiver { 52 | inner: ReceiverStream, 53 | topic_partition: TopicPartition, 54 | notify: Arc, 55 | } 56 | 57 | impl PartitionStreamReceiver { 58 | fn new( 59 | receiver: mpsc::Receiver, 60 | topic_partition: TopicPartition, 61 | notify: Arc, 62 | ) -> Self { 63 | Self { 64 | inner: ReceiverStream::new(receiver), 65 | topic_partition, 66 | notify, 67 | } 68 | } 69 | 70 | /// Processes the stream with a provided callback and listens for termination. 71 | /// 72 | /// # Parameters 73 | /// - `invoke`: A callback function that processes the `ReceiverStream`. 74 | /// 75 | /// # Behavior 76 | /// - The callback runs until either the stream is completed or a termination signal is received. 77 | pub async fn run_drain(self, f: F) 78 | where 79 | F: Fn(ReceiverStream) -> Fut, 80 | Fut: futures_util::Future, 81 | { 82 | let notify = self.notify.clone(); 83 | 84 | tokio::select! { 85 | _ = f(self.inner) => { 86 | info!("PartitionStreamReceiver completed processing for {:?}.", self.topic_partition); 87 | } 88 | _ = notify.notified() => { 89 | info!("Received termination signal for {:?}.", self.topic_partition); 90 | } 91 | } 92 | } 93 | 94 | pub fn topic_partition(&self) -> &TopicPartition { 95 | &self.topic_partition 96 | } 97 | } 98 | 99 | pub fn bounded( 100 | size: usize, 101 | topic_partition: TopicPartition, 102 | ) -> (PartitionStreamSender, PartitionStreamReceiver) { 103 | let (tx, rx) = mpsc::channel(size); 104 | let notify = Arc::new(Notify::new()); 105 | 106 | let sender = PartitionStreamSender::new(tx, notify.clone()); 107 | let receiver = PartitionStreamReceiver::new(rx, topic_partition, notify); 108 | 109 | (sender, receiver) 110 | } 111 | -------------------------------------------------------------------------------- /src/connectors/kafka/rebalance_listener.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use crate::connectors::common::shutdown::Shutdown; 20 | use crate::connectors::kafka::state::StreamState; 21 | use crate::connectors::kafka::RebalanceEvent; 22 | use std::sync::Arc; 23 | use tokio::sync::RwLock; 24 | use tokio::{runtime::Handle, sync::mpsc::Receiver}; 25 | use tracing::{info, warn}; 26 | 27 | pub struct RebalanceListener { 28 | rebalance_rx: Receiver, 29 | stream_state: Arc>, 30 | shutdown_handle: Shutdown, 31 | } 32 | 33 | impl RebalanceListener { 34 | pub fn new( 35 | rebalance_rx: Receiver, 36 | stream_state: Arc>, 37 | shutdown_handle: Shutdown, 38 | ) -> Self { 39 | Self { 40 | rebalance_rx, 41 | stream_state, 42 | shutdown_handle, 43 | } 44 | } 45 | 46 | pub fn start(self) { 47 | let mut rebalance_receiver = self.rebalance_rx; 48 | let stream_state = self.stream_state.clone(); 49 | let shutdown_handle = self.shutdown_handle.clone(); 50 | let tokio_runtime_handle = Handle::current(); 51 | 52 | std::thread::Builder::new().name("rebalance-listener-thread".to_string()).spawn(move || { 53 | tokio_runtime_handle.block_on(async move { 54 | loop { 55 | tokio::select! { 56 | rebalance = rebalance_receiver.recv() => { 57 | match rebalance { 58 | Some(RebalanceEvent::Assign(tpl)) => info!("RebalanceEvent Assign: {:?}", tpl), 59 | Some(RebalanceEvent::Revoke(tpl, callback)) => { 60 | info!("RebalanceEvent Revoke: {:?}", tpl); 61 | if let Ok(mut stream_state) = stream_state.try_write() { 62 | stream_state.terminate_partition_streams(tpl).await; 63 | drop(stream_state); 64 | } else { 65 | warn!("Stream state lock is busy, skipping rebalance revoke for {:?}", tpl); 66 | } 67 | if let Err(err) = callback.send(()) { 68 | warn!("Error during sending response to context. Cause: {:?}", err); 69 | } 70 | info!("Finished Rebalance Revoke"); 71 | } 72 | None => { 73 | info!("Rebalance event sender is closed!"); 74 | break 75 | } 76 | } 77 | }, 78 | _ = shutdown_handle.recv() => { 79 | info!("Gracefully stopping rebalance listener!"); 80 | break; 81 | }, 82 | } 83 | } 84 | }) 85 | }).expect("Failed to start rebalance listener thread"); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/connectors/kafka/sink.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | use crate::connectors::common::build_runtime; 19 | use crate::connectors::common::processor::Processor; 20 | use crate::connectors::kafka::consumer::KafkaStreams; 21 | use crate::connectors::kafka::processor::StreamWorker; 22 | use crate::connectors::kafka::ConsumerRecord; 23 | use anyhow::Result; 24 | use futures_util::StreamExt; 25 | use rdkafka::consumer::Consumer; 26 | use std::sync::Arc; 27 | use tokio::runtime::Runtime; 28 | use tracing::{error, info}; 29 | 30 | pub struct KafkaSinkConnector

31 | where 32 | P: Processor, ()>, 33 | { 34 | streams: KafkaStreams, 35 | stream_processor: Arc>, 36 | runtime: Runtime, 37 | } 38 | 39 | impl

KafkaSinkConnector

40 | where 41 | P: Processor, ()> + Send + Sync + 'static, 42 | { 43 | pub fn new(kafka_streams: KafkaStreams, processor: P) -> Self { 44 | let consumer = kafka_streams.consumer(); 45 | let stream_processor = Arc::new(StreamWorker::new( 46 | Arc::new(processor), 47 | Arc::clone(&consumer), 48 | )); 49 | 50 | let runtime = build_runtime( 51 | consumer.context().config.partition_listener_concurrency, 52 | "kafka-sink-worker", 53 | ) 54 | .expect("Failed to build runtime"); 55 | let _ = runtime.enter(); 56 | 57 | Self { 58 | streams: kafka_streams, 59 | stream_processor, 60 | runtime, 61 | } 62 | } 63 | 64 | pub async fn run(self) -> Result<()> { 65 | self.streams 66 | .partitioned() 67 | .map(|partition_stream| { 68 | let worker = Arc::clone(&self.stream_processor); 69 | let tp = partition_stream.topic_partition().clone(); 70 | self.runtime.spawn(async move { 71 | partition_stream 72 | .run_drain(|partition_records| async { 73 | info!("Starting task for partition: {:?}", tp); 74 | 75 | worker 76 | .process_partition(tp.clone(), partition_records) 77 | .await 78 | .unwrap(); 79 | }) 80 | .await; 81 | 82 | info!("Task completed for partition: {:?}", tp); 83 | }) 84 | }) 85 | .for_each_concurrent(None, |task| async { 86 | if let Err(e) = task.await { 87 | error!("Task failed: {:?}", e); 88 | } 89 | }) 90 | .await; 91 | 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/connectors/kafka/state.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use crate::connectors::kafka::partition_stream::PartitionStreamSender; 20 | use crate::connectors::kafka::{TopicPartition, TopicPartitionList}; 21 | use std::collections::HashMap; 22 | use tracing::info; 23 | 24 | pub struct StreamState { 25 | partition_senders: HashMap, 26 | } 27 | 28 | impl StreamState { 29 | pub fn new(capacity: usize) -> Self { 30 | Self { 31 | partition_senders: HashMap::with_capacity(capacity), 32 | } 33 | } 34 | 35 | pub fn insert_partition_sender( 36 | &mut self, 37 | tp: TopicPartition, 38 | sender: PartitionStreamSender, 39 | ) -> Option { 40 | self.partition_senders.insert(tp, sender) 41 | } 42 | 43 | pub fn get_partition_sender(&self, tp: &TopicPartition) -> Option<&PartitionStreamSender> { 44 | self.partition_senders.get(tp) 45 | } 46 | 47 | pub async fn terminate_partition_streams(&mut self, tpl: TopicPartitionList) { 48 | info!("Terminating streams: {:?}", tpl); 49 | 50 | for tp in tpl.tpl { 51 | if let Some(sender) = self.partition_senders.remove(&tp) { 52 | info!("Terminating stream for {:?}", tp); 53 | sender.terminate(); 54 | drop(sender); 55 | info!("Stream terminated for {:?}", tp); 56 | } else { 57 | info!("Stream already completed for {:?}", tp); 58 | } 59 | } 60 | 61 | info!("All streams terminated!"); 62 | } 63 | 64 | pub fn clear(&mut self) { 65 | info!("Clearing all stream states..."); 66 | self.partition_senders.clear(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/connectors/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::sync::Arc; 20 | 21 | use actix_web_prometheus::PrometheusMetrics; 22 | use common::{processor::Processor, shutdown::Shutdown}; 23 | use kafka::{ 24 | config::KafkaConfig, consumer::KafkaStreams, metrics::KafkaMetricsCollector, 25 | processor::ParseableSinkProcessor, rebalance_listener::RebalanceListener, 26 | sink::KafkaSinkConnector, state::StreamState, ConsumerRecord, KafkaContext, 27 | }; 28 | use prometheus::Registry; 29 | use tokio::sync::RwLock; 30 | use tracing::{info, warn}; 31 | 32 | use crate::{option::Mode, parseable::PARSEABLE}; 33 | 34 | pub mod common; 35 | pub mod kafka; 36 | 37 | pub async fn init(prometheus: &PrometheusMetrics) -> anyhow::Result<()> { 38 | if matches!(PARSEABLE.options.mode, Mode::Ingest | Mode::All) { 39 | match PARSEABLE.kafka_config.validate() { 40 | Err(e) => { 41 | warn!("Kafka connector configuration invalid. {}", e); 42 | } 43 | Ok(_) => { 44 | let config = PARSEABLE.kafka_config.clone(); 45 | let shutdown_handle = Shutdown::default(); 46 | let registry = prometheus.registry.clone(); 47 | let processor = ParseableSinkProcessor; 48 | 49 | tokio::spawn({ 50 | let shutdown_handle = shutdown_handle.clone(); 51 | async move { 52 | shutdown_handle.signal_listener().await; 53 | info!("Connector received shutdown signal!"); 54 | } 55 | }); 56 | 57 | run_kafka2parseable(config, registry, processor, shutdown_handle).await?; 58 | } 59 | } 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | async fn run_kafka2parseable

( 66 | config: KafkaConfig, 67 | registry: Registry, 68 | processor: P, 69 | shutdown_handle: Shutdown, 70 | ) -> anyhow::Result<()> 71 | where 72 | P: Processor, ()> + Send + Sync + 'static, 73 | { 74 | info!("Initializing KafkaSink connector..."); 75 | 76 | let kafka_config = Arc::new(config.clone()); 77 | let (kafka_context, rebalance_rx) = KafkaContext::new(kafka_config); 78 | 79 | //TODO: fetch topics metadata from kafka then give dynamic value to StreamState 80 | let stream_state = Arc::new(RwLock::new(StreamState::new(60))); 81 | let rebalance_listener = RebalanceListener::new( 82 | rebalance_rx, 83 | Arc::clone(&stream_state), 84 | shutdown_handle.clone(), 85 | ); 86 | 87 | let kafka_streams = KafkaStreams::init(kafka_context, stream_state, shutdown_handle.clone())?; 88 | 89 | let stats = kafka_streams.statistics(); 90 | registry.register(Box::new(KafkaMetricsCollector::new(stats)?))?; 91 | 92 | let kafka_parseable_sink_connector = KafkaSinkConnector::new(kafka_streams, processor); 93 | 94 | rebalance_listener.start(); 95 | kafka_parseable_sink_connector.run().await?; 96 | 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /src/enterprise/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | -------------------------------------------------------------------------------- /src/handlers/http/about.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::web::Json; 20 | use serde_json::{json, Value}; 21 | 22 | use crate::{ 23 | about::{self, get_latest_release}, 24 | parseable::PARSEABLE, 25 | storage::StorageMetadata, 26 | }; 27 | use std::path::PathBuf; 28 | 29 | /// { 30 | /// "version": current_version, 31 | /// "uiVersion": ui_version, 32 | /// "commit": commit, 33 | /// "deploymentId": deployment_id, 34 | /// "updateAvailable": update_available, 35 | /// "latestVersion": latest_release, 36 | /// "llmActive": is_llm_active, 37 | /// "llmProvider": llm_provider, 38 | /// "oidcActive": is_oidc_active, 39 | /// "license": "AGPL-3.0-only", 40 | /// "mode": mode, 41 | /// "staging": staging, 42 | /// "grpcPort": grpc_port, 43 | /// "store": { 44 | /// "type": PARSEABLE.get_storage_mode_string(), 45 | /// "path": store_endpoint 46 | /// } 47 | /// } 48 | pub async fn about() -> Json { 49 | let meta = StorageMetadata::global(); 50 | 51 | let current_release = about::current(); 52 | let latest_release = get_latest_release(); 53 | let (update_available, latest_release) = match latest_release { 54 | Some(latest_release) => ( 55 | latest_release.version > current_release.released_version, 56 | Some(format!("v{}", latest_release.version)), 57 | ), 58 | None => (false, None), 59 | }; 60 | 61 | let current_version = format!("v{}", current_release.released_version); 62 | let commit = current_release.commit_hash; 63 | let deployment_id = meta.deployment_id.to_string(); 64 | let mode = PARSEABLE.get_server_mode_string(); 65 | let staging = PARSEABLE.options.staging_dir().display().to_string(); 66 | let grpc_port = PARSEABLE.options.grpc_port; 67 | 68 | let store_endpoint = PARSEABLE.storage.get_endpoint(); 69 | let is_llm_active = &PARSEABLE.options.open_ai_key.is_some(); 70 | let llm_provider = is_llm_active.then_some("OpenAI"); 71 | let is_oidc_active = PARSEABLE.options.openid().is_some(); 72 | let ui_version = option_env!("UI_VERSION").unwrap_or("development"); 73 | 74 | let hot_tier_details: String = if PARSEABLE.hot_tier_dir().is_none() { 75 | "Disabled".to_string() 76 | } else { 77 | let hot_tier_dir: &Option = PARSEABLE.hot_tier_dir(); 78 | format!( 79 | "Enabled, Path: {}", 80 | hot_tier_dir.as_ref().unwrap().display(), 81 | ) 82 | }; 83 | 84 | let ms_clarity_tag = &PARSEABLE.options.ms_clarity_tag; 85 | 86 | Json(json!({ 87 | "version": current_version, 88 | "uiVersion": ui_version, 89 | "commit": commit, 90 | "deploymentId": deployment_id, 91 | "updateAvailable": update_available, 92 | "latestVersion": latest_release, 93 | "llmActive": is_llm_active, 94 | "llmProvider": llm_provider, 95 | "oidcActive": is_oidc_active, 96 | "license": "AGPL-3.0-only", 97 | "mode": mode, 98 | "staging": staging, 99 | "hotTier": hot_tier_details, 100 | "grpcPort": grpc_port, 101 | "store": { 102 | "type": PARSEABLE.get_storage_mode_string(), 103 | "path": store_endpoint 104 | }, 105 | "analytics": { 106 | "clarityTag": ms_clarity_tag 107 | }, 108 | })) 109 | } 110 | -------------------------------------------------------------------------------- /src/handlers/http/correlation.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::web::{Json, Path}; 20 | use actix_web::{web, HttpRequest, HttpResponse, Responder}; 21 | use anyhow::Error; 22 | use itertools::Itertools; 23 | 24 | use crate::rbac::Users; 25 | use crate::utils::actix::extract_session_key_from_req; 26 | use crate::utils::{get_hash, get_user_from_request, user_auth_for_datasets}; 27 | 28 | use crate::correlation::{CorrelationConfig, CorrelationError, CORRELATIONS}; 29 | 30 | pub async fn list(req: HttpRequest) -> Result { 31 | let session_key = extract_session_key_from_req(&req) 32 | .map_err(|err| CorrelationError::AnyhowError(Error::msg(err.to_string())))?; 33 | 34 | let correlations = CORRELATIONS.list_correlations(&session_key).await?; 35 | 36 | Ok(web::Json(correlations)) 37 | } 38 | 39 | pub async fn get( 40 | req: HttpRequest, 41 | correlation_id: Path, 42 | ) -> Result { 43 | let correlation_id = correlation_id.into_inner(); 44 | let session_key = extract_session_key_from_req(&req) 45 | .map_err(|err| CorrelationError::AnyhowError(Error::msg(err.to_string())))?; 46 | 47 | let correlation = CORRELATIONS.get_correlation(&correlation_id).await?; 48 | 49 | let permissions = Users.get_permissions(&session_key); 50 | 51 | let tables = &correlation 52 | .table_configs 53 | .iter() 54 | .map(|t| t.table_name.clone()) 55 | .collect_vec(); 56 | 57 | user_auth_for_datasets(&permissions, tables)?; 58 | 59 | Ok(web::Json(correlation)) 60 | } 61 | 62 | pub async fn post( 63 | req: HttpRequest, 64 | Json(mut correlation): Json, 65 | ) -> Result { 66 | let session_key = extract_session_key_from_req(&req) 67 | .map_err(|err| CorrelationError::AnyhowError(anyhow::Error::msg(err.to_string())))?; 68 | let user_id = get_user_from_request(&req) 69 | .map(|s| get_hash(&s.to_string())) 70 | .map_err(|err| CorrelationError::AnyhowError(Error::msg(err.to_string())))?; 71 | correlation.user_id = user_id; 72 | 73 | let correlation = CORRELATIONS.create(correlation, &session_key).await?; 74 | 75 | Ok(web::Json(correlation)) 76 | } 77 | 78 | pub async fn modify( 79 | req: HttpRequest, 80 | correlation_id: Path, 81 | Json(mut correlation): Json, 82 | ) -> Result { 83 | correlation.id = correlation_id.into_inner(); 84 | correlation.user_id = get_user_from_request(&req) 85 | .map(|s| get_hash(&s.to_string())) 86 | .map_err(|err| CorrelationError::AnyhowError(Error::msg(err.to_string())))?; 87 | 88 | let session_key = extract_session_key_from_req(&req) 89 | .map_err(|err| CorrelationError::AnyhowError(anyhow::Error::msg(err.to_string())))?; 90 | 91 | let correlation = CORRELATIONS.update(correlation, &session_key).await?; 92 | 93 | Ok(web::Json(correlation)) 94 | } 95 | 96 | pub async fn delete( 97 | req: HttpRequest, 98 | correlation_id: Path, 99 | ) -> Result { 100 | let correlation_id = correlation_id.into_inner(); 101 | let user_id = get_user_from_request(&req) 102 | .map(|s| get_hash(&s.to_string())) 103 | .map_err(|err| CorrelationError::AnyhowError(Error::msg(err.to_string())))?; 104 | 105 | CORRELATIONS.delete(&correlation_id, &user_id).await?; 106 | 107 | Ok(HttpResponse::Ok().finish()) 108 | } 109 | -------------------------------------------------------------------------------- /src/handlers/http/health_check.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::sync::Arc; 20 | 21 | use actix_web::{ 22 | body::MessageBody, 23 | dev::{ServiceRequest, ServiceResponse}, 24 | error::Error, 25 | error::ErrorServiceUnavailable, 26 | middleware::Next, 27 | HttpResponse, 28 | }; 29 | use http::StatusCode; 30 | use once_cell::sync::Lazy; 31 | use tokio::{sync::Mutex, task::JoinSet}; 32 | use tracing::{error, info, warn}; 33 | 34 | use crate::parseable::PARSEABLE; 35 | 36 | // Create a global variable to store signal status 37 | static SIGNAL_RECEIVED: Lazy>> = Lazy::new(|| Arc::new(Mutex::new(false))); 38 | 39 | pub async fn liveness() -> HttpResponse { 40 | HttpResponse::new(StatusCode::OK) 41 | } 42 | 43 | pub async fn check_shutdown_middleware( 44 | req: ServiceRequest, 45 | next: Next, 46 | ) -> Result, Error> { 47 | // Acquire the shutdown flag to check if the server is shutting down. 48 | if *SIGNAL_RECEIVED.lock().await { 49 | // Return 503 Service Unavailable if the server is shutting down. 50 | Err(ErrorServiceUnavailable("Server is shutting down")) 51 | } else { 52 | // Continue processing the request if the server is not shutting down. 53 | next.call(req).await 54 | } 55 | } 56 | 57 | // This function is called when the server is shutting down 58 | pub async fn shutdown() { 59 | // Set the shutdown flag to true 60 | let mut shutdown_flag = SIGNAL_RECEIVED.lock().await; 61 | *shutdown_flag = true; 62 | 63 | let mut joinset = JoinSet::new(); 64 | 65 | // Sync staging 66 | PARSEABLE.streams.flush_and_convert(&mut joinset, true); 67 | 68 | while let Some(res) = joinset.join_next().await { 69 | match res { 70 | Ok(Ok(_)) => info!("Successfully converted arrow files to parquet."), 71 | Ok(Err(err)) => warn!("Failed to convert arrow files to parquet. {err:?}"), 72 | Err(err) => error!("Failed to join async task: {err}"), 73 | } 74 | } 75 | 76 | if let Err(e) = PARSEABLE 77 | .storage 78 | .get_object_store() 79 | .upload_files_from_staging() 80 | .await 81 | { 82 | warn!("Failed to sync local data with object store. {:?}", e); 83 | } else { 84 | info!("Successfully synced all data to S3."); 85 | } 86 | } 87 | 88 | pub async fn readiness() -> HttpResponse { 89 | // Check the object store connection 90 | if PARSEABLE.storage.get_object_store().check().await.is_ok() { 91 | HttpResponse::new(StatusCode::OK) 92 | } else { 93 | HttpResponse::new(StatusCode::SERVICE_UNAVAILABLE) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/handlers/http/kinesis.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use base64::{engine::general_purpose::STANDARD, Engine as _}; 20 | use serde::{Deserialize, Serialize}; 21 | use serde_json::{Map, Value}; 22 | use std::str; 23 | 24 | use crate::utils::json::flatten::{generic_flattening, has_more_than_max_allowed_levels}; 25 | 26 | #[derive(Serialize, Deserialize, Debug)] 27 | #[serde(rename_all = "camelCase")] 28 | pub struct Message { 29 | records: Vec, 30 | request_id: String, 31 | timestamp: u64, 32 | } 33 | #[derive(Serialize, Deserialize, Debug)] 34 | struct Data { 35 | data: String, 36 | } 37 | 38 | // Flatten Kinesis logs is used to flatten the Kinesis logs into a queryable JSON format. 39 | // Kinesis logs are in the format 40 | // { 41 | // "requestId": "9b848d8a-2d89-474b-b073-04b8e5232210", 42 | // "timestamp": 1705026780451, 43 | // "records": [ 44 | // { 45 | // "data": "eyJDSEFOR0UiOi0wLjQ1LCJQUklDRSI6NS4zNiwiVElDS0VSX1NZTUJPTCI6IkRFRyIsIlNFQ1RPUiI6IkVORVJHWSJ9" 46 | // } 47 | // ] 48 | // } 49 | // The data field is base64 encoded JSON (there can be multiple data fields), and there is a requestId and timestamp field. 50 | // Kinesis logs are flattened to the following format: 51 | // { 52 | // "CHANGE": 3.16, 53 | // "PRICE": 73.76, 54 | // "SECTOR": "RETAIL", 55 | // "TICKER_SYMBOL": "WMT", 56 | // "p_metadata": "", 57 | // "p_tags": "", 58 | // "p_timestamp": "2024-01-11T09:08:34.290", 59 | // "requestId": "b858288a-f5d8-4181-a746-3f3dd716be8a", 60 | // "timestamp": "1704964113659" 61 | // } 62 | pub async fn flatten_kinesis_logs(message: Message) -> Result, anyhow::Error> { 63 | let mut vec_kinesis_json = Vec::new(); 64 | 65 | for record in message.records.iter() { 66 | let bytes = STANDARD.decode(record.data.clone())?; 67 | if let Ok(json_string) = String::from_utf8(bytes) { 68 | let json: serde_json::Value = serde_json::from_str(&json_string)?; 69 | // Check if the JSON has more than the allowed levels of nesting 70 | // If it has less than or equal to the allowed levels, we flatten it. 71 | // If it has more than the allowed levels, we just push it as is 72 | // without flattening or modifying it. 73 | if !has_more_than_max_allowed_levels(&json, 1) { 74 | let flattened_json_arr = generic_flattening(&json)?; 75 | for flattened_json in flattened_json_arr { 76 | let mut kinesis_json: Map = 77 | serde_json::from_value(flattened_json)?; 78 | kinesis_json.insert( 79 | "requestId".to_owned(), 80 | Value::String(message.request_id.clone()), 81 | ); 82 | kinesis_json.insert( 83 | "timestamp".to_owned(), 84 | Value::String(message.timestamp.to_string()), 85 | ); 86 | 87 | vec_kinesis_json.push(Value::Object(kinesis_json)); 88 | } 89 | } else { 90 | // If the JSON has more than the allowed levels, we just push it as is 91 | // without flattening or modifying it. 92 | // This is a fallback to ensure we don't lose data. 93 | tracing::warn!( 94 | "Kinesis log with requestId {} and timestamp {} has more than the allowed levels of nesting, skipping flattening for this record.", 95 | message.request_id, message.timestamp 96 | ); 97 | vec_kinesis_json.push(json); 98 | } 99 | } else { 100 | tracing::error!( 101 | "Failed to decode base64 data for kinesis log with requestId {} and timestamp {}", 102 | message.request_id, 103 | message.timestamp 104 | ); 105 | return Err(anyhow::anyhow!( 106 | "Failed to decode base64 data for record with requestId {} and timestamp {}", 107 | message.request_id, 108 | message.timestamp 109 | )); 110 | } 111 | } 112 | 113 | Ok(vec_kinesis_json) 114 | } 115 | -------------------------------------------------------------------------------- /src/handlers/http/modal/ingest/ingestor_ingest.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::{HttpRequest, HttpResponse}; 20 | use bytes::Bytes; 21 | 22 | use crate::{handlers::http::{ingest::PostError, modal::utils::ingest_utils::flatten_and_push_logs}, metadata::PARSEABLE.streams}; 23 | 24 | 25 | // Handler for POST /api/v1/logstream/{logstream} 26 | // only ingests events into the specified logstream 27 | // fails if the logstream does not exist 28 | pub async fn post_event(req: HttpRequest, body: Bytes) -> Result { 29 | let stream_name: String = req.match_info().get("logstream").unwrap().parse().unwrap(); 30 | let internal_stream_names = PARSEABLE.streams.list_internal_streams(); 31 | if internal_stream_names.contains(&stream_name) { 32 | return Err(PostError::Invalid(anyhow::anyhow!( 33 | "Stream {} is an internal stream and cannot be ingested into", 34 | stream_name 35 | ))); 36 | } 37 | if !PARSEABLE.streams.stream_exists(&stream_name) { 38 | return Err(PostError::StreamNotFound(stream_name)); 39 | } 40 | 41 | flatten_and_push_logs(req, body, stream_name).await?; 42 | Ok(HttpResponse::Ok().finish()) 43 | } -------------------------------------------------------------------------------- /src/handlers/http/modal/ingest/ingestor_logstream.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::fs; 20 | 21 | use actix_web::{ 22 | web::{Json, Path}, 23 | HttpRequest, Responder, 24 | }; 25 | use bytes::Bytes; 26 | use http::StatusCode; 27 | use tracing::warn; 28 | 29 | use crate::{ 30 | catalog::remove_manifest_from_snapshot, 31 | handlers::http::logstream::error::StreamError, 32 | parseable::{StreamNotFound, PARSEABLE}, 33 | stats, 34 | }; 35 | 36 | pub async fn retention_cleanup( 37 | stream_name: Path, 38 | Json(date_list): Json>, 39 | ) -> Result { 40 | let stream_name = stream_name.into_inner(); 41 | let storage = PARSEABLE.storage.get_object_store(); 42 | // if the stream not found in memory map, 43 | //check if it exists in the storage 44 | //create stream and schema from storage 45 | if !PARSEABLE.streams.contains(&stream_name) 46 | && !PARSEABLE 47 | .create_stream_and_schema_from_storage(&stream_name) 48 | .await 49 | .unwrap_or(false) 50 | { 51 | return Err(StreamNotFound(stream_name.clone()).into()); 52 | } 53 | 54 | let res = remove_manifest_from_snapshot(storage.clone(), &stream_name, date_list).await; 55 | let first_event_at: Option = res.unwrap_or_default(); 56 | 57 | Ok((first_event_at, StatusCode::OK)) 58 | } 59 | 60 | pub async fn delete(stream_name: Path) -> Result { 61 | let stream_name = stream_name.into_inner(); 62 | 63 | // Delete from staging 64 | let stream_dir = PARSEABLE.get_stream(&stream_name)?; 65 | if let Err(err) = fs::remove_dir_all(&stream_dir.data_path) { 66 | warn!( 67 | "failed to delete local data for stream {} with error {err}. Clean {} manually", 68 | stream_name, 69 | stream_dir.data_path.to_string_lossy() 70 | ) 71 | } 72 | 73 | // Delete from memory 74 | PARSEABLE.streams.delete(&stream_name); 75 | stats::delete_stats(&stream_name, "json") 76 | .unwrap_or_else(|e| warn!("failed to delete stats for stream {}: {:?}", stream_name, e)); 77 | 78 | Ok((format!("log stream {stream_name} deleted"), StatusCode::OK)) 79 | } 80 | 81 | pub async fn put_stream( 82 | req: HttpRequest, 83 | stream_name: Path, 84 | body: Bytes, 85 | ) -> Result { 86 | let stream_name = stream_name.into_inner(); 87 | PARSEABLE 88 | .create_update_stream(req.headers(), &body, &stream_name) 89 | .await?; 90 | 91 | Ok(("Log stream created", StatusCode::OK)) 92 | } 93 | -------------------------------------------------------------------------------- /src/handlers/http/modal/ingest/ingestor_rbac.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::collections::HashSet; 20 | 21 | use actix_web::{web, Responder}; 22 | use tokio::sync::Mutex; 23 | 24 | use crate::{ 25 | handlers::http::{modal::utils::rbac_utils::get_metadata, rbac::RBACError}, 26 | rbac::{ 27 | user::{self, User as ParseableUser}, 28 | Users, 29 | }, 30 | storage, 31 | }; 32 | 33 | // async aware lock for updating storage metadata and user map atomicically 34 | static UPDATE_LOCK: Mutex<()> = Mutex::const_new(()); 35 | 36 | // Handler for POST /api/v1/user/{username} 37 | // Creates a new user by username if it does not exists 38 | pub async fn post_user( 39 | username: web::Path, 40 | body: Option>, 41 | ) -> Result { 42 | let username = username.into_inner(); 43 | 44 | let generated_password = String::default(); 45 | let metadata = get_metadata().await?; 46 | if let Some(body) = body { 47 | let user: ParseableUser = serde_json::from_value(body.into_inner())?; 48 | let _ = storage::put_staging_metadata(&metadata); 49 | let created_role = user.roles.clone(); 50 | Users.put_user(user.clone()); 51 | Users.put_role(&username, created_role.clone()); 52 | } 53 | 54 | Ok(generated_password) 55 | } 56 | 57 | // Handler for DELETE /api/v1/user/delete/{username} 58 | pub async fn delete_user(username: web::Path) -> Result { 59 | let username = username.into_inner(); 60 | let _ = UPDATE_LOCK.lock().await; 61 | // fail this request if the user does not exists 62 | if !Users.contains(&username) { 63 | return Err(RBACError::UserDoesNotExist); 64 | }; 65 | // delete from parseable.json first 66 | let mut metadata = get_metadata().await?; 67 | metadata.users.retain(|user| user.username() != username); 68 | 69 | let _ = storage::put_staging_metadata(&metadata); 70 | 71 | // update in mem table 72 | Users.delete_user(&username); 73 | Ok(format!("deleted user: {username}")) 74 | } 75 | 76 | // Handler PUT /user/{username}/roles => Put roles for user 77 | // Put roles for given user 78 | pub async fn put_role( 79 | username: web::Path, 80 | role: web::Json>, 81 | ) -> Result { 82 | let username = username.into_inner(); 83 | let role = role.into_inner(); 84 | 85 | if !Users.contains(&username) { 86 | return Err(RBACError::UserDoesNotExist); 87 | }; 88 | // update parseable.json first 89 | let mut metadata = get_metadata().await?; 90 | if let Some(user) = metadata 91 | .users 92 | .iter_mut() 93 | .find(|user| user.username() == username) 94 | { 95 | user.roles.clone_from(&role); 96 | } else { 97 | // should be unreachable given state is always consistent 98 | return Err(RBACError::UserDoesNotExist); 99 | } 100 | 101 | let _ = storage::put_staging_metadata(&metadata); 102 | // update in mem table 103 | Users.put_role(&username.clone(), role.clone()); 104 | 105 | Ok(format!("Roles updated successfully for {username}")) 106 | } 107 | 108 | // Handler for POST /api/v1/user/{username}/generate-new-password 109 | // Resets password for the user to a newly generated one and returns it 110 | pub async fn post_gen_password(username: web::Path) -> Result { 111 | let username = username.into_inner(); 112 | let mut new_hash = String::default(); 113 | let mut metadata = get_metadata().await?; 114 | 115 | let _ = storage::put_staging_metadata(&metadata); 116 | if let Some(user) = metadata 117 | .users 118 | .iter_mut() 119 | .filter_map(|user| match user.ty { 120 | user::UserType::Native(ref mut user) => Some(user), 121 | _ => None, 122 | }) 123 | .find(|user| user.username == username) 124 | { 125 | new_hash.clone_from(&user.password_hash); 126 | } else { 127 | return Err(RBACError::UserDoesNotExist); 128 | } 129 | Users.change_password_hash(&username, &new_hash); 130 | 131 | Ok("Updated") 132 | } 133 | -------------------------------------------------------------------------------- /src/handlers/http/modal/ingest/ingestor_role.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::{ 20 | web::{self, Json}, 21 | HttpResponse, Responder, 22 | }; 23 | 24 | use crate::{ 25 | handlers::http::{modal::utils::rbac_utils::get_metadata, role::RoleError}, 26 | rbac::{map::mut_roles, role::model::DefaultPrivilege}, 27 | storage, 28 | }; 29 | 30 | // Handler for PUT /api/v1/role/{name} 31 | // Creates a new role or update existing one 32 | pub async fn put( 33 | name: web::Path, 34 | Json(privileges): Json>, 35 | ) -> Result { 36 | let name = name.into_inner(); 37 | let mut metadata = get_metadata().await?; 38 | metadata.roles.insert(name.clone(), privileges.clone()); 39 | 40 | let _ = storage::put_staging_metadata(&metadata); 41 | mut_roles().insert(name.clone(), privileges); 42 | 43 | Ok(HttpResponse::Ok().finish()) 44 | } 45 | -------------------------------------------------------------------------------- /src/handlers/http/modal/ingest/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod ingestor_logstream; 20 | pub mod ingestor_rbac; 21 | pub mod ingestor_role; 22 | -------------------------------------------------------------------------------- /src/handlers/http/modal/query/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod querier_ingest; 20 | pub mod querier_logstream; 21 | pub mod querier_rbac; 22 | pub mod querier_role; 23 | -------------------------------------------------------------------------------- /src/handlers/http/modal/query/querier_ingest.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::HttpResponse; 20 | 21 | use crate::handlers::http::ingest::PostError; 22 | 23 | // Handler for POST /api/v1/logstream/{logstream} 24 | // only ingests events into the specified logstream 25 | // fails if the logstream does not exist 26 | #[allow(unused)] 27 | pub async fn post_event() -> Result { 28 | Err(PostError::IngestionNotAllowed) 29 | } 30 | -------------------------------------------------------------------------------- /src/handlers/http/modal/query/querier_role.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::{ 20 | web::{self, Json}, 21 | HttpResponse, Responder, 22 | }; 23 | 24 | use crate::{ 25 | handlers::http::{ 26 | cluster::sync_role_update_with_ingestors, 27 | modal::utils::rbac_utils::{get_metadata, put_metadata}, 28 | role::RoleError, 29 | }, 30 | rbac::{map::mut_roles, role::model::DefaultPrivilege}, 31 | }; 32 | 33 | // Handler for PUT /api/v1/role/{name} 34 | // Creates a new role or update existing one 35 | pub async fn put( 36 | name: web::Path, 37 | Json(privileges): Json>, 38 | ) -> Result { 39 | let name = name.into_inner(); 40 | let mut metadata = get_metadata().await?; 41 | metadata.roles.insert(name.clone(), privileges.clone()); 42 | 43 | put_metadata(&metadata).await?; 44 | mut_roles().insert(name.clone(), privileges.clone()); 45 | 46 | sync_role_update_with_ingestors(name.clone(), privileges.clone()).await?; 47 | 48 | Ok(HttpResponse::Ok().finish()) 49 | } 50 | -------------------------------------------------------------------------------- /src/handlers/http/modal/ssl_acceptor.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::{ 20 | fs::{self, File}, 21 | io::BufReader, 22 | path::PathBuf, 23 | }; 24 | 25 | use rustls::ServerConfig; 26 | 27 | pub fn get_ssl_acceptor( 28 | tls_cert: &Option, 29 | tls_key: &Option, 30 | other_certs: &Option, 31 | ) -> anyhow::Result> { 32 | match (tls_cert, tls_key) { 33 | (Some(cert), Some(key)) => { 34 | let server_config = ServerConfig::builder().with_no_client_auth(); 35 | 36 | let cert_file = &mut BufReader::new(File::open(cert)?); 37 | let key_file = &mut BufReader::new(File::open(key)?); 38 | 39 | let mut certs = rustls_pemfile::certs(cert_file).collect::, _>>()?; 40 | // Load CA certificates from the directory 41 | if let Some(other_cert_dir) = other_certs { 42 | if other_cert_dir.is_dir() { 43 | for entry in fs::read_dir(other_cert_dir)? { 44 | let path = entry.unwrap().path(); 45 | 46 | if path.is_file() { 47 | let other_cert_file = &mut BufReader::new(File::open(&path)?); 48 | let mut other_certs = rustls_pemfile::certs(other_cert_file) 49 | .collect::, _>>()?; 50 | certs.append(&mut other_certs); 51 | } 52 | } 53 | } 54 | } 55 | let private_key = rustls_pemfile::private_key(key_file)? 56 | .ok_or(anyhow::anyhow!("Could not parse private key."))?; 57 | 58 | Ok(Some(server_config.with_single_cert(certs, private_key)?)) 59 | } 60 | (_, _) => Ok(None), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/handlers/http/modal/utils/logstream_utils.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::http::header::HeaderMap; 20 | 21 | use crate::{ 22 | event::format::LogSource, 23 | handlers::{ 24 | CUSTOM_PARTITION_KEY, LOG_SOURCE_KEY, STATIC_SCHEMA_FLAG, STREAM_TYPE_KEY, 25 | TIME_PARTITION_KEY, TIME_PARTITION_LIMIT_KEY, UPDATE_STREAM_KEY, 26 | }, 27 | storage::StreamType, 28 | }; 29 | 30 | #[derive(Debug, Default)] 31 | pub struct PutStreamHeaders { 32 | pub time_partition: String, 33 | pub time_partition_limit: String, 34 | pub custom_partition: Option, 35 | pub static_schema_flag: bool, 36 | pub update_stream_flag: bool, 37 | pub stream_type: StreamType, 38 | pub log_source: LogSource, 39 | } 40 | 41 | impl From<&HeaderMap> for PutStreamHeaders { 42 | fn from(headers: &HeaderMap) -> Self { 43 | PutStreamHeaders { 44 | time_partition: headers 45 | .get(TIME_PARTITION_KEY) 46 | .map_or("", |v| v.to_str().unwrap()) 47 | .to_string(), 48 | time_partition_limit: headers 49 | .get(TIME_PARTITION_LIMIT_KEY) 50 | .map_or("", |v| v.to_str().unwrap()) 51 | .to_string(), 52 | custom_partition: headers 53 | .get(CUSTOM_PARTITION_KEY) 54 | .map(|v| v.to_str().unwrap().to_string()), 55 | static_schema_flag: headers 56 | .get(STATIC_SCHEMA_FLAG) 57 | .is_some_and(|v| v.to_str().unwrap() == "true"), 58 | update_stream_flag: headers 59 | .get(UPDATE_STREAM_KEY) 60 | .is_some_and(|v| v.to_str().unwrap() == "true"), 61 | stream_type: headers 62 | .get(STREAM_TYPE_KEY) 63 | .map(|v| StreamType::from(v.to_str().unwrap())) 64 | .unwrap_or_default(), 65 | log_source: headers 66 | .get(LOG_SOURCE_KEY) 67 | .map_or(LogSource::default(), |v| v.to_str().unwrap().into()), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/handlers/http/modal/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod ingest_utils; 20 | pub mod logstream_utils; 21 | pub mod rbac_utils; 22 | -------------------------------------------------------------------------------- /src/handlers/http/modal/utils/rbac_utils.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use crate::{ 20 | parseable::PARSEABLE, 21 | storage::{self, ObjectStorageError, StorageMetadata}, 22 | }; 23 | 24 | pub async fn get_metadata() -> Result { 25 | let metadata = PARSEABLE 26 | .storage 27 | .get_object_store() 28 | .get_metadata() 29 | .await? 30 | .expect("metadata is initialized"); 31 | Ok(metadata) 32 | } 33 | 34 | pub async fn put_metadata(metadata: &StorageMetadata) -> Result<(), ObjectStorageError> { 35 | storage::put_remote_metadata(metadata).await?; 36 | storage::put_staging_metadata(metadata)?; 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/handlers/http/prism_home.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::collections::HashMap; 20 | 21 | use actix_web::{web, HttpRequest, Responder}; 22 | 23 | use crate::{ 24 | prism::home::{generate_home_response, generate_home_search_response, PrismHomeError}, 25 | utils::actix::extract_session_key_from_req, 26 | }; 27 | 28 | const HOME_SEARCH_QUERY_PARAM: &str = "key"; 29 | 30 | /// Fetches the data to populate Prism's home 31 | /// 32 | /// 33 | /// # Returns 34 | /// 35 | /// A JSONified version of the `HomeResponse` struct. 36 | pub async fn home_api(req: HttpRequest) -> Result { 37 | let key = extract_session_key_from_req(&req) 38 | .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; 39 | 40 | let res = generate_home_response(&key).await?; 41 | 42 | Ok(web::Json(res)) 43 | } 44 | 45 | pub async fn home_search(req: HttpRequest) -> Result { 46 | let key = extract_session_key_from_req(&req) 47 | .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; 48 | let query_map = web::Query::>::from_query(req.query_string()) 49 | .map_err(|_| PrismHomeError::InvalidQueryParameter(HOME_SEARCH_QUERY_PARAM.to_string()))?; 50 | 51 | if query_map.is_empty() { 52 | return Ok(web::Json(serde_json::json!({}))); 53 | } 54 | 55 | let query_value = query_map 56 | .get(HOME_SEARCH_QUERY_PARAM) 57 | .ok_or_else(|| PrismHomeError::InvalidQueryParameter(HOME_SEARCH_QUERY_PARAM.to_string()))? 58 | .to_lowercase(); 59 | let res = generate_home_search_response(&key, &query_value).await?; 60 | let json_res = serde_json::to_value(res) 61 | .map_err(|err| PrismHomeError::Anyhow(anyhow::Error::msg(err.to_string())))?; 62 | 63 | Ok(web::Json(json_res)) 64 | } 65 | -------------------------------------------------------------------------------- /src/handlers/http/prism_logstream.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::{ 20 | web::{self, Json, Path}, 21 | HttpRequest, Responder, 22 | }; 23 | 24 | use crate::{ 25 | prism::logstream::{get_prism_logstream_info, PrismDatasetRequest, PrismLogstreamError}, 26 | utils::actix::extract_session_key_from_req, 27 | }; 28 | 29 | /// This API is essentially just combining the responses of /info and /schema together 30 | pub async fn get_info(stream_name: Path) -> Result { 31 | let prism_logstream_info = get_prism_logstream_info(&stream_name).await?; 32 | 33 | Ok(web::Json(prism_logstream_info)) 34 | } 35 | 36 | /// A combination of /stats, /retention, /hottier, /info, /counts and /query 37 | pub async fn post_datasets( 38 | dataset_req: Option>, 39 | req: HttpRequest, 40 | ) -> Result { 41 | let session_key = extract_session_key_from_req(&req)?; 42 | let dataset = dataset_req 43 | .map(|Json(r)| r) 44 | .unwrap_or_default() 45 | .get_datasets(session_key) 46 | .await?; 47 | 48 | Ok(web::Json(dataset)) 49 | } 50 | -------------------------------------------------------------------------------- /src/handlers/http/users/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod dashboards; 20 | pub mod filters; 21 | 22 | pub const USERS_ROOT_DIR: &str = ".users"; 23 | pub const DASHBOARDS_DIR: &str = "dashboards"; 24 | pub const FILTER_DIR: &str = "filters"; 25 | pub const CORRELATION_DIR: &str = "correlations"; 26 | -------------------------------------------------------------------------------- /src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod airplane; 20 | pub mod http; 21 | pub mod livetail; 22 | 23 | pub const STREAM_NAME_HEADER_KEY: &str = "x-p-stream"; 24 | const LOG_SOURCE_KEY: &str = "x-p-log-source"; 25 | const EXTRACT_LOG_KEY: &str = "x-p-extract-log"; 26 | const TIME_PARTITION_KEY: &str = "x-p-time-partition"; 27 | const TIME_PARTITION_LIMIT_KEY: &str = "x-p-time-partition-limit"; 28 | const CUSTOM_PARTITION_KEY: &str = "x-p-custom-partition"; 29 | const STATIC_SCHEMA_FLAG: &str = "x-p-static-schema-flag"; 30 | const AUTHORIZATION_KEY: &str = "authorization"; 31 | const UPDATE_STREAM_KEY: &str = "x-p-update-stream"; 32 | pub const STREAM_TYPE_KEY: &str = "x-p-stream-type"; 33 | const OIDC_SCOPE: &str = "openid profile email"; 34 | const COOKIE_AGE_DAYS: usize = 7; 35 | const SESSION_COOKIE_NAME: &str = "session"; 36 | const USER_COOKIE_NAME: &str = "username"; 37 | 38 | // constants for log Source values for known sources and formats 39 | const LOG_SOURCE_KINESIS: &str = "kinesis"; 40 | 41 | // AWS Kinesis constants 42 | const KINESIS_COMMON_ATTRIBUTES_KEY: &str = "x-amz-firehose-common-attributes"; 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod about; 20 | pub mod alerts; 21 | pub mod analytics; 22 | pub mod audit; 23 | pub mod banner; 24 | pub mod catalog; 25 | mod cli; 26 | #[cfg(feature = "kafka")] 27 | pub mod connectors; 28 | pub mod correlation; 29 | pub mod enterprise; 30 | pub mod event; 31 | pub mod handlers; 32 | pub mod hottier; 33 | mod livetail; 34 | mod metadata; 35 | pub mod metrics; 36 | pub mod migration; 37 | mod oidc; 38 | pub mod option; 39 | pub mod otel; 40 | pub mod parseable; 41 | pub mod prism; 42 | pub mod query; 43 | pub mod rbac; 44 | mod response; 45 | mod static_schema; 46 | mod stats; 47 | pub mod storage; 48 | pub mod sync; 49 | pub mod users; 50 | pub mod utils; 51 | mod validator; 52 | 53 | use std::time::Duration; 54 | 55 | pub use handlers::http::modal::{ 56 | ingest_server::IngestServer, query_server::QueryServer, server::Server, ParseableServer, 57 | }; 58 | use once_cell::sync::Lazy; 59 | use parseable::PARSEABLE; 60 | use reqwest::{Client, ClientBuilder}; 61 | 62 | // It is very unlikely that panic will occur when dealing with locks. 63 | pub const LOCK_EXPECT: &str = "Thread shouldn't panic while holding a lock"; 64 | 65 | /// Describes the duration at the end of which in-memory buffers are flushed, 66 | /// arrows files are "finished" and compacted into parquet files. 67 | pub const LOCAL_SYNC_INTERVAL: Duration = Duration::from_secs(60); 68 | 69 | /// Duration used to configure prefix generation. 70 | pub const OBJECT_STORE_DATA_GRANULARITY: u32 = LOCAL_SYNC_INTERVAL.as_secs() as u32 / 60; 71 | 72 | /// Describes the duration at the end of which parquets are pushed into objectstore. 73 | pub const STORAGE_UPLOAD_INTERVAL: Duration = Duration::from_secs(30); 74 | 75 | // A single HTTP client for all outgoing HTTP requests from the parseable server 76 | pub static HTTP_CLIENT: Lazy = Lazy::new(|| { 77 | ClientBuilder::new() 78 | .connect_timeout(Duration::from_secs(3)) // set a timeout of 3s for each connection setup 79 | .timeout(Duration::from_secs(30)) // set a timeout of 30s for each request 80 | .pool_idle_timeout(Duration::from_secs(90)) // set a timeout of 90s for each idle connection 81 | .pool_max_idle_per_host(32) // max 32 idle connections per host 82 | .gzip(true) // gzip compress for all requests 83 | .brotli(true) // brotli compress for all requests 84 | .use_rustls_tls() // use only the rustls backend 85 | .http1_only() // use only http/1.1 86 | .build() 87 | .expect("Construction of client shouldn't fail") 88 | }); 89 | 90 | //separate client is created for intra cluster communication 91 | //allow invalid certificates for connecting other nodes in the cluster 92 | //required when querier/prism server tries to connect to other nodes via IP address directly 93 | //but the certificate is valid for a specific domain name 94 | pub static INTRA_CLUSTER_CLIENT: Lazy = Lazy::new(|| { 95 | ClientBuilder::new() 96 | .connect_timeout(Duration::from_secs(3)) // set a timeout of 3s for each connection setup 97 | .timeout(Duration::from_secs(30)) // set a timeout of 30s for each request 98 | .pool_idle_timeout(Duration::from_secs(90)) // set a timeout of 90s for each idle connection 99 | .pool_max_idle_per_host(32) // max 32 idle connections per host 100 | .gzip(true) // gzip compress for all requests 101 | .brotli(true) // brotli compress for all requests 102 | .use_rustls_tls() // use only the rustls backend 103 | .http1_only() // use only http/1.1 104 | .danger_accept_invalid_certs(PARSEABLE.options.tls_skip_verify) 105 | .build() 106 | .expect("Construction of client shouldn't fail") 107 | }); 108 | -------------------------------------------------------------------------------- /src/logstream/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::sync::Arc; 20 | 21 | use arrow_schema::Schema; 22 | use http::StatusCode; 23 | 24 | use crate::{handlers::http::{logstream::error::StreamError, query::update_schema_when_distributed}, parseable::{StreamNotFound, PARSEABLE}, storage::StreamInfo, LOCK_EXPECT}; 25 | 26 | 27 | 28 | pub async fn get_stream_schema_helper(stream_name: &str) -> Result, StreamError> { 29 | // Ensure parseable is aware of stream in distributed mode 30 | if !PARSEABLE.check_or_load_stream(&stream_name).await { 31 | return Err(StreamNotFound(stream_name.to_owned()).into()); 32 | } 33 | 34 | let stream = PARSEABLE.get_stream(&stream_name)?; 35 | match update_schema_when_distributed(&vec![stream_name.to_owned()]).await { 36 | Ok(_) => { 37 | let schema = stream.get_schema(); 38 | Ok(schema) 39 | } 40 | Err(err) => Err(StreamError::Custom { 41 | msg: err.to_string(), 42 | status: StatusCode::EXPECTATION_FAILED, 43 | }), 44 | } 45 | } 46 | 47 | pub async fn get_stream_info_helper(stream_name: &str) -> Result { 48 | // For query mode, if the stream not found in memory map, 49 | //check if it exists in the storage 50 | //create stream and schema from storage 51 | if !PARSEABLE.check_or_load_stream(&stream_name).await { 52 | return Err(StreamNotFound(stream_name.to_owned()).into()); 53 | } 54 | 55 | let storage = PARSEABLE.storage.get_object_store(); 56 | // if first_event_at is not found in memory map, check if it exists in the storage 57 | // if it exists in the storage, update the first_event_at in memory map 58 | let stream_first_event_at = 59 | if let Some(first_event_at) = PARSEABLE.get_stream(&stream_name)?.get_first_event() { 60 | Some(first_event_at) 61 | } else if let Ok(Some(first_event_at)) = 62 | storage.get_first_event_from_storage(&stream_name).await 63 | { 64 | PARSEABLE 65 | .update_first_event_at(&stream_name, &first_event_at) 66 | .await 67 | } else { 68 | None 69 | }; 70 | 71 | let stream_meta = PARSEABLE.get_stream(stream_name)? 72 | .metadata 73 | .read() 74 | .expect(LOCK_EXPECT); 75 | 76 | let stream_info = StreamInfo { 77 | stream_type: stream_meta.stream_type, 78 | created_at: stream_meta.created_at.clone(), 79 | first_event_at: stream_first_event_at, 80 | time_partition: stream_meta.time_partition.clone(), 81 | time_partition_limit: stream_meta 82 | .time_partition_limit 83 | .map(|limit| limit.to_string()), 84 | custom_partition: stream_meta.custom_partition.clone(), 85 | static_schema_flag: stream_meta.static_schema_flag, 86 | log_source: stream_meta.log_source.clone(), 87 | }; 88 | 89 | Ok(stream_info) 90 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | 3 | /* 4 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU Affero General Public License as 8 | * published by the Free Software Foundation, either version 3 of the 9 | * License, or (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU Affero General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU Affero General Public License 17 | * along with this program. If not, see . 18 | * 19 | */ 20 | #[cfg(feature = "kafka")] 21 | use parseable::connectors; 22 | use parseable::{ 23 | banner, metrics, option::Mode, parseable::PARSEABLE, rbac, storage, IngestServer, 24 | ParseableServer, QueryServer, Server, 25 | }; 26 | use tokio::signal::ctrl_c; 27 | use tokio::sync::oneshot; 28 | use tracing::Level; 29 | use tracing::{info, warn}; 30 | use tracing_subscriber::layer::SubscriberExt; 31 | use tracing_subscriber::util::SubscriberInitExt; 32 | use tracing_subscriber::{fmt, EnvFilter, Registry}; 33 | 34 | #[actix_web::main] 35 | async fn main() -> anyhow::Result<()> { 36 | init_logger(); 37 | 38 | // these are empty ptrs so mem footprint should be minimal 39 | let server: Box = match &PARSEABLE.options.mode { 40 | Mode::Query => Box::new(QueryServer), 41 | Mode::Ingest => Box::new(IngestServer), 42 | Mode::Index => { 43 | println!("Indexing is an enterprise feature. Check out https://www.parseable.com/pricing to know more!"); 44 | exit(0) 45 | } 46 | Mode::Prism => { 47 | println!("Prism is an enterprise feature. Check out https://www.parseable.com/pricing to know more!"); 48 | exit(0) 49 | } 50 | Mode::All => Box::new(Server), 51 | }; 52 | 53 | // load metadata from persistence 54 | let parseable_json = server.load_metadata().await?; 55 | let metadata = storage::resolve_parseable_metadata(&parseable_json).await?; 56 | banner::print(&PARSEABLE, &metadata).await; 57 | // initialize the rbac map 58 | rbac::map::init(&metadata); 59 | // keep metadata info in mem 60 | metadata.set_global(); 61 | 62 | // Spawn a task to trigger graceful shutdown on appropriate signal 63 | let (shutdown_trigger, shutdown_rx) = oneshot::channel::<()>(); 64 | tokio::spawn(async move { 65 | block_until_shutdown_signal().await; 66 | 67 | // Trigger graceful shutdown 68 | warn!("Received shutdown signal, notifying server to shut down..."); 69 | shutdown_trigger.send(()).unwrap(); 70 | }); 71 | 72 | let prometheus = metrics::build_metrics_handler(); 73 | // Start servers 74 | #[cfg(feature = "kafka")] 75 | { 76 | let parseable_server = server.init(&prometheus, shutdown_rx); 77 | let connectors = connectors::init(&prometheus); 78 | 79 | tokio::try_join!(parseable_server, connectors)?; 80 | } 81 | 82 | #[cfg(not(feature = "kafka"))] 83 | { 84 | let parseable_server = server.init(&prometheus, shutdown_rx); 85 | parseable_server.await?; 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | pub fn init_logger() { 92 | let filter_layer = EnvFilter::try_from_default_env().unwrap_or_else(|_| { 93 | let default_level = if cfg!(debug_assertions) { 94 | Level::DEBUG 95 | } else { 96 | Level::WARN 97 | }; 98 | EnvFilter::new(default_level.to_string()) 99 | }); 100 | 101 | let fmt_layer = fmt::layer() 102 | .with_thread_names(true) 103 | .with_thread_ids(true) 104 | .with_line_number(true) 105 | .with_timer(tracing_subscriber::fmt::time::UtcTime::rfc_3339()) 106 | .with_target(true) 107 | .compact(); 108 | 109 | Registry::default() 110 | .with(filter_layer) 111 | .with(fmt_layer) 112 | .init(); 113 | } 114 | 115 | #[cfg(windows)] 116 | /// Asynchronously blocks until a shutdown signal is received 117 | pub async fn block_until_shutdown_signal() { 118 | _ = ctrl_c().await; 119 | info!("Received a CTRL+C event"); 120 | } 121 | 122 | #[cfg(unix)] 123 | /// Asynchronously blocks until a shutdown signal is received 124 | pub async fn block_until_shutdown_signal() { 125 | use tokio::signal::unix::{signal, SignalKind}; 126 | let mut sigterm = 127 | signal(SignalKind::terminate()).expect("Failed to create SIGTERM signal handler"); 128 | 129 | tokio::select! { 130 | _ = ctrl_c() => info!("Received SIGINT signal"), 131 | _ = sigterm.recv() => info!("Received SIGTERM signal"), 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/metrics/storage.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web_prometheus::PrometheusMetrics; 20 | 21 | pub trait StorageMetrics { 22 | fn register_metrics(&self, handler: &PrometheusMetrics); 23 | } 24 | 25 | pub mod localfs { 26 | use crate::{metrics::METRICS_NAMESPACE, storage::FSConfig}; 27 | use once_cell::sync::Lazy; 28 | use prometheus::{HistogramOpts, HistogramVec}; 29 | 30 | use super::StorageMetrics; 31 | 32 | pub static REQUEST_RESPONSE_TIME: Lazy = Lazy::new(|| { 33 | HistogramVec::new( 34 | HistogramOpts::new("local_fs_response_time", "FileSystem Request Latency") 35 | .namespace(METRICS_NAMESPACE), 36 | &["method", "status"], 37 | ) 38 | .expect("metric can be created") 39 | }); 40 | 41 | impl StorageMetrics for FSConfig { 42 | fn register_metrics(&self, handler: &actix_web_prometheus::PrometheusMetrics) { 43 | handler 44 | .registry 45 | .register(Box::new(REQUEST_RESPONSE_TIME.clone())) 46 | .expect("metric can be registered"); 47 | } 48 | } 49 | } 50 | 51 | pub mod s3 { 52 | use crate::{metrics::METRICS_NAMESPACE, storage::S3Config}; 53 | use once_cell::sync::Lazy; 54 | use prometheus::{HistogramOpts, HistogramVec}; 55 | 56 | use super::StorageMetrics; 57 | 58 | pub static REQUEST_RESPONSE_TIME: Lazy = Lazy::new(|| { 59 | HistogramVec::new( 60 | HistogramOpts::new("s3_response_time", "S3 Request Latency") 61 | .namespace(METRICS_NAMESPACE), 62 | &["method", "status"], 63 | ) 64 | .expect("metric can be created") 65 | }); 66 | 67 | pub static QUERY_LAYER_STORAGE_REQUEST_RESPONSE_TIME: Lazy = Lazy::new(|| { 68 | HistogramVec::new( 69 | HistogramOpts::new("query_s3_response_time", "S3 Request Latency") 70 | .namespace(METRICS_NAMESPACE), 71 | &["method", "status"], 72 | ) 73 | .expect("metric can be created") 74 | }); 75 | 76 | impl StorageMetrics for S3Config { 77 | fn register_metrics(&self, handler: &actix_web_prometheus::PrometheusMetrics) { 78 | handler 79 | .registry 80 | .register(Box::new(REQUEST_RESPONSE_TIME.clone())) 81 | .expect("metric can be registered"); 82 | handler 83 | .registry 84 | .register(Box::new(QUERY_LAYER_STORAGE_REQUEST_RESPONSE_TIME.clone())) 85 | .expect("metric can be registered"); 86 | } 87 | } 88 | } 89 | 90 | pub mod azureblob { 91 | use crate::{metrics::METRICS_NAMESPACE, storage::AzureBlobConfig}; 92 | use once_cell::sync::Lazy; 93 | use prometheus::{HistogramOpts, HistogramVec}; 94 | 95 | use super::StorageMetrics; 96 | 97 | pub static REQUEST_RESPONSE_TIME: Lazy = Lazy::new(|| { 98 | HistogramVec::new( 99 | HistogramOpts::new("azr_blob_response_time", "AzureBlob Request Latency") 100 | .namespace(METRICS_NAMESPACE), 101 | &["method", "status"], 102 | ) 103 | .expect("metric can be created") 104 | }); 105 | 106 | pub static QUERY_LAYER_STORAGE_REQUEST_RESPONSE_TIME: Lazy = Lazy::new(|| { 107 | HistogramVec::new( 108 | HistogramOpts::new("query_azr_blob_response_time", "AzureBlob Request Latency") 109 | .namespace(METRICS_NAMESPACE), 110 | &["method", "status"], 111 | ) 112 | .expect("metric can be created") 113 | }); 114 | 115 | impl StorageMetrics for AzureBlobConfig { 116 | fn register_metrics(&self, handler: &actix_web_prometheus::PrometheusMetrics) { 117 | handler 118 | .registry 119 | .register(Box::new(REQUEST_RESPONSE_TIME.clone())) 120 | .expect("metric can be registered"); 121 | handler 122 | .registry 123 | .register(Box::new(QUERY_LAYER_STORAGE_REQUEST_RESPONSE_TIME.clone())) 124 | .expect("metric can be registered"); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/migration/schema_migration.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | */ 19 | 20 | use std::collections::HashMap; 21 | 22 | use arrow_schema::{DataType, Field, Schema}; 23 | use serde_json::Value; 24 | 25 | pub(super) fn v1_v4(schema: Option) -> anyhow::Result { 26 | if let Some(schema) = schema { 27 | value_to_schema(schema) 28 | } else { 29 | Ok(Schema::empty()) 30 | } 31 | } 32 | 33 | pub(super) fn v2_v4(schemas: HashMap) -> anyhow::Result { 34 | let mut derived_schemas = Vec::new(); 35 | 36 | for value in schemas.into_values() { 37 | let schema = value_to_schema(value)?; 38 | derived_schemas.push(schema); 39 | } 40 | 41 | let schema = Schema::try_merge(derived_schemas)?; 42 | let mut fields: Vec<_> = schema.fields.iter().cloned().collect(); 43 | fields.sort_by(|a, b| a.name().cmp(b.name())); 44 | Ok(Schema::new(fields)) 45 | } 46 | 47 | fn value_to_schema(schema: Value) -> Result { 48 | let fields = schema 49 | .as_object() 50 | .expect("schema is an object") 51 | .get("fields") 52 | .expect("fields exists") 53 | .as_array() 54 | .expect("fields is an array"); 55 | 56 | let mut new_fields = Vec::new(); 57 | 58 | for field in fields { 59 | let field = field.as_object().unwrap(); 60 | let field_name: String = 61 | serde_json::from_value(field.get("name").unwrap().clone()).unwrap(); 62 | let field_dt: DataType = 63 | serde_json::from_value(field.get("data_type").unwrap().clone()).unwrap(); 64 | new_fields.push(Field::new(field_name, field_dt, true)); 65 | } 66 | new_fields.sort_by(|a, b| a.name().cmp(b.name())); 67 | 68 | Ok(Schema::new(new_fields)) 69 | } 70 | -------------------------------------------------------------------------------- /src/oidc.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::collections::HashMap; 20 | 21 | use openid::{Client, CompactJson, CustomClaims, Discovered, StandardClaims}; 22 | use url::Url; 23 | 24 | pub type DiscoveredClient = Client; 25 | 26 | // If domain is not configured then 27 | // we can assume running in a development mode or private environment 28 | #[derive(Debug, Clone)] 29 | pub enum Origin { 30 | // socket address 31 | Local { socket_addr: String, https: bool }, 32 | // domain url 33 | Production(Url), 34 | } 35 | 36 | /// Configuration for OpenID Connect 37 | #[derive(Debug, Clone)] 38 | pub struct OpenidConfig { 39 | /// Client id 40 | pub id: String, 41 | /// Client Secret 42 | pub secret: String, 43 | /// OP host address over which discovery can be done 44 | pub issuer: Url, 45 | /// Current client host address which will be used for redirects 46 | pub origin: Origin, 47 | } 48 | 49 | impl OpenidConfig { 50 | /// Create a new oidc client from server configuration. 51 | /// redirect_suffix 52 | pub async fn connect( 53 | self, 54 | redirect_to: &str, 55 | ) -> Result { 56 | let redirect_uri = match self.origin { 57 | Origin::Local { socket_addr, https } => { 58 | let protocol = if https { "https" } else { "http" }; 59 | url::Url::parse(&format!("{protocol}://{socket_addr}")).expect("valid url") 60 | } 61 | Origin::Production(url) => url, 62 | }; 63 | 64 | let redirect_uri = redirect_uri.join(redirect_to).expect("valid suffix"); 65 | DiscoveredClient::discover(self.id, self.secret, redirect_uri.to_string(), self.issuer) 66 | .await 67 | } 68 | } 69 | 70 | #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] 71 | pub struct Claims { 72 | #[serde(flatten)] 73 | pub standard: StandardClaims, 74 | #[serde(flatten)] 75 | pub other: HashMap, 76 | } 77 | 78 | impl CustomClaims for Claims { 79 | fn standard_claims(&self) -> &StandardClaims { 80 | &self.standard 81 | } 82 | } 83 | 84 | impl CompactJson for Claims {} 85 | -------------------------------------------------------------------------------- /src/otel.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod logs; 20 | pub mod metrics; 21 | pub mod otel_utils; 22 | pub mod traces; 23 | -------------------------------------------------------------------------------- /src/parseable/staging/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | */ 19 | 20 | pub mod reader; 21 | pub mod writer; 22 | 23 | #[derive(Debug, thiserror::Error)] 24 | pub enum StagingError { 25 | #[error("Unable to create recordbatch stream")] 26 | Arrow(#[from] arrow_schema::ArrowError), 27 | #[error("Could not generate parquet file")] 28 | Parquet(#[from] parquet::errors::ParquetError), 29 | #[error("IO Error {0}")] 30 | ObjectStorage(#[from] std::io::Error), 31 | #[error("Could not generate parquet file")] 32 | Create, 33 | // #[error("Metadata Error: {0}")] 34 | // Metadata(#[from] MetadataError), 35 | } 36 | -------------------------------------------------------------------------------- /src/prism/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod home; 20 | pub mod logstream; 21 | -------------------------------------------------------------------------------- /src/rbac/utils.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | use std::collections::HashMap; 19 | 20 | use url::Url; 21 | 22 | use crate::parseable::PARSEABLE; 23 | 24 | use super::{ 25 | map::roles, 26 | role::model::DefaultPrivilege, 27 | user::{User, UserType}, 28 | Users, UsersPrism, 29 | }; 30 | 31 | pub fn to_prism_user(user: &User) -> UsersPrism { 32 | let (id, method, email, picture) = match &user.ty { 33 | UserType::Native(_) => (user.username(), "native", None, None), 34 | UserType::OAuth(oauth) => ( 35 | user.username(), 36 | "oauth", 37 | oauth.user_info.email.clone(), 38 | oauth.user_info.picture.clone(), 39 | ), 40 | }; 41 | let roles: HashMap> = Users 42 | .get_role(id) 43 | .iter() 44 | .filter_map(|role_name| { 45 | roles() 46 | .get(role_name) 47 | .map(|role| (role_name.to_owned(), role.clone())) 48 | }) 49 | .collect(); 50 | 51 | UsersPrism { 52 | id: id.into(), 53 | method: method.into(), 54 | email: mask_pii_string(email), 55 | picture: mask_pii_url(picture), 56 | roles, 57 | } 58 | } 59 | 60 | //mask PII string if the P_MASK_PII is set 61 | fn mask_pii_string(input: Option) -> Option { 62 | if !PARSEABLE.options.mask_pii { 63 | return input; 64 | } 65 | mask_string(input) 66 | } 67 | 68 | //mask PII url if the P_MASK_PII is set 69 | fn mask_pii_url(input: Option) -> Option { 70 | if !PARSEABLE.options.mask_pii { 71 | return input; 72 | } 73 | None 74 | } 75 | 76 | fn mask_string(input: Option) -> Option { 77 | let input = input.as_ref()?; 78 | if input.contains('@') { 79 | // masking an email 80 | let parts: Vec<&str> = input.split('@').collect(); 81 | //mask everything if not a proper email format 82 | if parts.len() != 2 { 83 | return Some("X".repeat(input.len())); 84 | } 85 | 86 | let username = parts[0]; 87 | 88 | // Mask the username - first letter capitalized, rest are X 89 | let masked_username = if !username.is_empty() { 90 | let first = username.chars().next().unwrap().to_uppercase().to_string(); 91 | format!("{}{}", first, "X".repeat(username.len() - 1)) 92 | } else { 93 | return Some("X".repeat(input.len())); 94 | }; 95 | 96 | // mask to XXX for everything after the @ symbol 97 | Some(format!("{}@XXX", masked_username)) 98 | } else { 99 | // mask all other strings with X 100 | Some("X".repeat(input.len())) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::*; 107 | 108 | #[test] 109 | fn test_mask_string_with_email() { 110 | // Test masking a valid email 111 | let email = Some("test@example.com".to_string()); 112 | let masked_email = mask_string(email); 113 | assert_eq!(masked_email, Some("TXXX@XXX".to_string())); 114 | } 115 | 116 | #[test] 117 | fn test_mask_string_with_invalid_email() { 118 | // Test masking an invalid email 119 | let invalid_email = Some("invalid-email".to_string()); 120 | let masked_email = mask_string(invalid_email); 121 | assert_eq!(masked_email, Some("XXXXXXXXXXXXX".to_string())); 122 | } 123 | 124 | #[test] 125 | fn test_mask_string_with_empty_string() { 126 | // Test masking an empty string 127 | let empty_string = Some("".to_string()); 128 | let masked_string = mask_string(empty_string); 129 | assert_eq!(masked_string, Some("".to_string())); 130 | } 131 | 132 | #[test] 133 | fn test_mask_string_with_generic_string() { 134 | // Test masking a generic string 135 | let generic_string = Some("sensitive_data".to_string()); 136 | let masked_string = mask_string(generic_string); 137 | assert_eq!(masked_string, Some("XXXXXXXXXXXXXX".to_string())); 138 | } 139 | 140 | #[test] 141 | fn test_mask_string_with_none() { 142 | // Test masking a None value 143 | let none_value: Option = None; 144 | let masked_value = mask_string(none_value); 145 | assert_eq!(masked_value, None); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use crate::{handlers::http::query::QueryError, utils::arrow::record_batches_to_json}; 20 | use datafusion::arrow::record_batch::RecordBatch; 21 | use itertools::Itertools; 22 | use serde_json::{json, Value}; 23 | use tracing::info; 24 | 25 | pub struct QueryResponse { 26 | pub records: Vec, 27 | pub fields: Vec, 28 | pub fill_null: bool, 29 | pub with_fields: bool, 30 | } 31 | 32 | impl QueryResponse { 33 | pub fn to_json(&self) -> Result { 34 | info!("{}", "Returning query results"); 35 | let mut json_records = record_batches_to_json(&self.records)?; 36 | 37 | if self.fill_null { 38 | for map in &mut json_records { 39 | for field in &self.fields { 40 | if !map.contains_key(field) { 41 | map.insert(field.clone(), Value::Null); 42 | } 43 | } 44 | } 45 | } 46 | let values = json_records.into_iter().map(Value::Object).collect_vec(); 47 | 48 | let response = if self.with_fields { 49 | json!({ 50 | "fields": self.fields, 51 | "records": values, 52 | }) 53 | } else { 54 | Value::Array(values) 55 | }; 56 | 57 | Ok(response) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/users/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | pub mod dashboards; 20 | pub mod filters; 21 | 22 | use serde::{Deserialize, Serialize}; 23 | 24 | #[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq, Eq)] 25 | pub struct TimeFilter { 26 | to: String, 27 | from: String, 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/actix.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | * 18 | */ 19 | 20 | use actix_web::{ 21 | dev::ServiceRequest, 22 | error::{ErrorUnauthorized, ErrorUnprocessableEntity}, 23 | Error, FromRequest, HttpRequest, 24 | }; 25 | use actix_web_httpauth::extractors::basic::BasicAuth; 26 | 27 | use crate::rbac::map::SessionKey; 28 | 29 | pub fn extract_session_key(req: &mut ServiceRequest) -> Result { 30 | // Extract username and password from the request using basic auth extractor. 31 | let creds = req.extract::().into_inner(); 32 | let basic = creds.map(|creds| { 33 | let username = creds.user_id().trim().to_owned(); 34 | // password is not mandatory by basic auth standard. 35 | // If not provided then treat as empty string 36 | let password = creds.password().unwrap_or("").trim().to_owned(); 37 | SessionKey::BasicAuth { username, password } 38 | }); 39 | 40 | if let Ok(basic) = basic { 41 | Ok(basic) 42 | } else if let Some(cookie) = req.cookie("session") { 43 | let ulid = ulid::Ulid::from_string(cookie.value()) 44 | .map_err(|_| ErrorUnprocessableEntity("Cookie is tampered with or invalid"))?; 45 | Ok(SessionKey::SessionId(ulid)) 46 | } else { 47 | Err(ErrorUnauthorized("No authentication method supplied")) 48 | } 49 | } 50 | 51 | pub fn extract_session_key_from_req(req: &HttpRequest) -> Result { 52 | // Extract username and password from the request using basic auth extractor. 53 | let creds = BasicAuth::extract(req).into_inner(); 54 | let basic = creds.map(|creds| { 55 | let username = creds.user_id().trim().to_owned(); 56 | // password is not mandatory by basic auth standard. 57 | // If not provided then treat as empty string 58 | let password = creds.password().unwrap_or("").trim().to_owned(); 59 | SessionKey::BasicAuth { username, password } 60 | }); 61 | 62 | if let Ok(basic) = basic { 63 | Ok(basic) 64 | } else if let Some(cookie) = req.cookie("session") { 65 | let ulid = ulid::Ulid::from_string(cookie.value()) 66 | .map_err(|_| ErrorUnprocessableEntity("Cookie is tampered with or invalid"))?; 67 | Ok(SessionKey::SessionId(ulid)) 68 | } else { 69 | Err(ErrorUnauthorized("No authentication method supplied")) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/arrow/batch_adapter.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use datafusion::arrow::array::new_null_array; 20 | use datafusion::arrow::array::ArrayRef; 21 | use datafusion::arrow::datatypes::Schema; 22 | use datafusion::arrow::record_batch::RecordBatch; 23 | 24 | use std::sync::Arc; 25 | 26 | // This function takes a new event's record batch and the 27 | // current schema of the log stream. It returns a new record 28 | // with nulls added to the fields that don't exist 29 | // in the record batch (i.e. the event) but are present in the 30 | // log stream schema. 31 | // This is necessary because all the record batches in a log 32 | // stream need to have all the fields. 33 | pub fn adapt_batch(table_schema: &Schema, batch: &RecordBatch) -> RecordBatch { 34 | let batch_schema = &*batch.schema(); 35 | let batch_cols = batch.columns().to_vec(); 36 | 37 | let mut cols: Vec = Vec::with_capacity(table_schema.fields().len()); 38 | for table_field in table_schema.fields() { 39 | if let Some((batch_idx, _)) = batch_schema.column_with_name(table_field.name().as_str()) { 40 | cols.push(Arc::clone(&batch_cols[batch_idx])); 41 | } else { 42 | cols.push(new_null_array(table_field.data_type(), batch.num_rows())) 43 | } 44 | } 45 | 46 | let merged_schema = Arc::new(table_schema.clone()); 47 | RecordBatch::try_new(merged_schema, cols).unwrap() 48 | } 49 | -------------------------------------------------------------------------------- /src/utils/header_parsing.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use actix_web::{HttpResponse, ResponseError}; 20 | 21 | #[derive(Debug, thiserror::Error)] 22 | pub enum ParseHeaderError { 23 | #[error("Too many headers received. Limit is of 5 headers")] 24 | MaxHeadersLimitExceeded, 25 | #[error("A value passed in header can't be formatted to plain visible ASCII")] 26 | InvalidValue, 27 | #[error("Invalid Key was passed which terminated just after the end of prefix")] 28 | Emptykey, 29 | #[error("A key passed in header contains reserved char {0}")] 30 | SeperatorInKey(char), 31 | #[error("A value passed in header contains reserved char {0}")] 32 | SeperatorInValue(char), 33 | #[error("Stream name not found in header [x-p-stream]")] 34 | MissingStreamName, 35 | #[error("Log source not found in header [x-p-log-source]")] 36 | MissingLogSource, 37 | } 38 | 39 | impl ResponseError for ParseHeaderError { 40 | fn status_code(&self) -> http::StatusCode { 41 | http::StatusCode::BAD_REQUEST 42 | } 43 | 44 | fn error_response(&self) -> HttpResponse { 45 | HttpResponse::build(self.status_code()).body(self.to_string()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/human_size.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use human_size::{Any, SpecificSize}; 4 | use serde::{de, Deserialize, Deserializer, Serializer}; 5 | 6 | #[derive(Debug, thiserror::Error)] 7 | enum ParsingError { 8 | #[error("Expected 'X' | 'X Bytes', but error: {0}")] 9 | Int(#[from] std::num::ParseIntError), 10 | #[error("Could not parse given string as human size, erro: {0}")] 11 | HumanSize(#[from] human_size::ParsingError), 12 | } 13 | 14 | // Function to convert human-readable size to bytes (already provided) 15 | // NOTE: consider number values as byte count, e.g. "1234" is 1234 bytes. 16 | fn human_size_to_bytes(s: &str) -> Result { 17 | let s = s.trim(); 18 | if let Some(s) = s.strip_suffix("Bytes") { 19 | let size: u64 = s.trim().parse()?; 20 | return Ok(size); 21 | } else if let Ok(size) = s.parse() { 22 | return Ok(size); 23 | } 24 | 25 | fn parse_and_map(s: &str) -> Result { 26 | SpecificSize::::from_str(s).map(|x| x.to_bytes()) 27 | } 28 | let size = parse_and_map::(s)?; 29 | 30 | Ok(size) 31 | } 32 | 33 | // Function to convert bytes to human-readable size (already provided) 34 | pub fn bytes_to_human_size(bytes: u64) -> String { 35 | const KIB: u64 = 1024; 36 | const MIB: u64 = KIB * 1024; 37 | const GIB: u64 = MIB * 1024; 38 | const TIB: u64 = GIB * 1024; 39 | const PIB: u64 = TIB * 1024; 40 | 41 | if bytes < KIB { 42 | format!("{} B", bytes) 43 | } else if bytes < MIB { 44 | format!("{:.2} KB", bytes as f64 / KIB as f64) 45 | } else if bytes < GIB { 46 | format!("{:.2} MiB", bytes as f64 / MIB as f64) 47 | } else if bytes < TIB { 48 | format!("{:.2} GiB", bytes as f64 / GIB as f64) 49 | } else if bytes < PIB { 50 | format!("{:.2} TiB", bytes as f64 / TIB as f64) 51 | } else { 52 | format!("{:.2} PiB", bytes as f64 / PIB as f64) 53 | } 54 | } 55 | 56 | pub fn serialize(bytes: &u64, serializer: S) -> Result 57 | where 58 | S: Serializer, 59 | { 60 | // let human_readable = bytes_to_human_size(*bytes); 61 | // NOTE: frontend expects the size in bytes 62 | let human_readable = format!("{bytes} Bytes"); 63 | serializer.serialize_str(&human_readable) 64 | } 65 | 66 | pub fn deserialize<'de, D>(deserializer: D) -> Result 67 | where 68 | D: Deserializer<'de>, 69 | { 70 | let s = String::deserialize(deserializer)?; 71 | human_size_to_bytes(&s).map_err(de::Error::custom) 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn parse_numeric_input_without_unit() { 80 | assert_eq!(human_size_to_bytes("1234").unwrap(), 1234); 81 | } 82 | 83 | #[test] 84 | fn parse_bytes_string_to_bytes() { 85 | assert_eq!(human_size_to_bytes("1234 Bytes").unwrap(), 1234); 86 | } 87 | 88 | #[test] 89 | fn handle_empty_string_input() { 90 | assert!(matches!( 91 | human_size_to_bytes(""), 92 | Err(ParsingError::HumanSize(_)) 93 | )); 94 | } 95 | 96 | #[test] 97 | fn handle_byte_string_input_without_value() { 98 | assert!(matches!( 99 | human_size_to_bytes("Bytes"), 100 | Err(ParsingError::Int(_)) 101 | )); 102 | } 103 | 104 | #[test] 105 | fn convert_mebibyte_string_to_bytes() { 106 | assert_eq!(human_size_to_bytes("1 MiB").unwrap(), 1048576); 107 | } 108 | 109 | #[test] 110 | fn parse_gigabyte_string_input() { 111 | assert_eq!(human_size_to_bytes("1 GB").unwrap(), 1_000_000_000); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/utils/uid.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use ulid::Ulid; 20 | 21 | pub type Uid = Ulid; 22 | 23 | pub fn gen() -> Ulid { 24 | Ulid::new() 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/update.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Parseable Server (C) 2022 - 2024 Parseable, Inc. 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU Affero General Public License as 6 | * published by the Free Software Foundation, either version 3 of the 7 | * License, or (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU Affero General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Affero General Public License 15 | * along with this program. If not, see . 16 | * 17 | */ 18 | 19 | use std::time::Duration; 20 | 21 | use anyhow::anyhow; 22 | use chrono::{DateTime, Utc}; 23 | 24 | use crate::about; 25 | 26 | use super::uid; 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct LatestRelease { 30 | pub version: semver::Version, 31 | pub date: DateTime, 32 | } 33 | 34 | pub async fn get_latest(deployment_id: &uid::Uid) -> Result { 35 | let agent = reqwest::ClientBuilder::new() 36 | .user_agent(about::user_agent(deployment_id)) 37 | .timeout(Duration::from_secs(8)) 38 | .build() 39 | .expect("client can be built on this system"); 40 | let json: serde_json::Value = agent 41 | .get("https://download.parseable.io/latest-version") 42 | .send() 43 | .await? 44 | .json() 45 | .await?; 46 | let version = json["tag_name"] 47 | .as_str() 48 | .and_then(|ver| ver.strip_prefix('v')) 49 | .and_then(|ver| semver::Version::parse(ver).ok()) 50 | .ok_or_else(|| anyhow!("Failed parsing version"))?; 51 | let date = json["published_at"] 52 | .as_str() 53 | .ok_or_else(|| anyhow!("Failed parsing published date"))?; 54 | 55 | let date = chrono::DateTime::parse_from_rfc3339(date) 56 | .expect("date-time from github is in rfc3339 format") 57 | .into(); 58 | 59 | Ok(LatestRelease { version, date }) 60 | } 61 | -------------------------------------------------------------------------------- /systemd/README.md: -------------------------------------------------------------------------------- 1 | # Linux Systemd Service 2 | 3 | Refer the Systemd setup [documentation ↗︎](https://www.parseable.io/docs/installation/systemd) 4 | -------------------------------------------------------------------------------- /systemd/parseable.local.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Parseable 3 | Wants=network-online.target 4 | After=network-online.target 5 | AssertFileIsExecutable=/usr/local/bin/parseable 6 | 7 | [Service] 8 | WorkingDirectory=/usr/local/ 9 | 10 | User=parseable-user 11 | Group=parseable-user 12 | ProtectProc=invisible 13 | 14 | EnvironmentFile=/etc/default/parseable 15 | ExecStart=/usr/local/bin/parseable local-store 16 | 17 | # Let systemd restart this service always 18 | Restart=always 19 | 20 | # Specifies the maximum file descriptor number that can be opened by this process 21 | LimitNOFILE=1048576 22 | 23 | # Specifies the maximum number of threads this process can create 24 | TasksMax=infinity 25 | 26 | # Disable timeout logic and wait until process is stopped 27 | TimeoutStopSec=infinity 28 | SendSIGKILL=no 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | 33 | # Built for ${project.name}-${project.version} (${project.name}) 34 | -------------------------------------------------------------------------------- /systemd/parseable.s3.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Parseable 3 | Wants=network-online.target 4 | After=network-online.target 5 | AssertFileIsExecutable=/usr/local/bin/parseable 6 | 7 | [Service] 8 | WorkingDirectory=/usr/local/ 9 | 10 | User=parseable-user 11 | Group=parseable-user 12 | ProtectProc=invisible 13 | 14 | EnvironmentFile=/etc/default/parseable 15 | ExecStart=/usr/local/bin/parseable s3-store 16 | 17 | # Let systemd restart this service always 18 | Restart=always 19 | 20 | # Specifies the maximum file descriptor number that can be opened by this process 21 | LimitNOFILE=1048576 22 | 23 | # Specifies the maximum number of threads this process can create 24 | TasksMax=infinity 25 | 26 | # Disable timeout logic and wait until process is stopped 27 | TimeoutStopSec=infinity 28 | SendSIGKILL=no 29 | 30 | [Install] 31 | WantedBy=multi-user.target 32 | 33 | # Built for ${project.name}-${project.version} (${project.name}) 34 | --------------------------------------------------------------------------------