From 6e1cb343187ddd1ad6e694592ac1a3a7d5f0d14a Mon Sep 17 00:00:00 2001 From: George Date: Wed, 13 May 2026 18:36:46 +0200 Subject: [PATCH] fix: remove stale buttcrack dir, add PACK workflows and README entry - Remove buttcrack/ directory left over from rename - Add pack-build-check.yaml: fmt + clippy + cargo check on pack/** changes - Add pack-release.yaml: manual semver release with tag pack-v - Add PACK entry to repository README --- .github/workflows/pack-build-check.yaml | 51 +++++ .github/workflows/pack-release.yaml | 45 ++++ README.md | 1 + buttcrack/Cargo.toml | 17 -- buttcrack/README.md | 292 ------------------------ buttcrack/src/archive.rs | 76 ------ buttcrack/src/collector.rs | 100 -------- buttcrack/src/main.rs | 56 ----- 8 files changed, 97 insertions(+), 541 deletions(-) create mode 100644 .github/workflows/pack-build-check.yaml create mode 100644 .github/workflows/pack-release.yaml delete mode 100644 buttcrack/Cargo.toml delete mode 100644 buttcrack/README.md delete mode 100644 buttcrack/src/archive.rs delete mode 100644 buttcrack/src/collector.rs delete mode 100644 buttcrack/src/main.rs diff --git a/.github/workflows/pack-build-check.yaml b/.github/workflows/pack-build-check.yaml new file mode 100644 index 0000000..c5d76a0 --- /dev/null +++ b/.github/workflows/pack-build-check.yaml @@ -0,0 +1,51 @@ +name: "PACK: Build Check" + +on: + push: + branches: [ main ] + paths: + - 'pack/**' + - '.github/workflows/pack-build-check.yaml' + pull_request: + branches: [ main ] + paths: + - 'pack/**' + - '.github/workflows/pack-build-check.yaml' + workflow_call: + workflow_dispatch: + +jobs: + build-check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + pack/target + key: ${{ runner.os }}-cargo-${{ hashFiles('pack/Cargo.toml') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + working-directory: pack + run: cargo fmt --check + + - name: Run clippy + working-directory: pack + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Check compilation + working-directory: pack + run: cargo check --all-targets --all-features diff --git a/.github/workflows/pack-release.yaml b/.github/workflows/pack-release.yaml new file mode 100644 index 0000000..7a1e8fd --- /dev/null +++ b/.github/workflows/pack-release.yaml @@ -0,0 +1,45 @@ +name: "PACK: Release" + +on: + workflow_dispatch: + inputs: + version: + description: 'Release version' + required: true + type: string + +jobs: + build-check: + uses: ./.github/workflows/pack-build-check.yaml + + release: + needs: [ build-check ] + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4 + + - name: Validate version format + run: | + if ! echo "${{ inputs.version }}" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then + echo "Error: Version must be in semver format (e.g., 1.0.0)" + exit 1 + fi + + - name: Check tag does not already exist + run: | + if git ls-remote --tags origin | grep -q "refs/tags/pack-v${{ inputs.version }}$"; then + echo "Error: Tag pack-v${{ inputs.version }} already exists" + exit 1 + fi + + - name: Create tag and GitHub release + uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1 + with: + tag_name: pack-v${{ inputs.version }} + name: "PACK v${{ inputs.version }}" + generate_release_notes: true + make_latest: true diff --git a/README.md b/README.md index 584443f..77ed09b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ A collection of utilities used by SUSE Edge Support. |------|-------------| | ๐Ÿฆ• [nessie](./nessie/) | Log and configuration collector for SUSE Edge clusters | | ๐Ÿ“‹ [components-versions](./components-versions/) | Version reporter for Helm charts, pods, and nodes | +| ๐Ÿ“ฆ [pack](./pack/) | Path Archiver Collection Kit โ€” collects arbitrary host paths into a tar.gz for upload and investigation | --- diff --git a/buttcrack/Cargo.toml b/buttcrack/Cargo.toml deleted file mode 100644 index ef4471e..0000000 --- a/buttcrack/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "buttcrack" -version = "0.1.0" -edition = "2021" -authors = ["George Agriogiannis "] -description = "Batch Utility To Transfer Cluster Resources And Configs Kit" -license = "Apache-2.0" - -[dependencies] -anyhow = "1.0" -chrono = "0.4" -clap = { version = "4.0", features = ["derive", "env"] } -flate2 = "1.0" -tar = "0.4" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter"] } -walkdir = "2.0" diff --git a/buttcrack/README.md b/buttcrack/README.md deleted file mode 100644 index e9b252d..0000000 --- a/buttcrack/README.md +++ /dev/null @@ -1,292 +0,0 @@ -# ๐Ÿ‘ BUTTCRACK - Batch Utility To Transfer Cluster Resources And Configs Kit - -> **Don't let your cluster data slip through the cracks!** ๐Ÿ•ต๏ธ - -A blazingly fast ๐Ÿฆ€ Rust-powered tool that collects arbitrary host paths and archives them into a compressed bundle for upload and investigation. - -## โœจ Features - -๐Ÿ“ **Path Flexible** - Collect any directory or file path from the host, not just predefined ones -๐Ÿ—œ๏ธ **Compressed Archives** - Creates `.tar.gz` archives with timestamped names for easy tracking -๐Ÿ”’ **Read-Only by Design** - Input paths are always mounted read-only in container mode -๐Ÿณ **Container Ready** - Runs as a privileged container with explicit host path mounts and SELinux support -โš™๏ธ **Env Var Support** - Every flag has a `BC_*` env var equivalent for Ansible-driven automation -๐Ÿ”Œ **stack-validation Native** - Output naming and archive format designed to plug directly into existing Ansible upload pipelines -๐Ÿฆ€ **Fast & Safe** - Built with Rust for reliable, predictable behaviour under privileged execution - -## ๐Ÿš€ Quick Start - -### Prerequisites - -- ๐Ÿณ **Podman** installed on the host -- ๐Ÿ“ **Read access** to the paths you want to collect -- ๐Ÿ“ค **Write access** to the output directory - -### Quick Start with Container - -```bash -# Collect a single path -podman run --rm --privileged \ - -v /var/lib/rancher/rke2/server/db/etcd:/var/lib/rancher/rke2/server/db/etcd:ro \ - -v /tmp:/tmp \ - registry.opensuse.org/isv/suse/edge/support-tools/images/buttcrack:latest \ - --paths /var/lib/rancher/rke2/server/db/etcd \ - --output /tmp - -# Collect multiple paths -podman run --rm --privileged \ - -v /var/lib/rancher/rke2/server/db/etcd:/var/lib/rancher/rke2/server/db/etcd:ro \ - -v /var/log/pods:/var/log/pods:ro \ - -v /tmp:/tmp \ - registry.opensuse.org/isv/suse/edge/support-tools/images/buttcrack:latest \ - --paths /var/lib/rancher/rke2/server/db/etcd,/var/log/pods \ - --output /tmp -``` - -**Note:** Each input path must be explicitly bind-mounted into the container with `:ro`. The output directory must be mounted with write access. The `:Z` flag may be required on SELinux systems โ€” see the [SELinux note](#-troubleshooting) below. - -### Building Custom Container Image - -```bash -# Build the container image -podman build -t buttcrack:custom . - -# Run your custom image -podman run --rm --privileged \ - -v /var/lib/rancher/rke2/server/db/etcd:/var/lib/rancher/rke2/server/db/etcd:ro \ - -v /tmp:/tmp \ - buttcrack:custom \ - --paths /var/lib/rancher/rke2/server/db/etcd \ - --output /tmp -``` - -### Installation from Source - -**Prerequisites:** -- ๐Ÿฆ€ **Rust 1.70+** (install via [rustup](https://rustup.rs/)) - -```bash -# Clone the repository -git clone https://github.com/suse-edge/support-tools.git -cd support-tools/buttcrack - -# Build the tool -cargo build --release - -# Run it -cargo run -- --paths /var/lib/rancher/rke2/server/db/etcd --output /tmp -``` - -## ๐Ÿ“– Usage - -### Basic Usage - -```bash -# Collect a single path -buttcrack --paths /var/lib/rancher/rke2/server/db/etcd --output /tmp - -# Collect multiple paths (comma-separated) -buttcrack --paths /var/lib/rancher/rke2/server/db/etcd,/var/log/pods --output /tmp - -# Using environment variables -BC_PATHS=/var/lib/rancher/rke2/server/db/etcd BC_OUTPUT=/tmp buttcrack -``` - -### Command Line Options - -| Option | Short | Env var | Description | Default | -|--------|-------|---------|-------------|---------| -| `--paths` | `-p` | `BC_PATHS` | **Required** Comma-separated list of host paths to collect | - | -| `--output` | `-o` | `BC_OUTPUT` | Output directory for the archive | `/tmp` | -| `--verbose` | `-v` | `BC_VERBOSE` | Verbose logging | `false` | - -## ๐Ÿ“ Output Structure - -BUTTCRACK produces a single timestamped archive preserving the original directory structure of all collected paths: - -``` -# Archive written to output directory: -/tmp/buttcrack_logs_2025-11-12_14-30-00.tar.gz - -# Contents of the archive (original paths preserved): -buttcrack_logs_2025-11-12_14-30-00/ -โ”œโ”€โ”€ var/ -โ”‚ โ”œโ”€โ”€ lib/ -โ”‚ โ”‚ โ””โ”€โ”€ rancher/ -โ”‚ โ”‚ โ””โ”€โ”€ rke2/ -โ”‚ โ”‚ โ””โ”€โ”€ server/ -โ”‚ โ”‚ โ””โ”€โ”€ db/ -โ”‚ โ”‚ โ””โ”€โ”€ etcd/ -โ”‚ โ”‚ โ””โ”€โ”€ member/ -โ”‚ โ”‚ โ”œโ”€โ”€ snap/ -โ”‚ โ”‚ โ””โ”€โ”€ wal/ -โ”‚ โ””โ”€โ”€ log/ -โ”‚ โ””โ”€โ”€ pods/ -โ”‚ โ””โ”€โ”€ ... -โ””โ”€โ”€ collection-summary.yaml -``` - -The `_logs_` infix in the archive name is intentional โ€” it allows the existing `nessie_upload_logs.yaml` Ansible playbook in stack-validation to pick up BUTTCRACK archives with its `*logs*.tar.gz` glob without any changes. - -## ๐Ÿ—๏ธ Architecture - -``` -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ INPUT โ”‚ -โ”‚ โ”‚ -โ”‚ CLI flags Env vars โ”‚ -โ”‚ --paths /var/lib/etcd,... BC_PATHS โ”‚ -โ”‚ --output /tmp BC_OUTPUT โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ BUTTCRACK โ”‚ -โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ main.rs โ”‚ โ”‚ -โ”‚ โ”‚ parse args + env vars ยท orchestrate ยท logging โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ collector.rs โ”‚ โ”‚ -โ”‚ โ”‚ validate paths exist ยท walk each directory recursively โ”‚ โ”‚ -โ”‚ โ”‚ build ordered file list โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ archive.rs โ”‚ โ”‚ -โ”‚ โ”‚ stream files into GzEncoder โ†’ tar::Builder โ”‚ โ”‚ -โ”‚ โ”‚ write buttcrack_logs_.tar.gz to output dir โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - buttcrack_logs_.tar.gz - โ”‚ -โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -โ”‚ ANSIBLE / stack-validation โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ buttcrack_collect.yaml โ”‚ โ”‚ -โ”‚ โ”‚ podman run --privileged โ”‚ โ”‚ -โ”‚ โ”‚ -v /host/path:/host/path:ro (per input path) โ”‚ โ”‚ -โ”‚ โ”‚ -v /tmp:/tmp (output, rw) โ”‚ โ”‚ -โ”‚ โ”‚ โ”‚ โ”‚ -โ”‚ โ”‚ sed rename โ†’ โ”‚ โ”‚ -โ”‚ โ”‚ buttcrack__logs_ โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ -โ”‚ โ”‚ nessie_upload_logs.yaml (reused as-is) โ”‚ โ”‚ -โ”‚ โ”‚ glob: *logs*.tar.gz ยท WebDAV PUT โ”‚ โ”‚ -โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ -โ”‚ โ”‚ โ”‚ -โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ–ผ - WebDAV server - /pipelines//logs/ -``` - -## ๐Ÿ’ก Common Use Cases - -### Collect etcd Database for Investigation -```bash -podman run --rm --privileged \ - -v /var/lib/rancher/rke2/server/db/etcd:/var/lib/rancher/rke2/server/db/etcd:ro \ - -v /tmp:/tmp \ - buttcrack:latest \ - --paths /var/lib/rancher/rke2/server/db/etcd --output /tmp -``` - -### Collect from k3s Instead -```bash -podman run --rm --privileged \ - -v /var/lib/rancher/k3s/server/db/etcd:/var/lib/rancher/k3s/server/db/etcd:ro \ - -v /tmp:/tmp \ - buttcrack:latest \ - --paths /var/lib/rancher/k3s/server/db/etcd --output /tmp -``` - -### Collect Multiple Diagnostic Paths -```bash -podman run --rm --privileged \ - -v /var/lib/rancher/rke2/server/db/etcd:/var/lib/rancher/rke2/server/db/etcd:ro \ - -v /var/log/pods:/var/log/pods:ro \ - -v /tmp:/tmp \ - buttcrack:latest \ - --paths /var/lib/rancher/rke2/server/db/etcd,/var/log/pods \ - --output /tmp --verbose -``` - -## ๐Ÿ”ง Development - -### Building from Source - -```bash -# Debug build -cargo build - -# Release build (optimized) -cargo build --release - -# Run tests -cargo test - -# Format code -cargo fmt - -# Check for issues -cargo clippy -``` - -### Project Structure - -``` -src/ -โ”œโ”€โ”€ main.rs # ๐Ÿšช CLI interface and orchestration -โ”œโ”€โ”€ collector.rs # ๐Ÿ“‚ Path validation and recursive directory walking -โ””โ”€โ”€ archive.rs # ๐Ÿ—œ๏ธ tar.gz archive creation -``` - -## ๐Ÿ› Troubleshooting - -**๐Ÿšซ "Path does not exist or is not accessible"** -- Verify the path exists on the host: `ls -la /your/path` -- Ensure the path is bind-mounted into the container with the same name -- Check that the source path is not empty - -**๐Ÿ“ "Permission denied" on output** -- Ensure the output directory is mounted with write access (no `:ro`) -- Try using `/tmp` as the output directory - -**๐Ÿ”’ SELinux volume mount errors** -- Add `:Z` to volume mounts on SELinux-enforcing systems: - ```bash - -v /your/path:/your/path:ro,Z - -v /tmp:/tmp:Z - ``` - -**๐Ÿ“ฆ Archive not picked up by upload playbook** -- Verify the archive name contains `_logs_` โ€” this is required by the `nessie_upload_logs.yaml` glob -- Check the output directory matches `log_source_dir` passed to the upload playbook - -## ๐Ÿ“„ License - -This project is licensed under the Apache License 2.0 - see the [LICENSE](../LICENSE) file for details. - -This project is part of the SUSE Edge Support Tools collection. - -## ๐Ÿ™ Acknowledgments - -- ๐Ÿฆ€ Built with **Rust** for performance and safety -- ๐Ÿ”Œ Designed to integrate natively with **stack-validation** Ansible pipelines -- ๐Ÿ‘ Named after a perfectly reasonable acronym, we promise - ---- - -**Made with โค๏ธ and ๐Ÿฆ€ by the SUSE Support Team** - -*Don't let your cluster data slip through the cracks!* ๐Ÿ‘โœจ diff --git a/buttcrack/src/archive.rs b/buttcrack/src/archive.rs deleted file mode 100644 index 1463b84..0000000 --- a/buttcrack/src/archive.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::collector::CollectedEntry; -use anyhow::{Context, Result}; -use std::fs; -use tracing::info; - -pub fn create_archive( - entries: &[CollectedEntry], - source_paths: &[String], - output_dir: &str, - timestamp: &str, -) -> Result { - let archive_name = format!("{}/buttcrack_logs_{}.tar.gz", output_dir, timestamp); - let top_dir = format!("buttcrack_logs_{}", timestamp); - - info!("Creating archive: {}", archive_name); - - let tar_gz = fs::File::create(&archive_name).context("Failed to create archive file")?; - let enc = flate2::write::GzEncoder::new(tar_gz, flate2::Compression::default()); - let mut tar = tar::Builder::new(enc); - - let summary = build_summary(entries, source_paths, timestamp); - let summary_bytes = summary.as_bytes(); - let mut header = tar::Header::new_gnu(); - header.set_path(format!("{}/collection-summary.yaml", top_dir))?; - header.set_size(summary_bytes.len() as u64); - header.set_mode(0o644); - header.set_cksum(); - tar.append(&header, summary_bytes) - .context("Failed to add summary to archive")?; - - for entry in entries { - let archive_path = format!("{}/{}", top_dir, entry.archive_path); - - if entry.is_dir { - let mut header = tar::Header::new_gnu(); - header.set_path(&archive_path)?; - header.set_entry_type(tar::EntryType::Directory); - header.set_mode(0o755); - header.set_size(0); - header.set_cksum(); - tar.append(&header, &[][..]) - .context(format!("Failed to add directory: {}", archive_path))?; - } else { - tar.append_path_with_name(&entry.absolute_path, &archive_path) - .context(format!( - "Failed to add file: {}", - entry.absolute_path.display() - ))?; - } - } - - tar.finish().context("Failed to finalize archive")?; - - info!("Archive finalized: {}", archive_name); - Ok(archive_name) -} - -fn build_summary(entries: &[CollectedEntry], source_paths: &[String], timestamp: &str) -> String { - let file_count = entries.iter().filter(|e| !e.is_dir).count(); - let dir_count = entries.iter().filter(|e| e.is_dir).count(); - - let paths_yaml = source_paths - .iter() - .map(|p| format!(" - {}", p)) - .collect::>() - .join("\n"); - - format!( - "collection_info:\n timestamp: \"{}\"\n tool: buttcrack\n version: \"{}\"\n\npaths_collected:\n{}\n\ncollection_summary:\n total_files: {}\n total_directories: {}\n", - timestamp, - env!("CARGO_PKG_VERSION"), - paths_yaml, - file_count, - dir_count, - ) -} diff --git a/buttcrack/src/collector.rs b/buttcrack/src/collector.rs deleted file mode 100644 index 641261e..0000000 --- a/buttcrack/src/collector.rs +++ /dev/null @@ -1,100 +0,0 @@ -use anyhow::Result; -use std::os::unix::fs::FileTypeExt; -use std::path::PathBuf; -use tracing::{info, warn}; -use walkdir::WalkDir; - -pub struct CollectedEntry { - pub absolute_path: PathBuf, - pub archive_path: String, - pub is_dir: bool, -} - -pub fn collect_paths(paths: &[String]) -> Result> { - let mut entries = Vec::new(); - let mut total_files = 0usize; - - for path_str in paths { - let path = std::path::Path::new(path_str); - - if !path.exists() { - warn!("Path does not exist, skipping: {}", path_str); - continue; - } - - info!("Collecting: {}", path_str); - - for entry in WalkDir::new(path).follow_links(false) { - match entry { - Ok(e) => { - let ft = e.file_type(); - let path_display = e.path().display(); - - if ft.is_symlink() { - warn!( - "Skipping {}: symlink โ€” symlinks are not followed to prevent escaping the intended path or creating loops", - path_display - ); - continue; - } - - if ft.is_fifo() { - warn!( - "Skipping {}: named pipe (FIFO) โ€” reading a FIFO blocks forever waiting for a writer, skipping to avoid hang", - path_display - ); - continue; - } - - if ft.is_socket() { - warn!( - "Skipping {}: Unix socket โ€” opening a socket blocks indefinitely, skipping to avoid hang", - path_display - ); - continue; - } - - if ft.is_block_device() { - warn!( - "Skipping {}: block device โ€” raw block device reads are not supported", - path_display - ); - continue; - } - - if ft.is_char_device() { - warn!( - "Skipping {}: character device โ€” raw character device reads are not supported", - path_display - ); - continue; - } - - let is_dir = ft.is_dir(); - let archive_path = e - .path() - .to_str() - .unwrap_or("") - .trim_start_matches('/') - .to_string(); - - if !is_dir { - total_files += 1; - } - - entries.push(CollectedEntry { - absolute_path: e.path().to_path_buf(), - archive_path, - is_dir, - }); - } - Err(e) => { - warn!("Skipping inaccessible entry: {}", e); - } - } - } - } - - info!("Total files to archive: {}", total_files); - Ok(entries) -} diff --git a/buttcrack/src/main.rs b/buttcrack/src/main.rs deleted file mode 100644 index ebf975d..0000000 --- a/buttcrack/src/main.rs +++ /dev/null @@ -1,56 +0,0 @@ -use anyhow::Result; -use chrono::Local; -use clap::Parser; -use tracing::{info, warn}; - -mod archive; -mod collector; - -#[derive(Parser, Debug)] -#[command( - name = "buttcrack", - about = "Batch Utility To Transfer Cluster Resources And Configs Kit", - version -)] -struct Args { - /// Comma-separated list of host paths to collect - #[arg(short, long, env = "BC_PATHS", value_delimiter = ',', required = true)] - paths: Vec, - - /// Output directory for the archive - #[arg(short, long, env = "BC_OUTPUT", default_value = "/tmp")] - output: String, - - /// Verbose logging - #[arg(short, long, env = "BC_VERBOSE", default_value_t = false)] - verbose: bool, -} - -fn main() -> Result<()> { - let args = Args::parse(); - - let level = if args.verbose { "info" } else { "warn" }; - tracing_subscriber::fmt() - .with_env_filter(level) - .without_time() - .init(); - - let timestamp = Local::now().format("%Y-%m-%d_%H-%M-%S").to_string(); - - info!("Starting BUTTCRACK collection"); - info!("Paths: {:?}", args.paths); - info!("Output: {}", args.output); - - let entries = collector::collect_paths(&args.paths)?; - - if entries.iter().all(|e| e.is_dir) { - warn!("No files found in the provided paths - archive will be empty"); - } - - let archive_path = archive::create_archive(&entries, &args.paths, &args.output, ×tamp)?; - - info!("Archive created: {}", archive_path); - println!("{}", archive_path); - - Ok(()) -}