Compare commits
158 Commits
2cd9faf4fa
...
network_se
Author | SHA1 | Date | |
---|---|---|---|
|
da3da0ae30 | ||
|
784f87db97 | ||
|
773db2238d | ||
|
e8a369e3a2 | ||
|
4b4f3371b0 | ||
|
1bb731711b | ||
|
af89ef0149 | ||
|
768e3e176d | ||
|
aa0248ef17 | ||
|
aab2b6f128 | ||
|
d735316b7f | ||
|
d1c80863b8 | ||
|
169c62da47 | ||
|
33a5f24981 | ||
|
d7562ce466 | ||
ca736d62f3 | |||
|
078c6f723b | ||
|
9fdb8d8845 | ||
8203a3b1ff | |||
1770ac561e | |||
|
eed6dbf8dc | ||
4cd4e04028 | |||
8cc828fc0e | |||
56af312aad | |||
dfd6931c5b | |||
6e01f99958 | |||
0c02d0e99f | |||
7856fc0a4e | |||
|
758e59e921 | ||
f1806eb788 | |||
|
6e5d9b35e8 | ||
61f5331804 | |||
|
423b7bfa7e | ||
fc2830da31 | |||
|
6b12001ca2 | ||
|
99e121b0d8 | ||
|
502e345f91 | ||
|
352e846410 | ||
|
b72c50bed9 | ||
|
95122dffee | ||
|
a63cbe2bd9 | ||
|
1e4c0ac41a | ||
|
0e49be8d71 | ||
|
32339e6063 | ||
|
131d978450 | ||
|
46ad848e7e | ||
|
b4e370b668 | ||
ef8cc74d2b | |||
|
23db07b0bd | ||
b4dfa7733d | |||
|
e01b83f12a | ||
|
52f2f7e3c4 | ||
717cd7b16f | |||
|
e125bb6511 | ||
|
8012a66250 | ||
|
6dead402a2 | ||
|
c94467c205 | ||
|
b737cd6337 | ||
|
455f84528b | ||
|
3e3d0a1d45 | ||
|
511729c477 | ||
|
74217364fa | ||
|
d22fd686b7 | ||
|
c4cdb8126c | ||
|
a35edc2030 | ||
|
a7a7353aa1 | ||
|
4a8d3bfd24 | ||
|
3e617c2489 | ||
|
4d51518f31 | ||
|
e031b03e04 | ||
ba9103685f | |||
dee38eb6c2 | |||
49c879359b | |||
c0df07d6df | |||
6a1e70c484 | |||
e7e8e7daf8 | |||
8a8ead17cb | |||
0e7dba9466 | |||
f0d7636cda | |||
3a6bde02d5 | |||
3a7b323f9a | |||
66d5c8588a | |||
29a06d2bb4 | |||
|
bb39f3e3f2 | ||
|
5194f5245d | ||
a9255de679 | |||
7d05567ad2 | |||
|
c0e11c6510 | ||
|
fedf957079 | ||
|
65e404e517 | ||
944f22be23 | |||
887e66bb17 | |||
|
e5a4a1b634
|
||
|
7f55cf4fba
|
||
|
c26e0e5ad8 | ||
|
365814b424
|
||
|
cc4e087f1d | ||
|
229fef217f | ||
|
dd84ce3f48 | ||
|
7b8b8c662e | ||
|
d29a8fbb67
|
||
|
771df07c25 | ||
|
9a23c4cc09 | ||
|
2014c63b78
|
||
|
2adda10664
|
||
|
7b1908b676
|
||
|
e9b867a36e
|
||
|
78c0fd7871
|
||
|
e44ee83e74
|
||
0c425470a5 | |||
|
3e64a53a83 | ||
|
3225b3f029 | ||
|
3417e2c1ff | ||
|
7add64562e | ||
|
809599d60c | ||
|
25f2ae6fa9 | ||
|
dfe6c91273
|
||
a4438d63e0 | |||
393c4270d4 | |||
495fe92321 | |||
577d80b282 | |||
3f8aecb786 | |||
|
c7a5699798 | ||
|
3a0900fc15 | ||
|
916eabfa42 | ||
a8ed0900fd | |||
e47e163285 | |||
8aa2b2da26 | |||
992481ce1b | |||
516d0177e7 | |||
|
8285fdb7b9 | ||
|
1ebd591f19 | ||
7298645368 | |||
f669bdb84f | |||
654f91b849 | |||
619ce57776 | |||
2695b5f5f7 | |||
7828f82f58 | |||
7a346a1dd1 | |||
07390c3cae | |||
|
138dce66fa | ||
|
49e85ff8e6 | ||
|
f386890a8a | ||
663367ea57 | |||
|
114d63e590 | ||
|
22f87b320e | ||
|
f002445c9e | ||
|
61bd58498a | ||
|
d3c645e8e6 | ||
98ab2e1536 | |||
|
83662736c0 | ||
|
4897eb9133 | ||
|
1286939608 | ||
|
32217b6545 | ||
|
4578b10acb | ||
|
4e166f7750 | ||
|
d6905916ee | ||
|
76582706ab |
227
.github/workflows/publish.yml
vendored
Normal file
227
.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,227 @@
|
||||
name: Publish SAL Crates
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to publish (e.g., 0.1.0)'
|
||||
required: true
|
||||
type: string
|
||||
dry_run:
|
||||
description: 'Dry run (do not actually publish)'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish to crates.io
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cache Cargo dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Install cargo-edit for version management
|
||||
run: cargo install cargo-edit
|
||||
|
||||
- name: Set version from release tag
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
VERSION=${GITHUB_REF#refs/tags/v}
|
||||
echo "PUBLISH_VERSION=$VERSION" >> $GITHUB_ENV
|
||||
echo "Publishing version: $VERSION"
|
||||
|
||||
- name: Set version from workflow input
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
echo "PUBLISH_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
echo "Publishing version: ${{ github.event.inputs.version }}"
|
||||
|
||||
- name: Update version in all crates
|
||||
run: |
|
||||
echo "Updating version to $PUBLISH_VERSION"
|
||||
|
||||
# Update root Cargo.toml
|
||||
cargo set-version $PUBLISH_VERSION
|
||||
|
||||
# Update each crate
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai)
|
||||
for crate in "${CRATES[@]}"; do
|
||||
if [ -d "$crate" ]; then
|
||||
cd "$crate"
|
||||
cargo set-version $PUBLISH_VERSION
|
||||
cd ..
|
||||
echo "Updated $crate to version $PUBLISH_VERSION"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --workspace --verbose
|
||||
|
||||
- name: Check formatting
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Run clippy
|
||||
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||
|
||||
- name: Dry run publish (check packages)
|
||||
run: |
|
||||
echo "Checking all packages can be published..."
|
||||
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai)
|
||||
for crate in "${CRATES[@]}"; do
|
||||
if [ -d "$crate" ]; then
|
||||
echo "Checking $crate..."
|
||||
cd "$crate"
|
||||
cargo publish --dry-run
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Checking main crate..."
|
||||
cargo publish --dry-run
|
||||
|
||||
- name: Publish crates (dry run)
|
||||
if: github.event.inputs.dry_run == 'true'
|
||||
run: |
|
||||
echo "🔍 DRY RUN MODE - Would publish the following crates:"
|
||||
echo "Individual crates: sal-os, sal-process, sal-text, sal-net, sal-git, sal-vault, sal-kubernetes, sal-virt, sal-redisclient, sal-postgresclient, sal-zinit-client, sal-mycelium, sal-rhai"
|
||||
echo "Meta-crate: sal"
|
||||
echo "Version: $PUBLISH_VERSION"
|
||||
|
||||
- name: Publish individual crates
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
echo "Publishing individual crates..."
|
||||
|
||||
# Crates in dependency order
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai)
|
||||
|
||||
for crate in "${CRATES[@]}"; do
|
||||
if [ -d "$crate" ]; then
|
||||
echo "Publishing sal-$crate..."
|
||||
cd "$crate"
|
||||
|
||||
# Retry logic for transient failures
|
||||
for attempt in 1 2 3; do
|
||||
if cargo publish --token $CARGO_REGISTRY_TOKEN; then
|
||||
echo "✅ sal-$crate published successfully"
|
||||
break
|
||||
else
|
||||
if [ $attempt -eq 3 ]; then
|
||||
echo "❌ Failed to publish sal-$crate after 3 attempts"
|
||||
exit 1
|
||||
else
|
||||
echo "⚠️ Attempt $attempt failed, retrying in 30 seconds..."
|
||||
sleep 30
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
cd ..
|
||||
|
||||
# Wait for crates.io to process
|
||||
if [ "$crate" != "rhai" ]; then
|
||||
echo "⏳ Waiting 30 seconds for crates.io to process..."
|
||||
sleep 30
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Publish main crate
|
||||
if: github.event.inputs.dry_run != 'true'
|
||||
env:
|
||||
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
run: |
|
||||
echo "Publishing main sal crate..."
|
||||
|
||||
# Wait a bit longer before publishing the meta-crate
|
||||
echo "⏳ Waiting 60 seconds for all individual crates to be available..."
|
||||
sleep 60
|
||||
|
||||
# Retry logic for the main crate
|
||||
for attempt in 1 2 3; do
|
||||
if cargo publish --token $CARGO_REGISTRY_TOKEN; then
|
||||
echo "✅ Main sal crate published successfully"
|
||||
break
|
||||
else
|
||||
if [ $attempt -eq 3 ]; then
|
||||
echo "❌ Failed to publish main sal crate after 3 attempts"
|
||||
exit 1
|
||||
else
|
||||
echo "⚠️ Attempt $attempt failed, retrying in 60 seconds..."
|
||||
sleep 60
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "## 📦 SAL Publishing Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Version:** $PUBLISH_VERSION" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then
|
||||
echo "**Mode:** Dry Run" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Mode:** Live Publishing" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Published Crates" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-os" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-process" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-text" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-net" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-git" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-vault" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-kubernetes" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-virt" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-redisclient" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-postgresclient" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-zinit-client" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-mycelium" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal-rhai" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- sal (meta-crate)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Usage" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```bash' >> $GITHUB_STEP_SUMMARY
|
||||
echo "# Individual crates" >> $GITHUB_STEP_SUMMARY
|
||||
echo "cargo add sal-os sal-process sal-text" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "# Meta-crate with features" >> $GITHUB_STEP_SUMMARY
|
||||
echo "cargo add sal --features core" >> $GITHUB_STEP_SUMMARY
|
||||
echo "cargo add sal --features all" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
73
.github/workflows/rhai-tests.yml
vendored
Normal file
73
.github/workflows/rhai-tests.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Rhai Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
paths:
|
||||
- 'src/rhai_tests/**'
|
||||
- 'src/rhai/**'
|
||||
- 'src/git/**'
|
||||
- 'src/os/**'
|
||||
- 'run_rhai_tests.sh'
|
||||
- '.github/workflows/rhai-tests.yml'
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
paths:
|
||||
- 'src/rhai_tests/**'
|
||||
- 'src/rhai/**'
|
||||
- 'src/git/**'
|
||||
- 'src/os/**'
|
||||
- 'run_rhai_tests.sh'
|
||||
- '.github/workflows/rhai-tests.yml'
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
jobs:
|
||||
rhai-tests:
|
||||
name: Run Rhai Tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
|
||||
- name: Cache Rust dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Build herodo
|
||||
run: |
|
||||
cargo build --bin herodo
|
||||
echo "${{ github.workspace }}/target/debug" >> $GITHUB_PATH
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y git curl
|
||||
|
||||
- name: Run Rhai tests
|
||||
run: |
|
||||
chmod +x run_rhai_tests.sh
|
||||
./run_rhai_tests.sh
|
||||
|
||||
- name: Check for test failures
|
||||
run: |
|
||||
if grep -q "Some tests failed" run_rhai_tests.log; then
|
||||
echo "::error::Some Rhai tests failed. Check the logs for details."
|
||||
exit 1
|
||||
else
|
||||
echo "All Rhai tests passed!"
|
||||
fi
|
||||
if: always()
|
233
.github/workflows/test-publish.yml
vendored
Normal file
233
.github/workflows/test-publish.yml
vendored
Normal file
@@ -0,0 +1,233 @@
|
||||
name: Test Publishing Setup
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- 'scripts/publish-all.sh'
|
||||
- '.github/workflows/publish.yml'
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
paths:
|
||||
- '**/Cargo.toml'
|
||||
- 'scripts/publish-all.sh'
|
||||
- '.github/workflows/publish.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
test-publish-setup:
|
||||
name: Test Publishing Setup
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Cache Cargo dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
key: ${{ runner.os }}-cargo-publish-test-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cargo-publish-test-
|
||||
${{ runner.os }}-cargo-
|
||||
|
||||
- name: Install cargo-edit
|
||||
run: cargo install cargo-edit
|
||||
|
||||
- name: Test workspace structure
|
||||
run: |
|
||||
echo "Testing workspace structure..."
|
||||
|
||||
# Check that all expected crates exist
|
||||
EXPECTED_CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai herodo)
|
||||
|
||||
for crate in "${EXPECTED_CRATES[@]}"; do
|
||||
if [ -d "$crate" ] && [ -f "$crate/Cargo.toml" ]; then
|
||||
echo "✅ $crate exists"
|
||||
else
|
||||
echo "❌ $crate missing or invalid"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test feature configuration
|
||||
run: |
|
||||
echo "Testing feature configuration..."
|
||||
|
||||
# Test that features work correctly
|
||||
cargo check --features os
|
||||
cargo check --features process
|
||||
cargo check --features text
|
||||
cargo check --features net
|
||||
cargo check --features git
|
||||
cargo check --features vault
|
||||
cargo check --features kubernetes
|
||||
cargo check --features virt
|
||||
cargo check --features redisclient
|
||||
cargo check --features postgresclient
|
||||
cargo check --features zinit_client
|
||||
cargo check --features mycelium
|
||||
cargo check --features rhai
|
||||
|
||||
echo "✅ All individual features work"
|
||||
|
||||
# Test feature groups
|
||||
cargo check --features core
|
||||
cargo check --features clients
|
||||
cargo check --features infrastructure
|
||||
cargo check --features scripting
|
||||
|
||||
echo "✅ All feature groups work"
|
||||
|
||||
# Test all features
|
||||
cargo check --features all
|
||||
|
||||
echo "✅ All features together work"
|
||||
|
||||
- name: Test dry-run publishing
|
||||
run: |
|
||||
echo "Testing dry-run publishing..."
|
||||
|
||||
# Test each individual crate can be packaged
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai)
|
||||
|
||||
for crate in "${CRATES[@]}"; do
|
||||
echo "Testing sal-$crate..."
|
||||
cd "$crate"
|
||||
cargo publish --dry-run
|
||||
cd ..
|
||||
echo "✅ sal-$crate can be published"
|
||||
done
|
||||
|
||||
# Test main crate
|
||||
echo "Testing main sal crate..."
|
||||
cargo publish --dry-run
|
||||
echo "✅ Main sal crate can be published"
|
||||
|
||||
- name: Test publishing script
|
||||
run: |
|
||||
echo "Testing publishing script..."
|
||||
|
||||
# Make script executable
|
||||
chmod +x scripts/publish-all.sh
|
||||
|
||||
# Test dry run
|
||||
./scripts/publish-all.sh --dry-run --version 0.1.0-test
|
||||
|
||||
echo "✅ Publishing script works"
|
||||
|
||||
- name: Test version consistency
|
||||
run: |
|
||||
echo "Testing version consistency..."
|
||||
|
||||
# Get version from root Cargo.toml
|
||||
ROOT_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
|
||||
echo "Root version: $ROOT_VERSION"
|
||||
|
||||
# Check all crates have the same version
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai herodo)
|
||||
|
||||
for crate in "${CRATES[@]}"; do
|
||||
if [ -f "$crate/Cargo.toml" ]; then
|
||||
CRATE_VERSION=$(grep '^version = ' "$crate/Cargo.toml" | head -1 | sed 's/version = "\(.*\)"/\1/')
|
||||
if [ "$CRATE_VERSION" = "$ROOT_VERSION" ]; then
|
||||
echo "✅ $crate version matches: $CRATE_VERSION"
|
||||
else
|
||||
echo "❌ $crate version mismatch: $CRATE_VERSION (expected $ROOT_VERSION)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test metadata completeness
|
||||
run: |
|
||||
echo "Testing metadata completeness..."
|
||||
|
||||
# Check that all crates have required metadata
|
||||
CRATES=(os process text net git vault kubernetes virt redisclient postgresclient zinit_client mycelium rhai)
|
||||
|
||||
for crate in "${CRATES[@]}"; do
|
||||
echo "Checking sal-$crate metadata..."
|
||||
cd "$crate"
|
||||
|
||||
# Check required fields exist
|
||||
if ! grep -q '^name = "sal-' Cargo.toml; then
|
||||
echo "❌ $crate missing or incorrect name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q '^description = ' Cargo.toml; then
|
||||
echo "❌ $crate missing description"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q '^repository = ' Cargo.toml; then
|
||||
echo "❌ $crate missing repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q '^license = ' Cargo.toml; then
|
||||
echo "❌ $crate missing license"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ sal-$crate metadata complete"
|
||||
cd ..
|
||||
done
|
||||
|
||||
- name: Test dependency resolution
|
||||
run: |
|
||||
echo "Testing dependency resolution..."
|
||||
|
||||
# Test that all workspace dependencies resolve correctly
|
||||
cargo tree --workspace > /dev/null
|
||||
echo "✅ All dependencies resolve correctly"
|
||||
|
||||
# Test that there are no dependency conflicts
|
||||
cargo check --workspace
|
||||
echo "✅ No dependency conflicts"
|
||||
|
||||
- name: Generate publishing report
|
||||
if: always()
|
||||
run: |
|
||||
echo "## 🧪 Publishing Setup Test Report" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### ✅ Tests Passed" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Workspace structure validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Feature configuration testing" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Dry-run publishing simulation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Publishing script validation" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Version consistency check" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Metadata completeness verification" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- Dependency resolution testing" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### 📦 Ready for Publishing" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "All SAL crates are ready for publishing to crates.io!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Individual Crates:** 13 modules" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Meta-crate:** sal with optional features" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Binary:** herodo script executor" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### 🚀 Next Steps" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "1. Create a release tag (e.g., v0.1.0)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "2. The publish workflow will automatically trigger" >> $GITHUB_STEP_SUMMARY
|
||||
echo "3. All crates will be published to crates.io" >> $GITHUB_STEP_SUMMARY
|
||||
echo "4. Users can install with: \`cargo add sal-os\` or \`cargo add sal --features all\`" >> $GITHUB_STEP_SUMMARY
|
45
.gitignore
vendored
45
.gitignore
vendored
@@ -19,3 +19,48 @@ Cargo.lock
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
/rhai_test_template
|
||||
/rhai_test_download
|
||||
/rhai_test_fs
|
||||
run_rhai_tests.log
|
||||
new_location
|
||||
log.txt
|
||||
file.txt
|
||||
fix_doc*
|
||||
|
||||
# Dependencies
|
||||
/node_modules
|
||||
|
||||
# Production
|
||||
/build
|
||||
|
||||
# Generated files
|
||||
.docusaurus
|
||||
.cache-loader
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
bun.lockb
|
||||
bun.lock
|
||||
|
||||
yarn.lock
|
||||
|
||||
build.sh
|
||||
build_dev.sh
|
||||
develop.sh
|
||||
|
||||
docusaurus.config.ts
|
||||
|
||||
sidebars.ts
|
||||
|
||||
tsconfig.json
|
||||
Cargo.toml.bak
|
||||
for_augment
|
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"gitea": {
|
||||
"command": "/Users/despiegk/hero/bin/mcpgitea",
|
||||
"args": [
|
||||
"-t", "stdio",
|
||||
"--host", "https://gitea.com",
|
||||
"--token", "5bd13c898368a2edbfcef43f898a34857b51b37a"
|
||||
],
|
||||
"env": {
|
||||
"GITEA_HOST": "https://git.ourworld.tf/",
|
||||
"GITEA_ACCESS_TOKEN": "5bd13c898368a2edbfcef43f898a34857b51b37a"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
210
Cargo.toml
210
Cargo.toml
@@ -4,40 +4,196 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["PlanetFirst <info@incubaid.com>"]
|
||||
description = "System Abstraction Layer - A library for easy interaction with operating system features"
|
||||
repository = "https://git.ourworld.tf/herocode/sal"
|
||||
repository = "https://git.threefold.info/herocode/sal"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["system", "os", "abstraction", "platform", "filesystem"]
|
||||
categories = ["os", "filesystem", "api-bindings"]
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
tera = "1.19.0" # Template engine for text rendering
|
||||
# Cross-platform functionality
|
||||
[workspace]
|
||||
members = [
|
||||
"packages/clients/myceliumclient",
|
||||
"packages/clients/postgresclient",
|
||||
"packages/clients/redisclient",
|
||||
"packages/clients/zinitclient",
|
||||
"packages/core/net",
|
||||
"packages/core/text",
|
||||
"packages/crypt/vault",
|
||||
"packages/data/ourdb",
|
||||
"packages/data/radixtree",
|
||||
"packages/data/tst",
|
||||
"packages/system/git",
|
||||
"packages/system/kubernetes",
|
||||
"packages/system/os",
|
||||
"packages/system/process",
|
||||
"packages/system/virt",
|
||||
"rhai",
|
||||
"rhailib",
|
||||
"herodo",
|
||||
"packages/clients/hetznerclient",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.metadata]
|
||||
# Workspace-level metadata
|
||||
rust-version = "1.70.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Core shared dependencies with consistent versions
|
||||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
dirs = "6.0.0"
|
||||
env_logger = "0.11.8"
|
||||
futures = "0.3.30"
|
||||
glob = "0.3.1"
|
||||
lazy_static = "1.4.0"
|
||||
libc = "0.2"
|
||||
cfg-if = "1.0"
|
||||
thiserror = "1.0" # For error handling
|
||||
redis = "0.22.0" # Redis client
|
||||
lazy_static = "1.4.0" # For lazy initialization of static variables
|
||||
regex = "1.8.1" # For regex pattern matching
|
||||
serde = { version = "1.0", features = ["derive"] } # For serialization/deserialization
|
||||
serde_json = "1.0" # For JSON handling
|
||||
glob = "0.3.1" # For file pattern matching
|
||||
tempfile = "3.5" # For temporary file operations
|
||||
log = "0.4" # Logging facade
|
||||
rhai = { version = "1.12.0", features = ["sync"] } # Embedded scripting language
|
||||
rand = "0.8.5" # Random number generation
|
||||
clap = "2.33" # Command-line argument parsing
|
||||
log = "0.4"
|
||||
once_cell = "1.18.0"
|
||||
rand = "0.8.5"
|
||||
regex = "1.8.1"
|
||||
reqwest = { version = "0.12.15", features = ["json", "blocking"] }
|
||||
rhai = { version = "1.12.0", features = ["sync"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tempfile = "3.5"
|
||||
thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.0", features = ["full"] }
|
||||
url = "2.4"
|
||||
uuid = { version = "1.16.0", features = ["v4"] }
|
||||
|
||||
# Optional features for specific OS functionality
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
nix = "0.26" # Unix-specific functionality
|
||||
# Database dependencies
|
||||
postgres = "0.19.10"
|
||||
r2d2_postgres = "0.18.2"
|
||||
redis = "0.31.0"
|
||||
tokio-postgres = "0.7.13"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows = { version = "0.48", features = ["Win32_Foundation", "Win32_System_Threading", "Win32_Storage_FileSystem"] }
|
||||
# Crypto dependencies
|
||||
chacha20poly1305 = "0.10.1"
|
||||
k256 = { version = "0.13.4", features = ["ecdsa", "ecdh"] }
|
||||
sha2 = "0.10.7"
|
||||
hex = "0.4"
|
||||
bincode = { version = "2.0.1", features = ["serde"] }
|
||||
pbkdf2 = "0.12.2"
|
||||
getrandom = { version = "0.3.3", features = ["wasm_js"] }
|
||||
tera = "1.19.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.5" # For tests that need temporary files/directories
|
||||
# Ethereum dependencies
|
||||
ethers = { version = "2.0.7", features = ["legacy"] }
|
||||
|
||||
[[bin]]
|
||||
name = "herodo"
|
||||
path = "src/bin/herodo.rs"
|
||||
# Platform-specific dependencies
|
||||
nix = "0.30.1"
|
||||
windows = { version = "0.61.1", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Threading",
|
||||
"Win32_Storage_FileSystem",
|
||||
] }
|
||||
|
||||
# Specialized dependencies
|
||||
zinit-client = "0.4.0"
|
||||
urlencoding = "2.1.3"
|
||||
tokio-test = "0.4.4"
|
||||
kube = { version = "0.95.0", features = ["client", "config", "derive"] }
|
||||
k8s-openapi = { version = "0.23.0", features = ["latest"] }
|
||||
tokio-retry = "0.3.0"
|
||||
governor = "0.6.3"
|
||||
tower = { version = "0.5.2", features = ["timeout", "limit"] }
|
||||
serde_yaml = "0.9"
|
||||
postgres-types = "0.2.5"
|
||||
r2d2 = "0.8.10"
|
||||
|
||||
# SAL dependencies
|
||||
sal-git = { path = "packages/system/git" }
|
||||
sal-kubernetes = { path = "packages/system/kubernetes" }
|
||||
sal-redisclient = { path = "packages/clients/redisclient" }
|
||||
sal-mycelium = { path = "packages/clients/myceliumclient" }
|
||||
sal-hetzner = { path = "packages/clients/hetznerclient" }
|
||||
sal-text = { path = "packages/core/text" }
|
||||
sal-os = { path = "packages/system/os" }
|
||||
sal-net = { path = "packages/core/net" }
|
||||
sal-zinit-client = { path = "packages/clients/zinitclient" }
|
||||
sal-process = { path = "packages/system/process" }
|
||||
sal-virt = { path = "packages/system/virt" }
|
||||
sal-postgresclient = { path = "packages/clients/postgresclient" }
|
||||
sal-vault = { path = "packages/crypt/vault" }
|
||||
sal-rhai = { path = "rhai" }
|
||||
sal-service-manager = { path = "_archive/service_manager" }
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
# Optional dependencies - users can choose which modules to include
|
||||
sal-git = { workspace = true, optional = true }
|
||||
sal-kubernetes = { workspace = true, optional = true }
|
||||
sal-redisclient = { workspace = true, optional = true }
|
||||
sal-mycelium = { workspace = true, optional = true }
|
||||
sal-hetzner = { workspace = true, optional = true }
|
||||
sal-text = { workspace = true, optional = true }
|
||||
sal-os = { workspace = true, optional = true }
|
||||
sal-net = { workspace = true, optional = true }
|
||||
sal-zinit-client = { workspace = true, optional = true }
|
||||
sal-process = { workspace = true, optional = true }
|
||||
sal-virt = { workspace = true, optional = true }
|
||||
sal-postgresclient = { workspace = true, optional = true }
|
||||
sal-vault = { workspace = true, optional = true }
|
||||
sal-rhai = { workspace = true, optional = true }
|
||||
sal-service-manager = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# Individual module features
|
||||
git = ["dep:sal-git"]
|
||||
kubernetes = ["dep:sal-kubernetes"]
|
||||
redisclient = ["dep:sal-redisclient"]
|
||||
mycelium = ["dep:sal-mycelium"]
|
||||
hetzner = ["dep:sal-hetzner"]
|
||||
text = ["dep:sal-text"]
|
||||
os = ["dep:sal-os"]
|
||||
net = ["dep:sal-net"]
|
||||
zinit_client = ["dep:sal-zinit-client"]
|
||||
process = ["dep:sal-process"]
|
||||
virt = ["dep:sal-virt"]
|
||||
postgresclient = ["dep:sal-postgresclient"]
|
||||
vault = ["dep:sal-vault"]
|
||||
rhai = ["dep:sal-rhai"]
|
||||
# service_manager is removed as it's not a direct member anymore
|
||||
|
||||
# Convenience feature groups
|
||||
core = ["os", "process", "text", "net"]
|
||||
clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner"]
|
||||
infrastructure = ["git", "vault", "kubernetes", "virt"]
|
||||
scripting = ["rhai"]
|
||||
all = [
|
||||
"git",
|
||||
"kubernetes",
|
||||
"redisclient",
|
||||
"mycelium",
|
||||
"hetzner",
|
||||
"text",
|
||||
"os",
|
||||
"net",
|
||||
"zinit_client",
|
||||
"process",
|
||||
"virt",
|
||||
"postgresclient",
|
||||
"vault",
|
||||
"rhai",
|
||||
]
|
||||
|
||||
# Examples
|
||||
[[example]]
|
||||
name = "postgres_cluster"
|
||||
path = "examples/kubernetes/clusters/postgres.rs"
|
||||
required-features = ["kubernetes"]
|
||||
|
||||
[[example]]
|
||||
name = "redis_cluster"
|
||||
path = "examples/kubernetes/clusters/redis.rs"
|
||||
required-features = ["kubernetes"]
|
||||
|
||||
[[example]]
|
||||
name = "generic_cluster"
|
||||
path = "examples/kubernetes/clusters/generic.rs"
|
||||
required-features = ["kubernetes"]
|
||||
|
239
PUBLISHING.md
Normal file
239
PUBLISHING.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# SAL Publishing Guide
|
||||
|
||||
This guide explains how to publish SAL crates to crates.io and how users can consume them.
|
||||
|
||||
## 🎯 Publishing Strategy
|
||||
|
||||
SAL uses a **modular publishing approach** where each module is published as an individual crate. This allows users to install only the functionality they need, reducing compilation time and binary size.
|
||||
|
||||
## 📦 Crate Structure
|
||||
|
||||
### Individual Crates
|
||||
|
||||
Each SAL module is published as a separate crate:
|
||||
|
||||
| Crate Name | Description | Category |
|
||||
|------------|-------------|----------|
|
||||
| `sal-os` | Operating system operations | Core |
|
||||
| `sal-process` | Process management | Core |
|
||||
| `sal-text` | Text processing utilities | Core |
|
||||
| `sal-net` | Network operations | Core |
|
||||
| `sal-git` | Git repository management | Infrastructure |
|
||||
| `sal-vault` | Cryptographic operations | Infrastructure |
|
||||
| `sal-kubernetes` | Kubernetes cluster management | Infrastructure |
|
||||
| `sal-virt` | Virtualization tools (Buildah, nerdctl) | Infrastructure |
|
||||
| `sal-redisclient` | Redis database client | Clients |
|
||||
| `sal-postgresclient` | PostgreSQL database client | Clients |
|
||||
| `sal-zinit-client` | Zinit process supervisor client | Clients |
|
||||
| `sal-mycelium` | Mycelium network client | Clients |
|
||||
| `sal-rhai` | Rhai scripting integration | Scripting |
|
||||
|
||||
### Meta-crate
|
||||
|
||||
The main `sal` crate serves as a meta-crate that re-exports all modules with optional features:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sal = { version = "0.1.0", features = ["os", "process", "text"] }
|
||||
```
|
||||
|
||||
## 🚀 Publishing Process
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Crates.io Account**: Ensure you have a crates.io account and API token
|
||||
2. **Repository Access**: Ensure the repository URL is accessible
|
||||
3. **Version Consistency**: All crates should use the same version number
|
||||
|
||||
### Publishing Individual Crates
|
||||
|
||||
Each crate can be published independently:
|
||||
|
||||
```bash
|
||||
# Publish core modules
|
||||
cd os && cargo publish
|
||||
cd ../process && cargo publish
|
||||
cd ../text && cargo publish
|
||||
cd ../net && cargo publish
|
||||
|
||||
# Publish infrastructure modules
|
||||
cd ../git && cargo publish
|
||||
cd ../vault && cargo publish
|
||||
cd ../kubernetes && cargo publish
|
||||
cd ../virt && cargo publish
|
||||
|
||||
# Publish client modules
|
||||
cd ../redisclient && cargo publish
|
||||
cd ../postgresclient && cargo publish
|
||||
cd ../zinit_client && cargo publish
|
||||
cd ../mycelium && cargo publish
|
||||
|
||||
# Publish scripting module
|
||||
cd ../rhai && cargo publish
|
||||
|
||||
# Finally, publish the meta-crate
|
||||
cd .. && cargo publish
|
||||
```
|
||||
|
||||
### Automated Publishing
|
||||
|
||||
Use the comprehensive publishing script:
|
||||
|
||||
```bash
|
||||
# Test the publishing process (safe)
|
||||
./scripts/publish-all.sh --dry-run --version 0.1.0
|
||||
|
||||
# Actually publish to crates.io
|
||||
./scripts/publish-all.sh --version 0.1.0
|
||||
```
|
||||
|
||||
The script handles:
|
||||
- ✅ **Dependency order** - Publishes crates in correct dependency order
|
||||
- ✅ **Path dependencies** - Automatically updates path deps to version deps
|
||||
- ✅ **Rate limiting** - Waits between publishes to avoid rate limits
|
||||
- ✅ **Error handling** - Stops on failures with clear error messages
|
||||
- ✅ **Dry run mode** - Test without actually publishing
|
||||
|
||||
## 👥 User Consumption
|
||||
|
||||
### Installation Options
|
||||
|
||||
#### Option 1: Individual Crates (Recommended)
|
||||
|
||||
Users install only what they need:
|
||||
|
||||
```bash
|
||||
# Core functionality
|
||||
cargo add sal-os sal-process sal-text sal-net
|
||||
|
||||
# Database operations
|
||||
cargo add sal-redisclient sal-postgresclient
|
||||
|
||||
# Infrastructure management
|
||||
cargo add sal-git sal-vault sal-kubernetes
|
||||
|
||||
# Service integration
|
||||
cargo add sal-zinit-client sal-mycelium
|
||||
|
||||
# Scripting
|
||||
cargo add sal-rhai
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```rust
|
||||
use sal_os::fs;
|
||||
use sal_process::run;
|
||||
use sal_git::GitManager;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let files = fs::list_files(".")?;
|
||||
let result = run::command("echo hello")?;
|
||||
let git = GitManager::new(".")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
#### Option 2: Meta-crate with Features
|
||||
|
||||
Users can use the main crate with selective features:
|
||||
|
||||
```bash
|
||||
# Specific modules
|
||||
cargo add sal --features os,process,text
|
||||
|
||||
# Feature groups
|
||||
cargo add sal --features core # os, process, text, net
|
||||
cargo add sal --features clients # redisclient, postgresclient, zinit_client, mycelium
|
||||
cargo add sal --features infrastructure # git, vault, kubernetes, virt
|
||||
cargo add sal --features scripting # rhai
|
||||
|
||||
# Everything
|
||||
cargo add sal --features all
|
||||
```
|
||||
|
||||
**Usage:**
|
||||
```rust
|
||||
// Cargo.toml: sal = { version = "0.1.0", features = ["os", "process", "git"] }
|
||||
use sal::os::fs;
|
||||
use sal::process::run;
|
||||
use sal::git::GitManager;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let files = fs::list_files(".")?;
|
||||
let result = run::command("echo hello")?;
|
||||
let git = GitManager::new(".")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Feature Groups
|
||||
|
||||
The meta-crate provides convenient feature groups:
|
||||
|
||||
- **`core`**: Essential system operations (os, process, text, net)
|
||||
- **`clients`**: Database and service clients (redisclient, postgresclient, zinit_client, mycelium)
|
||||
- **`infrastructure`**: Infrastructure management tools (git, vault, kubernetes, virt)
|
||||
- **`scripting`**: Rhai scripting support (rhai)
|
||||
- **`all`**: Everything included
|
||||
|
||||
## 📋 Version Management
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
All SAL crates follow semantic versioning:
|
||||
|
||||
- **Major version**: Breaking API changes
|
||||
- **Minor version**: New features, backward compatible
|
||||
- **Patch version**: Bug fixes, backward compatible
|
||||
|
||||
### Synchronized Releases
|
||||
|
||||
All crates are released with the same version number to ensure compatibility:
|
||||
|
||||
```toml
|
||||
# All crates use the same version
|
||||
sal-os = "0.1.0"
|
||||
sal-process = "0.1.0"
|
||||
sal-git = "0.1.0"
|
||||
# etc.
|
||||
```
|
||||
|
||||
## 🔧 Maintenance
|
||||
|
||||
### Updating Dependencies
|
||||
|
||||
When updating dependencies:
|
||||
|
||||
1. Update `Cargo.toml` in the workspace root
|
||||
2. Update individual crate dependencies if needed
|
||||
3. Test all crates: `cargo test --workspace`
|
||||
4. Publish with incremented version numbers
|
||||
|
||||
### Adding New Modules
|
||||
|
||||
To add a new SAL module:
|
||||
|
||||
1. Create the new crate directory
|
||||
2. Add to workspace members in root `Cargo.toml`
|
||||
3. Add optional dependency in root `Cargo.toml`
|
||||
4. Add feature flag in root `Cargo.toml`
|
||||
5. Add conditional re-export in `src/lib.rs`
|
||||
6. Update documentation
|
||||
|
||||
## 🎉 Benefits
|
||||
|
||||
### For Users
|
||||
|
||||
- **Minimal Dependencies**: Install only what you need
|
||||
- **Faster Builds**: Smaller dependency trees compile faster
|
||||
- **Smaller Binaries**: Reduced binary size
|
||||
- **Clear Dependencies**: Explicit about what functionality is used
|
||||
|
||||
### For Maintainers
|
||||
|
||||
- **Independent Releases**: Can release individual crates as needed
|
||||
- **Focused Testing**: Test individual modules in isolation
|
||||
- **Clear Ownership**: Each crate has clear responsibility
|
||||
- **Easier Maintenance**: Smaller, focused codebases
|
||||
|
||||
This publishing strategy provides the best of both worlds: modularity for users who want minimal dependencies, and convenience for users who prefer a single crate with features.
|
165
README.md
165
README.md
@@ -1,73 +1,136 @@
|
||||
# SAL (System Abstraction Layer)
|
||||
# Herocode Herolib Rust Repository
|
||||
|
||||
A Rust library that provides a unified interface for interacting with operating system features across different platforms. It abstracts away platform-specific details, allowing developers to write cross-platform code with ease.
|
||||
## Overview
|
||||
|
||||
## Features
|
||||
This repository contains the **Herocode Herolib** Rust library and a collection of scripts, examples, and utilities for building, testing, and publishing the SAL (System Abstraction Layer) crates. The repository includes:
|
||||
|
||||
- **File System Operations**: Simplified file and directory management
|
||||
- **Process Management**: Create, monitor, and control processes
|
||||
- **System Information**: Access system details and metrics
|
||||
- **Git Integration**: Interface with Git repositories
|
||||
- **Redis Client**: Robust Redis connection management and command execution
|
||||
- **Text Processing**: Utilities for text manipulation and formatting
|
||||
- **Rust crates** for various system components (e.g., `os`, `process`, `text`, `git`, `vault`, `kubernetes`, etc.).
|
||||
- **Rhai scripts** and test suites for each crate.
|
||||
- **Utility scripts** to automate common development tasks.
|
||||
|
||||
## Modules
|
||||
## Scripts
|
||||
|
||||
### Redis Client
|
||||
The repository provides three primary helper scripts located in the repository root:
|
||||
|
||||
The Redis client module provides a robust wrapper around the Redis client library for Rust, offering:
|
||||
| Script | Description | Typical Usage |
|
||||
|--------|-------------|--------------|
|
||||
| `scripts/publish-all.sh` | Publishes all SAL crates to **crates.io** in the correct dependency order. Handles version bumping, dependency updates, dry‑run mode, and rate‑limiting. | `./scripts/publish-all.sh [--dry-run] [--wait <seconds>] [--version <ver>]` |
|
||||
| `build_herodo.sh` | Builds the `herodo` binary from the `herodo` package and optionally runs a specified Rhai script. | `./build_herodo.sh [script_name]` |
|
||||
| `run_rhai_tests.sh` | Executes all Rhai test suites across the repository, logging results and providing a summary. | `./run_rhai_tests.sh` |
|
||||
|
||||
- Automatic connection management and reconnection
|
||||
- Support for both Unix socket and TCP connections
|
||||
- Database selection via environment variables
|
||||
- Thread-safe global client instance
|
||||
- Simple command execution interface
|
||||
Below are detailed usage instructions for each script.
|
||||
|
||||
[View Redis Client Documentation](src/redisclient/README.md)
|
||||
---
|
||||
|
||||
### OS Module
|
||||
## 1. `scripts/publish-all.sh`
|
||||
|
||||
Provides platform-independent interfaces for operating system functionality.
|
||||
### Purpose
|
||||
|
||||
### Git Module
|
||||
- Publishes each SAL crate in the correct dependency order.
|
||||
- Updates crate versions (if `--version` is supplied).
|
||||
- Updates path dependencies to version dependencies before publishing.
|
||||
- Supports **dry‑run** mode to preview actions without publishing.
|
||||
- Handles rate‑limiting between crate publishes.
|
||||
|
||||
Tools for interacting with Git repositories programmatically.
|
||||
### Options
|
||||
|
||||
### Process Module
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| `--dry-run` | Shows what would be published without actually publishing. |
|
||||
| `--wait <seconds>` | Wait time between publishes (default: 15 s). |
|
||||
| `--version <ver>` | Set a new version for all crates (updates `Cargo.toml` files). |
|
||||
| `-h, --help` | Show help message. |
|
||||
|
||||
Utilities for process creation, monitoring, and management.
|
||||
### Example Usage
|
||||
|
||||
### Text Module
|
||||
```bash
|
||||
# Dry run – no crates will be published
|
||||
./scripts/publish-all.sh --dry-run
|
||||
|
||||
Text processing utilities for common operations.
|
||||
# Publish with a custom wait time and version bump
|
||||
./scripts/publish-all.sh --wait 30 --version 1.2.3
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sal = "0.1.0"
|
||||
# Normal publish (no dry‑run)
|
||||
./scripts/publish-all.sh
|
||||
```
|
||||
|
||||
Basic example:
|
||||
### Notes
|
||||
|
||||
```rust
|
||||
use sal::redisclient::execute;
|
||||
use redis::cmd;
|
||||
- Must be run from the repository root (where `Cargo.toml` lives).
|
||||
- Requires `cargo` and a logged‑in `cargo` session (`cargo login`).
|
||||
- The script automatically updates dependencies in each crate’s `Cargo.toml` to use the new version before publishing.
|
||||
|
||||
fn main() -> redis::RedisResult<()> {
|
||||
// Execute a Redis command
|
||||
let mut cmd = redis::cmd("SET");
|
||||
cmd.arg("example_key").arg("example_value");
|
||||
execute(&mut cmd)?;
|
||||
|
||||
// Retrieve the value
|
||||
let mut get_cmd = redis::cmd("GET");
|
||||
get_cmd.arg("example_key");
|
||||
let value: String = execute(&mut get_cmd)?;
|
||||
println!("Value: {}", value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
---
|
||||
|
||||
## 2. `build_herodo.sh`
|
||||
|
||||
### Purpose
|
||||
|
||||
- Builds the `herodo` binary from the `herodo` package.
|
||||
- Copies the binary to a system‑wide location (`/usr/local/bin`) if run as root, otherwise to `~/hero/bin`.
|
||||
- Optionally runs a specified Rhai script after building.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Build only
|
||||
./build_herodo.sh
|
||||
|
||||
# Build and run a specific Rhai script (e.g., `example`):
|
||||
./build_herodo.sh example
|
||||
```
|
||||
|
||||
### Details
|
||||
|
||||
- The script changes to its own directory, builds the `herodo` crate (`cargo build`), and copies the binary.
|
||||
- If a script name is provided, it looks for the script in:
|
||||
- `src/rhaiexamples/<name>.rhai`
|
||||
- `src/herodo/scripts/<name>.rhai`
|
||||
- If the script is not found, the script exits with an error.
|
||||
|
||||
---
|
||||
|
||||
## 3. `run_rhai_tests.sh`
|
||||
|
||||
### Purpose
|
||||
|
||||
- Runs **all** Rhai test suites across the repository.
|
||||
- Supports both the legacy `rhai_tests` directory and the newer `*/tests/rhai` layout.
|
||||
- Logs output to `run_rhai_tests.log` and prints a summary.
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
./run_rhai_tests.sh
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
- Colored console output for readability.
|
||||
- Log file (`run_rhai_tests.log`) contains full output for later review.
|
||||
- Summary includes total modules, passed, and failed counts.
|
||||
- Exit code `0` if all tests pass, `1` otherwise.
|
||||
|
||||
---
|
||||
|
||||
## General Development Workflow
|
||||
|
||||
1. **Build**: Use `build_herodo.sh` to compile the `herodo` binary.
|
||||
2. **Test**: Run `run_rhai_tests.sh` to ensure all Rhai scripts pass.
|
||||
3. **Publish**: When ready to release, use `scripts/publish-all.sh` (with `--dry-run` first to verify).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Rust toolchain** (`cargo`, `rustc`) installed.
|
||||
- **Rhai** interpreter (`herodo`) built and available.
|
||||
- **Git** for version control.
|
||||
- **Cargo login** for publishing to crates.io.
|
||||
|
||||
## License
|
||||
|
||||
See `LICENSE` for details.
|
||||
|
||||
---
|
||||
|
||||
**Happy coding!**
|
||||
|
43
_archive/service_manager/Cargo.toml
Normal file
43
_archive/service_manager/Cargo.toml
Normal file
@@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "sal-service-manager"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["PlanetFirst <info@incubaid.com>"]
|
||||
description = "SAL Service Manager - Cross-platform service management for dynamic worker deployment"
|
||||
repository = "https://git.threefold.info/herocode/sal"
|
||||
license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
# Use workspace dependencies for consistency
|
||||
thiserror = "1.0"
|
||||
tokio = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
# Use base zinit-client instead of SAL wrapper
|
||||
zinit-client = { version = "0.4.0" }
|
||||
# Optional Rhai integration
|
||||
rhai = { workspace = true, optional = true }
|
||||
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
# macOS-specific dependencies for launchctl
|
||||
plist = "1.6"
|
||||
|
||||
[features]
|
||||
default = ["zinit"]
|
||||
zinit = []
|
||||
rhai = ["dep:rhai"]
|
||||
|
||||
# Enable zinit feature for tests
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
rhai = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
env_logger = "0.10"
|
||||
|
||||
[[test]]
|
||||
name = "zinit_integration_tests"
|
||||
required-features = ["zinit"]
|
198
_archive/service_manager/README.md
Normal file
198
_archive/service_manager/README.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# SAL Service Manager
|
||||
|
||||
[](https://crates.io/crates/sal-service-manager)
|
||||
[](https://docs.rs/sal-service-manager)
|
||||
|
||||
A cross-platform service management library for the System Abstraction Layer (SAL). This crate provides a unified interface for managing system services across different platforms, enabling dynamic deployment of workers and services.
|
||||
|
||||
## Features
|
||||
|
||||
- **Cross-platform service management** - Unified API across macOS and Linux
|
||||
- **Dynamic worker deployment** - Perfect for circle workers and on-demand services
|
||||
- **Platform-specific implementations**:
|
||||
- **macOS**: Uses `launchctl` with plist management
|
||||
- **Linux**: Uses `zinit` for lightweight service management (systemd also available)
|
||||
- **Complete lifecycle management** - Start, stop, restart, status monitoring, and log retrieval
|
||||
- **Service configuration** - Environment variables, working directories, auto-restart
|
||||
- **Production-ready** - Comprehensive error handling and resource management
|
||||
|
||||
## Usage
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sal-service-manager = "0.1.0"
|
||||
```
|
||||
|
||||
Or use it as part of the SAL ecosystem:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
sal = { version = "0.1.0", features = ["service_manager"] }
|
||||
```
|
||||
|
||||
## Primary Use Case: Dynamic Circle Worker Management
|
||||
|
||||
This service manager was designed specifically for dynamic deployment of circle workers in freezone environments. When a new resident registers, you can instantly launch a dedicated circle worker:
|
||||
|
||||
```rust,no_run
|
||||
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// New resident registration triggers worker creation
|
||||
fn deploy_circle_worker(resident_id: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let manager = create_service_manager();
|
||||
|
||||
let mut env = HashMap::new();
|
||||
env.insert("RESIDENT_ID".to_string(), resident_id.to_string());
|
||||
env.insert("WORKER_TYPE".to_string(), "circle".to_string());
|
||||
|
||||
let config = ServiceConfig {
|
||||
name: format!("circle-worker-{}", resident_id),
|
||||
binary_path: "/usr/bin/circle-worker".to_string(),
|
||||
args: vec!["--resident".to_string(), resident_id.to_string()],
|
||||
working_directory: Some("/var/lib/circle-workers".to_string()),
|
||||
environment: env,
|
||||
auto_restart: true,
|
||||
};
|
||||
|
||||
// Deploy the worker
|
||||
manager.start(&config)?;
|
||||
println!("✅ Circle worker deployed for resident: {}", resident_id);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Basic Usage Example
|
||||
|
||||
Here is an example of the core service management API:
|
||||
|
||||
```rust,no_run
|
||||
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let service_manager = create_service_manager();
|
||||
|
||||
let config = ServiceConfig {
|
||||
name: "my-service".to_string(),
|
||||
binary_path: "/usr/local/bin/my-service-executable".to_string(),
|
||||
args: vec!["--config".to_string(), "/etc/my-service.conf".to_string()],
|
||||
working_directory: Some("/var/tmp".to_string()),
|
||||
environment: HashMap::new(),
|
||||
auto_restart: true,
|
||||
};
|
||||
|
||||
// Start a new service
|
||||
service_manager.start(&config)?;
|
||||
|
||||
// Get the status of the service
|
||||
let status = service_manager.status("my-service")?;
|
||||
println!("Service status: {:?}", status);
|
||||
|
||||
// Stop the service
|
||||
service_manager.stop("my-service")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
Comprehensive examples are available in the SAL examples directory:
|
||||
|
||||
### Circle Worker Manager Example
|
||||
|
||||
The primary use case - dynamically launching circle workers for new freezone residents:
|
||||
|
||||
```bash
|
||||
# Run the circle worker management example
|
||||
herodo examples/service_manager/circle_worker_manager.rhai
|
||||
```
|
||||
|
||||
This example demonstrates:
|
||||
- Creating service configurations for circle workers
|
||||
- Complete service lifecycle management
|
||||
- Error handling and status monitoring
|
||||
- Service cleanup and removal
|
||||
|
||||
### Basic Usage Example
|
||||
|
||||
A simpler example showing the core API:
|
||||
|
||||
```bash
|
||||
# Run the basic usage example
|
||||
herodo examples/service_manager/basic_usage.rhai
|
||||
```
|
||||
|
||||
See `examples/service_manager/README.md` for detailed documentation.
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
cargo test -p sal-service-manager
|
||||
```
|
||||
|
||||
For Rhai integration tests:
|
||||
|
||||
```bash
|
||||
cargo test -p sal-service-manager --features rhai
|
||||
```
|
||||
|
||||
### Testing with Herodo
|
||||
|
||||
To test the service manager with real Rhai scripts using herodo, first build herodo:
|
||||
|
||||
```bash
|
||||
./build_herodo.sh
|
||||
```
|
||||
|
||||
Then run Rhai scripts that use the service manager:
|
||||
|
||||
```bash
|
||||
herodo your_service_script.rhai
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Linux (zinit/systemd)
|
||||
|
||||
The service manager automatically discovers running zinit servers and falls back to systemd if none are found.
|
||||
|
||||
**For zinit (recommended):**
|
||||
|
||||
```bash
|
||||
# Start zinit with default socket
|
||||
zinit -s /tmp/zinit.sock init
|
||||
|
||||
# Or with a custom socket path
|
||||
zinit -s /var/run/zinit.sock init
|
||||
```
|
||||
|
||||
**Socket Discovery:**
|
||||
The service manager will automatically find running zinit servers by checking:
|
||||
1. `ZINIT_SOCKET_PATH` environment variable (if set)
|
||||
2. Common socket locations: `/var/run/zinit.sock`, `/tmp/zinit.sock`, `/run/zinit.sock`, `./zinit.sock`
|
||||
|
||||
**Custom socket path:**
|
||||
```bash
|
||||
# Set custom socket path
|
||||
export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock
|
||||
```
|
||||
|
||||
**Systemd fallback:**
|
||||
If no zinit server is detected, the service manager automatically falls back to systemd.
|
||||
|
||||
### macOS (launchctl)
|
||||
|
||||
No additional setup required - uses the built-in launchctl system.
|
||||
|
||||
## Platform Support
|
||||
|
||||
- **macOS**: Full support using `launchctl` for service management
|
||||
- **Linux**: Full support using `zinit` for service management (systemd also available as alternative)
|
||||
- **Windows**: Not currently supported
|
47
_archive/service_manager/examples/README.md
Normal file
47
_archive/service_manager/examples/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Service Manager Examples
|
||||
|
||||
This directory contains examples demonstrating the usage of the `sal-service-manager` crate.
|
||||
|
||||
## Running Examples
|
||||
|
||||
To run any example, use the following command structure from the `service_manager` crate's root directory:
|
||||
|
||||
```sh
|
||||
cargo run --example <EXAMPLE_NAME>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1. `simple_service`
|
||||
|
||||
This example demonstrates the ideal, clean lifecycle of a service using the separated `create` and `start` steps.
|
||||
|
||||
**Behavior:**
|
||||
1. Creates a new service definition.
|
||||
2. Starts the newly created service.
|
||||
3. Checks its status to confirm it's running.
|
||||
4. Stops the service.
|
||||
5. Checks its status again to confirm it's stopped.
|
||||
6. Removes the service definition.
|
||||
|
||||
**Run it:**
|
||||
```sh
|
||||
cargo run --example simple_service
|
||||
```
|
||||
|
||||
### 2. `service_spaghetti`
|
||||
|
||||
This example demonstrates how the service manager handles "messy" or improper sequences of operations, showcasing its error handling and robustness.
|
||||
|
||||
**Behavior:**
|
||||
1. Creates a service.
|
||||
2. Starts the service.
|
||||
3. Tries to start the **same service again** (which should fail as it's already running).
|
||||
4. Removes the service **without stopping it first** (the manager should handle this gracefully).
|
||||
5. Tries to stop the **already removed** service (which should fail).
|
||||
6. Tries to remove the service **again** (which should also fail).
|
||||
|
||||
**Run it:**
|
||||
```sh
|
||||
cargo run --example service_spaghetti
|
||||
```
|
109
_archive/service_manager/examples/service_spaghetti.rs
Normal file
109
_archive/service_manager/examples/service_spaghetti.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! service_spaghetti - An example of messy service management.
|
||||
//!
|
||||
//! This example demonstrates how the service manager behaves when commands
|
||||
//! are issued in a less-than-ideal order, such as starting a service that's
|
||||
//! already running or removing a service that hasn't been stopped.
|
||||
|
||||
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||
use std::collections::HashMap;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Initialize logging to see socket discovery in action
|
||||
env_logger::init();
|
||||
|
||||
let manager = match create_service_manager() {
|
||||
Ok(manager) => manager,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed to create service manager: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let service_name = "com.herocode.examples.spaghetti";
|
||||
|
||||
let service_config = ServiceConfig {
|
||||
name: service_name.to_string(),
|
||||
binary_path: "/bin/sh".to_string(),
|
||||
args: vec![
|
||||
"-c".to_string(),
|
||||
"while true; do echo 'Spaghetti service is running...'; sleep 5; done".to_string(),
|
||||
],
|
||||
working_directory: None,
|
||||
environment: HashMap::new(),
|
||||
auto_restart: false,
|
||||
};
|
||||
|
||||
println!("--- Service Spaghetti Example ---");
|
||||
println!("This example demonstrates messy, error-prone service management.");
|
||||
|
||||
// Cleanup from previous runs to ensure a clean slate
|
||||
if let Ok(true) = manager.exists(service_name) {
|
||||
println!(
|
||||
"\nService '{}' found from a previous run. Cleaning up first.",
|
||||
service_name
|
||||
);
|
||||
let _ = manager.stop(service_name);
|
||||
let _ = manager.remove(service_name);
|
||||
println!("Cleanup complete.");
|
||||
}
|
||||
|
||||
// 1. Start the service (creates and starts in one step)
|
||||
println!("\n1. Starting the service for the first time...");
|
||||
match manager.start(&service_config) {
|
||||
Ok(()) => println!(" -> Success: Service '{}' started.", service_name),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
" -> Error: Failed to start service: {}. Halting example.",
|
||||
e
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
|
||||
// 2. Try to start the service again while it's already running
|
||||
println!("\n2. Trying to start the *same service* again...");
|
||||
match manager.start(&service_config) {
|
||||
Ok(()) => println!(" -> Unexpected Success: Service started again."),
|
||||
Err(e) => eprintln!(
|
||||
" -> Expected Error: {}. The manager should detect it is already running.",
|
||||
e
|
||||
),
|
||||
}
|
||||
|
||||
// 3. Let it run for a bit
|
||||
println!("\n3. Letting the service run for 5 seconds...");
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
|
||||
// 4. Remove the service without stopping it first
|
||||
// The `remove` function is designed to stop the service if it's running.
|
||||
println!("\n4. Removing the service without explicitly stopping it first...");
|
||||
match manager.remove(service_name) {
|
||||
Ok(()) => println!(" -> Success: Service was stopped and removed."),
|
||||
Err(e) => eprintln!(" -> Error: Failed to remove service: {}", e),
|
||||
}
|
||||
|
||||
// 5. Try to stop the service after it has been removed
|
||||
println!("\n5. Trying to stop the service that was just removed...");
|
||||
match manager.stop(service_name) {
|
||||
Ok(()) => println!(" -> Unexpected Success: Stopped a removed service."),
|
||||
Err(e) => eprintln!(
|
||||
" -> Expected Error: {}. The manager knows the service is gone.",
|
||||
e
|
||||
),
|
||||
}
|
||||
|
||||
// 6. Try to remove the service again
|
||||
println!("\n6. Trying to remove the service again...");
|
||||
match manager.remove(service_name) {
|
||||
Ok(()) => println!(" -> Unexpected Success: Removed a non-existent service."),
|
||||
Err(e) => eprintln!(
|
||||
" -> Expected Error: {}. The manager correctly reports it's not found.",
|
||||
e
|
||||
),
|
||||
}
|
||||
|
||||
println!("\n--- Spaghetti Example Finished ---");
|
||||
}
|
110
_archive/service_manager/examples/simple_service.rs
Normal file
110
_archive/service_manager/examples/simple_service.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||
use std::collections::HashMap;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
// Initialize logging to see socket discovery in action
|
||||
env_logger::init();
|
||||
|
||||
// 1. Create a service manager for the current platform
|
||||
let manager = match create_service_manager() {
|
||||
Ok(manager) => manager,
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed to create service manager: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// 2. Define the configuration for our new service
|
||||
let service_name = "com.herocode.examples.simpleservice";
|
||||
let service_config = ServiceConfig {
|
||||
name: service_name.to_string(),
|
||||
// A simple command that runs in a loop
|
||||
binary_path: "/bin/sh".to_string(),
|
||||
args: vec![
|
||||
"-c".to_string(),
|
||||
"while true; do echo 'Simple service is running...'; date; sleep 5; done".to_string(),
|
||||
],
|
||||
working_directory: None,
|
||||
environment: HashMap::new(),
|
||||
auto_restart: false,
|
||||
};
|
||||
|
||||
println!("--- Service Manager Example ---");
|
||||
|
||||
// Cleanup from previous runs, if necessary
|
||||
if let Ok(true) = manager.exists(service_name) {
|
||||
println!(
|
||||
"Service '{}' already exists. Cleaning up before starting.",
|
||||
service_name
|
||||
);
|
||||
if let Err(e) = manager.stop(service_name) {
|
||||
println!(
|
||||
"Note: could not stop existing service (it might not be running): {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
if let Err(e) = manager.remove(service_name) {
|
||||
eprintln!("Error: failed to remove existing service: {}", e);
|
||||
return;
|
||||
}
|
||||
println!("Cleanup complete.");
|
||||
}
|
||||
|
||||
// 3. Start the service (creates and starts in one step)
|
||||
println!("\n1. Starting service: '{}'", service_name);
|
||||
match manager.start(&service_config) {
|
||||
Ok(()) => println!("Service '{}' started successfully.", service_name),
|
||||
Err(e) => {
|
||||
eprintln!("Error: Failed to start service '{}': {}", service_name, e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Give it a moment to run
|
||||
println!("\nWaiting for 2 seconds for the service to initialize...");
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
|
||||
// 4. Check the status of the service
|
||||
println!("\n2. Checking service status...");
|
||||
match manager.status(service_name) {
|
||||
Ok(status) => println!("Service status: {:?}", status),
|
||||
Err(e) => eprintln!(
|
||||
"Error: Failed to get status for service '{}': {}",
|
||||
service_name, e
|
||||
),
|
||||
}
|
||||
|
||||
println!("\nLetting the service run for 10 seconds. Check logs if you can.");
|
||||
thread::sleep(Duration::from_secs(10));
|
||||
|
||||
// 5. Stop the service
|
||||
println!("\n3. Stopping service: '{}'", service_name);
|
||||
match manager.stop(service_name) {
|
||||
Ok(()) => println!("Service '{}' stopped successfully.", service_name),
|
||||
Err(e) => eprintln!("Error: Failed to stop service '{}': {}", service_name, e),
|
||||
}
|
||||
|
||||
println!("\nWaiting for 2 seconds for the service to stop...");
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
|
||||
// Check status again
|
||||
println!("\n4. Checking status after stopping...");
|
||||
match manager.status(service_name) {
|
||||
Ok(status) => println!("Service status: {:?}", status),
|
||||
Err(e) => eprintln!(
|
||||
"Error: Failed to get status for service '{}': {}",
|
||||
service_name, e
|
||||
),
|
||||
}
|
||||
|
||||
// 6. Remove the service
|
||||
println!("\n5. Removing service: '{}'", service_name);
|
||||
match manager.remove(service_name) {
|
||||
Ok(()) => println!("Service '{}' removed successfully.", service_name),
|
||||
Err(e) => eprintln!("Error: Failed to remove service '{}': {}", service_name, e),
|
||||
}
|
||||
|
||||
println!("\n--- Example Finished ---");
|
||||
}
|
47
_archive/service_manager/examples/socket_discovery_test.rs
Normal file
47
_archive/service_manager/examples/socket_discovery_test.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
//! Socket Discovery Test
|
||||
//!
|
||||
//! This example demonstrates the zinit socket discovery functionality.
|
||||
//! It shows how the service manager finds available zinit sockets.
|
||||
|
||||
use sal_service_manager::create_service_manager;
|
||||
|
||||
fn main() {
|
||||
// Initialize logging to see socket discovery in action
|
||||
env_logger::init();
|
||||
|
||||
println!("=== Zinit Socket Discovery Test ===");
|
||||
println!("This test demonstrates how the service manager discovers zinit sockets.");
|
||||
println!();
|
||||
|
||||
// Test environment variable
|
||||
if let Ok(socket_path) = std::env::var("ZINIT_SOCKET_PATH") {
|
||||
println!("🔍 ZINIT_SOCKET_PATH environment variable set to: {}", socket_path);
|
||||
} else {
|
||||
println!("🔍 ZINIT_SOCKET_PATH environment variable not set");
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("🚀 Creating service manager...");
|
||||
match create_service_manager() {
|
||||
Ok(_manager) => {
|
||||
println!("✅ Service manager created successfully!");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
println!("📱 Platform: macOS - Using launchctl");
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
println!("🐧 Platform: Linux - Check logs above for socket discovery details");
|
||||
}
|
||||
Err(e) => {
|
||||
println!("❌ Failed to create service manager: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!("=== Test Complete ===");
|
||||
println!();
|
||||
println!("To test zinit socket discovery on Linux:");
|
||||
println!("1. Start zinit: zinit -s /tmp/zinit.sock init");
|
||||
println!("2. Run with logging: RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager");
|
||||
println!("3. Or set custom path: ZINIT_SOCKET_PATH=/custom/path.sock RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager");
|
||||
}
|
492
_archive/service_manager/src/launchctl.rs
Normal file
492
_archive/service_manager/src/launchctl.rs
Normal file
@@ -0,0 +1,492 @@
|
||||
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use tokio::process::Command;
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
// Shared runtime for async operations - production-safe initialization
|
||||
static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok());
|
||||
|
||||
/// Get the async runtime, creating a temporary one if the static runtime failed
|
||||
fn get_runtime() -> Result<Runtime, ServiceManagerError> {
|
||||
// Try to use the static runtime first
|
||||
if let Some(_runtime) = ASYNC_RUNTIME.as_ref() {
|
||||
// We can't return a reference to the static runtime because we need ownership
|
||||
// for block_on, so we create a new one. This is a reasonable trade-off for safety.
|
||||
Runtime::new().map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||
})
|
||||
} else {
|
||||
// Static runtime failed, try to create a new one
|
||||
Runtime::new().map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LaunchctlServiceManager {
|
||||
service_prefix: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct LaunchDaemon {
|
||||
#[serde(rename = "Label")]
|
||||
label: String,
|
||||
#[serde(rename = "ProgramArguments")]
|
||||
program_arguments: Vec<String>,
|
||||
#[serde(rename = "WorkingDirectory", skip_serializing_if = "Option::is_none")]
|
||||
working_directory: Option<String>,
|
||||
#[serde(
|
||||
rename = "EnvironmentVariables",
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
environment_variables: Option<HashMap<String, String>>,
|
||||
#[serde(rename = "KeepAlive", skip_serializing_if = "Option::is_none")]
|
||||
keep_alive: Option<bool>,
|
||||
#[serde(rename = "RunAtLoad")]
|
||||
run_at_load: bool,
|
||||
#[serde(rename = "StandardOutPath", skip_serializing_if = "Option::is_none")]
|
||||
standard_out_path: Option<String>,
|
||||
#[serde(rename = "StandardErrorPath", skip_serializing_if = "Option::is_none")]
|
||||
standard_error_path: Option<String>,
|
||||
}
|
||||
|
||||
impl LaunchctlServiceManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
service_prefix: "tf.ourworld.circles".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_service_label(&self, service_name: &str) -> String {
|
||||
format!("{}.{}", self.service_prefix, service_name)
|
||||
}
|
||||
|
||||
fn get_plist_path(&self, service_name: &str) -> PathBuf {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
PathBuf::from(home)
|
||||
.join("Library")
|
||||
.join("LaunchAgents")
|
||||
.join(format!("{}.plist", self.get_service_label(service_name)))
|
||||
}
|
||||
|
||||
fn get_log_path(&self, service_name: &str) -> PathBuf {
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
PathBuf::from(home)
|
||||
.join("Library")
|
||||
.join("Logs")
|
||||
.join("circles")
|
||||
.join(format!("{}.log", service_name))
|
||||
}
|
||||
|
||||
async fn create_plist(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||
let label = self.get_service_label(&config.name);
|
||||
let plist_path = self.get_plist_path(&config.name);
|
||||
let log_path = self.get_log_path(&config.name);
|
||||
|
||||
// Ensure the LaunchAgents directory exists
|
||||
if let Some(parent) = plist_path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
// Ensure the logs directory exists
|
||||
if let Some(parent) = log_path.parent() {
|
||||
tokio::fs::create_dir_all(parent).await?;
|
||||
}
|
||||
|
||||
let mut program_arguments = vec![config.binary_path.clone()];
|
||||
program_arguments.extend(config.args.clone());
|
||||
|
||||
let launch_daemon = LaunchDaemon {
|
||||
label: label.clone(),
|
||||
program_arguments,
|
||||
working_directory: config.working_directory.clone(),
|
||||
environment_variables: if config.environment.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(config.environment.clone())
|
||||
},
|
||||
keep_alive: if config.auto_restart {
|
||||
Some(true)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
run_at_load: true,
|
||||
standard_out_path: Some(log_path.to_string_lossy().to_string()),
|
||||
standard_error_path: Some(log_path.to_string_lossy().to_string()),
|
||||
};
|
||||
|
||||
let mut plist_content = Vec::new();
|
||||
plist::to_writer_xml(&mut plist_content, &launch_daemon)
|
||||
.map_err(|e| ServiceManagerError::Other(format!("Failed to serialize plist: {}", e)))?;
|
||||
let plist_content = String::from_utf8(plist_content).map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to convert plist to string: {}", e))
|
||||
})?;
|
||||
|
||||
tokio::fs::write(&plist_path, plist_content).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_launchctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> {
|
||||
let output = Command::new("launchctl").args(args).output().await?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(ServiceManagerError::Other(format!(
|
||||
"launchctl command failed: {}",
|
||||
stderr
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
async fn wait_for_service_status(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
use tokio::time::{sleep, timeout, Duration};
|
||||
|
||||
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||
let poll_interval = Duration::from_millis(500);
|
||||
|
||||
let result = timeout(timeout_duration, async {
|
||||
loop {
|
||||
match self.status(service_name) {
|
||||
Ok(ServiceStatus::Running) => {
|
||||
return Ok(());
|
||||
}
|
||||
Ok(ServiceStatus::Failed) => {
|
||||
// Service failed, get error details from logs
|
||||
let logs = self.logs(service_name, Some(20)).unwrap_or_default();
|
||||
let error_msg = if logs.is_empty() {
|
||||
"Service failed to start (no logs available)".to_string()
|
||||
} else {
|
||||
// Extract error lines from logs
|
||||
let error_lines: Vec<&str> = logs
|
||||
.lines()
|
||||
.filter(|line| {
|
||||
line.to_lowercase().contains("error")
|
||||
|| line.to_lowercase().contains("failed")
|
||||
})
|
||||
.take(3)
|
||||
.collect();
|
||||
|
||||
if error_lines.is_empty() {
|
||||
format!(
|
||||
"Service failed to start. Recent logs:\n{}",
|
||||
logs.lines()
|
||||
.rev()
|
||||
.take(5)
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.rev()
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
)
|
||||
} else {
|
||||
format!(
|
||||
"Service failed to start. Errors:\n{}",
|
||||
error_lines.join("\n")
|
||||
)
|
||||
}
|
||||
};
|
||||
return Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
error_msg,
|
||||
));
|
||||
}
|
||||
Ok(ServiceStatus::Stopped) | Ok(ServiceStatus::Unknown) => {
|
||||
// Still starting, continue polling
|
||||
sleep(poll_interval).await;
|
||||
}
|
||||
Err(ServiceManagerError::ServiceNotFound(_)) => {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(_) => Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
format!("Service did not start within {} seconds", timeout_secs),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceManager for LaunchctlServiceManager {
|
||||
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||
let plist_path = self.get_plist_path(service_name);
|
||||
Ok(plist_path.exists())
|
||||
}
|
||||
|
||||
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||
// Use production-safe runtime for async operations
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let label = self.get_service_label(&config.name);
|
||||
|
||||
// Check if service is already loaded
|
||||
let list_output = self.run_launchctl(&["list"]).await?;
|
||||
if list_output.contains(&label) {
|
||||
return Err(ServiceManagerError::ServiceAlreadyExists(
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
// Create the plist file
|
||||
self.create_plist(config).await?;
|
||||
|
||||
// Load the service
|
||||
let plist_path = self.get_plist_path(&config.name);
|
||||
self.run_launchctl(&["load", &plist_path.to_string_lossy()])
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ServiceManagerError::StartFailed(config.name.clone(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let label = self.get_service_label(service_name);
|
||||
let plist_path = self.get_plist_path(service_name);
|
||||
|
||||
// Check if plist file exists
|
||||
if !plist_path.exists() {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check if service is already loaded and running
|
||||
let list_output = self.run_launchctl(&["list"]).await?;
|
||||
if list_output.contains(&label) {
|
||||
// Service is loaded, check if it's running
|
||||
match self.status(service_name)? {
|
||||
ServiceStatus::Running => {
|
||||
return Ok(()); // Already running, nothing to do
|
||||
}
|
||||
_ => {
|
||||
// Service is loaded but not running, try to start it
|
||||
self.run_launchctl(&["start", &label]).await.map_err(|e| {
|
||||
ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
e.to_string(),
|
||||
)
|
||||
})?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Service is not loaded, load it
|
||||
self.run_launchctl(&["load", &plist_path.to_string_lossy()])
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ServiceManagerError::StartFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn start_and_confirm(
|
||||
&self,
|
||||
config: &ServiceConfig,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// First start the service
|
||||
self.start(config)?;
|
||||
|
||||
// Then wait for confirmation using production-safe runtime
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
self.wait_for_service_status(&config.name, timeout_secs)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn start_existing_and_confirm(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// First start the existing service
|
||||
self.start_existing(service_name)?;
|
||||
|
||||
// Then wait for confirmation using production-safe runtime
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
self.wait_for_service_status(service_name, timeout_secs)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let _label = self.get_service_label(service_name);
|
||||
let plist_path = self.get_plist_path(service_name);
|
||||
|
||||
// Unload the service
|
||||
self.run_launchctl(&["unload", &plist_path.to_string_lossy()])
|
||||
.await
|
||||
.map_err(|e| {
|
||||
ServiceManagerError::StopFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
// For launchctl, we stop and start
|
||||
if let Err(e) = self.stop(service_name) {
|
||||
// If stop fails because service doesn't exist, that's ok for restart
|
||||
if !matches!(e, ServiceManagerError::ServiceNotFound(_)) {
|
||||
return Err(ServiceManagerError::RestartFailed(
|
||||
service_name.to_string(),
|
||||
e.to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// We need the config to restart, but we don't have it stored
|
||||
// For now, return an error - in a real implementation we might store configs
|
||||
Err(ServiceManagerError::RestartFailed(
|
||||
service_name.to_string(),
|
||||
"Restart requires re-providing service configuration".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let label = self.get_service_label(service_name);
|
||||
let plist_path = self.get_plist_path(service_name);
|
||||
|
||||
// First check if the plist file exists
|
||||
if !plist_path.exists() {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let list_output = self.run_launchctl(&["list"]).await?;
|
||||
|
||||
if !list_output.contains(&label) {
|
||||
return Ok(ServiceStatus::Stopped);
|
||||
}
|
||||
|
||||
// Get detailed status
|
||||
match self.run_launchctl(&["list", &label]).await {
|
||||
Ok(output) => {
|
||||
if output.contains("\"PID\" = ") {
|
||||
Ok(ServiceStatus::Running)
|
||||
} else if output.contains("\"LastExitStatus\" = ") {
|
||||
Ok(ServiceStatus::Failed)
|
||||
} else {
|
||||
Ok(ServiceStatus::Unknown)
|
||||
}
|
||||
}
|
||||
Err(_) => Ok(ServiceStatus::Stopped),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn logs(
|
||||
&self,
|
||||
service_name: &str,
|
||||
lines: Option<usize>,
|
||||
) -> Result<String, ServiceManagerError> {
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let log_path = self.get_log_path(service_name);
|
||||
|
||||
if !log_path.exists() {
|
||||
return Ok(String::new());
|
||||
}
|
||||
|
||||
match lines {
|
||||
Some(n) => {
|
||||
let output = Command::new("tail")
|
||||
.args(&["-n", &n.to_string(), &log_path.to_string_lossy()])
|
||||
.output()
|
||||
.await?;
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
None => {
|
||||
let content = tokio::fs::read_to_string(&log_path).await?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let list_output = self.run_launchctl(&["list"]).await?;
|
||||
|
||||
let services: Vec<String> = list_output
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
if line.contains(&self.service_prefix) {
|
||||
// Extract service name from label
|
||||
line.split_whitespace()
|
||||
.last()
|
||||
.and_then(|label| {
|
||||
label.strip_prefix(&format!("{}.", self.service_prefix))
|
||||
})
|
||||
.map(|s| s.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(services)
|
||||
})
|
||||
}
|
||||
|
||||
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
// Try to stop the service first, but don't fail if it's already stopped or doesn't exist
|
||||
if let Err(e) = self.stop(service_name) {
|
||||
// Log the error but continue with removal
|
||||
log::warn!(
|
||||
"Failed to stop service '{}' before removal: {}",
|
||||
service_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Remove the plist file using production-safe runtime
|
||||
let runtime = get_runtime()?;
|
||||
runtime.block_on(async {
|
||||
let plist_path = self.get_plist_path(service_name);
|
||||
if plist_path.exists() {
|
||||
tokio::fs::remove_file(&plist_path).await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
301
_archive/service_manager/src/lib.rs
Normal file
301
_archive/service_manager/src/lib.rs
Normal file
@@ -0,0 +1,301 @@
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ServiceManagerError {
|
||||
#[error("Service '{0}' not found")]
|
||||
ServiceNotFound(String),
|
||||
#[error("Service '{0}' already exists")]
|
||||
ServiceAlreadyExists(String),
|
||||
#[error("Failed to start service '{0}': {1}")]
|
||||
StartFailed(String, String),
|
||||
#[error("Failed to stop service '{0}': {1}")]
|
||||
StopFailed(String, String),
|
||||
#[error("Failed to restart service '{0}': {1}")]
|
||||
RestartFailed(String, String),
|
||||
#[error("Failed to get logs for service '{0}': {1}")]
|
||||
LogsFailed(String, String),
|
||||
#[error("IO error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("Service manager error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ServiceConfig {
|
||||
pub name: String,
|
||||
pub binary_path: String,
|
||||
pub args: Vec<String>,
|
||||
pub working_directory: Option<String>,
|
||||
pub environment: HashMap<String, String>,
|
||||
pub auto_restart: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ServiceStatus {
|
||||
Running,
|
||||
Stopped,
|
||||
Failed,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
pub trait ServiceManager: Send + Sync {
|
||||
/// Check if a service exists
|
||||
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError>;
|
||||
|
||||
/// Start a service with the given configuration
|
||||
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start an existing service by name (load existing plist/config)
|
||||
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start a service and wait for confirmation that it's running or failed
|
||||
fn start_and_confirm(
|
||||
&self,
|
||||
config: &ServiceConfig,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Start an existing service and wait for confirmation that it's running or failed
|
||||
fn start_existing_and_confirm(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Stop a service by name
|
||||
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Restart a service by name
|
||||
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
|
||||
/// Get the status of a service
|
||||
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError>;
|
||||
|
||||
/// Get logs for a service
|
||||
fn logs(&self, service_name: &str, lines: Option<usize>)
|
||||
-> Result<String, ServiceManagerError>;
|
||||
|
||||
/// List all managed services
|
||||
fn list(&self) -> Result<Vec<String>, ServiceManagerError>;
|
||||
|
||||
/// Remove a service configuration (stop if running)
|
||||
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||
}
|
||||
|
||||
// Platform-specific implementations
|
||||
#[cfg(target_os = "macos")]
|
||||
mod launchctl;
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use launchctl::LaunchctlServiceManager;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod systemd;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use systemd::SystemdServiceManager;
|
||||
|
||||
mod zinit;
|
||||
pub use zinit::ZinitServiceManager;
|
||||
|
||||
#[cfg(feature = "rhai")]
|
||||
pub mod rhai;
|
||||
|
||||
/// Discover available zinit socket paths
|
||||
///
|
||||
/// This function checks for zinit sockets in the following order:
|
||||
/// 1. Environment variable ZINIT_SOCKET_PATH (if set)
|
||||
/// 2. Common socket locations with connectivity testing
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the first working socket path found, or None if no working zinit server is detected.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn discover_zinit_socket() -> Option<String> {
|
||||
// First check environment variable
|
||||
if let Ok(env_socket_path) = std::env::var("ZINIT_SOCKET_PATH") {
|
||||
log::debug!("Checking ZINIT_SOCKET_PATH: {}", env_socket_path);
|
||||
if test_zinit_socket(&env_socket_path) {
|
||||
log::info!(
|
||||
"Using zinit socket from ZINIT_SOCKET_PATH: {}",
|
||||
env_socket_path
|
||||
);
|
||||
return Some(env_socket_path);
|
||||
} else {
|
||||
log::warn!(
|
||||
"ZINIT_SOCKET_PATH specified but socket is not accessible: {}",
|
||||
env_socket_path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Try common socket locations
|
||||
let common_paths = [
|
||||
"/var/run/zinit.sock",
|
||||
"/tmp/zinit.sock",
|
||||
"/run/zinit.sock",
|
||||
"./zinit.sock",
|
||||
];
|
||||
|
||||
log::debug!("Discovering zinit socket from common locations...");
|
||||
for path in &common_paths {
|
||||
log::debug!("Testing socket path: {}", path);
|
||||
if test_zinit_socket(path) {
|
||||
log::info!("Found working zinit socket at: {}", path);
|
||||
return Some(path.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("No working zinit socket found");
|
||||
None
|
||||
}
|
||||
|
||||
/// Test if a zinit socket is accessible and responsive
|
||||
///
|
||||
/// This function attempts to create a ZinitServiceManager and perform a basic
|
||||
/// connectivity test by listing services.
|
||||
#[cfg(target_os = "linux")]
|
||||
fn test_zinit_socket(socket_path: &str) -> bool {
|
||||
// Check if socket file exists first
|
||||
if !std::path::Path::new(socket_path).exists() {
|
||||
log::debug!("Socket file does not exist: {}", socket_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to create a manager and test basic connectivity
|
||||
match ZinitServiceManager::new(socket_path) {
|
||||
Ok(manager) => {
|
||||
// Test basic connectivity by trying to list services
|
||||
match manager.list() {
|
||||
Ok(_) => {
|
||||
log::debug!("Socket {} is responsive", socket_path);
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Socket {} exists but not responsive: {}", socket_path, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("Failed to create manager for socket {}: {}", socket_path, e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a service manager appropriate for the current platform
|
||||
///
|
||||
/// - On macOS: Uses launchctl for service management
|
||||
/// - On Linux: Uses zinit for service management with systemd fallback
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns a Result containing the service manager or an error if initialization fails.
|
||||
/// On Linux, it first tries to discover a working zinit socket. If no zinit server is found,
|
||||
/// it will fall back to systemd.
|
||||
///
|
||||
/// # Environment Variables
|
||||
///
|
||||
/// - `ZINIT_SOCKET_PATH`: Specifies the zinit socket path (Linux only)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `ServiceManagerError` if:
|
||||
/// - The platform is not supported (Windows, etc.)
|
||||
/// - Service manager initialization fails on all available backends
|
||||
pub fn create_service_manager() -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Ok(Box::new(LaunchctlServiceManager::new()))
|
||||
}
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
// Try to discover a working zinit socket
|
||||
if let Some(socket_path) = discover_zinit_socket() {
|
||||
match ZinitServiceManager::new(&socket_path) {
|
||||
Ok(zinit_manager) => {
|
||||
log::info!("Using zinit service manager with socket: {}", socket_path);
|
||||
return Ok(Box::new(zinit_manager));
|
||||
}
|
||||
Err(zinit_error) => {
|
||||
log::warn!(
|
||||
"Failed to create zinit manager for discovered socket {}: {}",
|
||||
socket_path,
|
||||
zinit_error
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::info!("No running zinit server detected. To use zinit, start it with: zinit -s /tmp/zinit.sock init");
|
||||
}
|
||||
|
||||
// Fallback to systemd
|
||||
log::info!("Falling back to systemd service manager");
|
||||
Ok(Box::new(SystemdServiceManager::new()))
|
||||
}
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||
{
|
||||
Err(ServiceManagerError::Other(
|
||||
"Service manager not implemented for this platform".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a service manager for zinit with a custom socket path
|
||||
///
|
||||
/// This is useful when zinit is running with a non-default socket path
|
||||
pub fn create_zinit_service_manager(
|
||||
socket_path: &str,
|
||||
) -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||
Ok(Box::new(ZinitServiceManager::new(socket_path)?))
|
||||
}
|
||||
|
||||
/// Create a service manager for systemd (Linux alternative)
|
||||
///
|
||||
/// This creates a systemd-based service manager as an alternative to zinit on Linux
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn create_systemd_service_manager() -> Box<dyn ServiceManager> {
|
||||
Box::new(SystemdServiceManager::new())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_create_service_manager() {
|
||||
// This test ensures the service manager can be created without panicking
|
||||
let result = create_service_manager();
|
||||
assert!(result.is_ok(), "Service manager creation should succeed");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_socket_discovery_with_env_var() {
|
||||
// Test that environment variable is respected
|
||||
std::env::set_var("ZINIT_SOCKET_PATH", "/test/path.sock");
|
||||
|
||||
// The discover function should check the env var first
|
||||
// Since the socket doesn't exist, it should return None, but we can't test
|
||||
// the actual discovery logic without a real socket
|
||||
|
||||
std::env::remove_var("ZINIT_SOCKET_PATH");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_socket_discovery_without_env_var() {
|
||||
// Ensure env var is not set
|
||||
std::env::remove_var("ZINIT_SOCKET_PATH");
|
||||
|
||||
// The discover function should try common paths
|
||||
// Since no zinit is running, it should return None
|
||||
let result = discover_zinit_socket();
|
||||
|
||||
// This is expected to be None in test environment
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Should return None when no zinit server is running"
|
||||
);
|
||||
}
|
||||
}
|
256
_archive/service_manager/src/rhai.rs
Normal file
256
_archive/service_manager/src/rhai.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
//! Rhai integration for the service manager module
|
||||
//!
|
||||
//! This module provides Rhai scripting support for service management operations.
|
||||
|
||||
use crate::{create_service_manager, ServiceConfig, ServiceManager};
|
||||
use rhai::{Engine, EvalAltResult, Map};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A wrapper around ServiceManager that can be used in Rhai
|
||||
#[derive(Clone)]
|
||||
pub struct RhaiServiceManager {
|
||||
inner: Arc<Box<dyn ServiceManager>>,
|
||||
}
|
||||
|
||||
impl RhaiServiceManager {
|
||||
pub fn new() -> Result<Self, Box<EvalAltResult>> {
|
||||
let manager = create_service_manager()
|
||||
.map_err(|e| format!("Failed to create service manager: {}", e))?;
|
||||
Ok(Self {
|
||||
inner: Arc::new(manager),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the service manager module with a Rhai engine
|
||||
pub fn register_service_manager_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||
// Factory function to create service manager
|
||||
engine.register_type::<RhaiServiceManager>();
|
||||
engine.register_fn(
|
||||
"create_service_manager",
|
||||
|| -> Result<RhaiServiceManager, Box<EvalAltResult>> { RhaiServiceManager::new() },
|
||||
);
|
||||
|
||||
// Service management functions
|
||||
engine.register_fn(
|
||||
"start",
|
||||
|manager: &mut RhaiServiceManager, config: Map| -> Result<(), Box<EvalAltResult>> {
|
||||
let service_config = map_to_service_config(config)?;
|
||||
manager
|
||||
.inner
|
||||
.start(&service_config)
|
||||
.map_err(|e| format!("Failed to start service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"stop",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.stop(&service_name)
|
||||
.map_err(|e| format!("Failed to stop service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"restart",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.restart(&service_name)
|
||||
.map_err(|e| format!("Failed to restart service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"status",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<String, Box<EvalAltResult>> {
|
||||
let status = manager
|
||||
.inner
|
||||
.status(&service_name)
|
||||
.map_err(|e| format!("Failed to get service status: {}", e))?;
|
||||
Ok(format!("{:?}", status))
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"logs",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String,
|
||||
lines: i64|
|
||||
-> Result<String, Box<EvalAltResult>> {
|
||||
let lines_opt = if lines > 0 {
|
||||
Some(lines as usize)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.logs(&service_name, lines_opt)
|
||||
.map_err(|e| format!("Failed to get service logs: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"list",
|
||||
|manager: &mut RhaiServiceManager| -> Result<Vec<String>, Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.list()
|
||||
.map_err(|e| format!("Failed to list services: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"remove",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.remove(&service_name)
|
||||
.map_err(|e| format!("Failed to remove service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"exists",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String|
|
||||
-> Result<bool, Box<EvalAltResult>> {
|
||||
manager
|
||||
.inner
|
||||
.exists(&service_name)
|
||||
.map_err(|e| format!("Failed to check if service exists: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"start_and_confirm",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
config: Map,
|
||||
timeout_secs: i64|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
let service_config = map_to_service_config(config)?;
|
||||
let timeout = if timeout_secs > 0 {
|
||||
timeout_secs as u64
|
||||
} else {
|
||||
30
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.start_and_confirm(&service_config, timeout)
|
||||
.map_err(|e| format!("Failed to start and confirm service: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
engine.register_fn(
|
||||
"start_existing_and_confirm",
|
||||
|manager: &mut RhaiServiceManager,
|
||||
service_name: String,
|
||||
timeout_secs: i64|
|
||||
-> Result<(), Box<EvalAltResult>> {
|
||||
let timeout = if timeout_secs > 0 {
|
||||
timeout_secs as u64
|
||||
} else {
|
||||
30
|
||||
};
|
||||
manager
|
||||
.inner
|
||||
.start_existing_and_confirm(&service_name, timeout)
|
||||
.map_err(|e| format!("Failed to start existing service and confirm: {}", e).into())
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert a Rhai Map to a ServiceConfig
|
||||
fn map_to_service_config(map: Map) -> Result<ServiceConfig, Box<EvalAltResult>> {
|
||||
let name = map
|
||||
.get("name")
|
||||
.and_then(|v| v.clone().into_string().ok())
|
||||
.ok_or("Service config must have a 'name' field")?;
|
||||
|
||||
let binary_path = map
|
||||
.get("binary_path")
|
||||
.and_then(|v| v.clone().into_string().ok())
|
||||
.ok_or("Service config must have a 'binary_path' field")?;
|
||||
|
||||
let args = map
|
||||
.get("args")
|
||||
.and_then(|v| v.clone().try_cast::<rhai::Array>())
|
||||
.map(|arr| {
|
||||
arr.into_iter()
|
||||
.filter_map(|v| v.into_string().ok())
|
||||
.collect::<Vec<String>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let working_directory = map
|
||||
.get("working_directory")
|
||||
.and_then(|v| v.clone().into_string().ok());
|
||||
|
||||
let environment = map
|
||||
.get("environment")
|
||||
.and_then(|v| v.clone().try_cast::<Map>())
|
||||
.map(|env_map| {
|
||||
env_map
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| v.into_string().ok().map(|val| (k.to_string(), val)))
|
||||
.collect::<HashMap<String, String>>()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let auto_restart = map
|
||||
.get("auto_restart")
|
||||
.and_then(|v| v.as_bool().ok())
|
||||
.unwrap_or(false);
|
||||
|
||||
Ok(ServiceConfig {
|
||||
name,
|
||||
binary_path,
|
||||
args,
|
||||
working_directory,
|
||||
environment,
|
||||
auto_restart,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rhai::{Engine, Map};
|
||||
|
||||
#[test]
|
||||
fn test_register_service_manager_module() {
|
||||
let mut engine = Engine::new();
|
||||
register_service_manager_module(&mut engine).unwrap();
|
||||
|
||||
// Test that the functions are registered
|
||||
// Note: Rhai doesn't expose a public API to check if functions are registered
|
||||
// So we'll just verify the module registration doesn't panic
|
||||
assert!(true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_map_to_service_config() {
|
||||
let mut map = Map::new();
|
||||
map.insert("name".into(), "test-service".into());
|
||||
map.insert("binary_path".into(), "/bin/echo".into());
|
||||
map.insert("auto_restart".into(), true.into());
|
||||
|
||||
let config = map_to_service_config(map).unwrap();
|
||||
assert_eq!(config.name, "test-service");
|
||||
assert_eq!(config.binary_path, "/bin/echo");
|
||||
assert_eq!(config.auto_restart, true);
|
||||
}
|
||||
}
|
434
_archive/service_manager/src/systemd.rs
Normal file
434
_archive/service_manager/src/systemd.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SystemdServiceManager {
|
||||
service_prefix: String,
|
||||
user_mode: bool,
|
||||
}
|
||||
|
||||
impl SystemdServiceManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
service_prefix: "sal".to_string(),
|
||||
user_mode: true, // Default to user services for safety
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_system() -> Self {
|
||||
Self {
|
||||
service_prefix: "sal".to_string(),
|
||||
user_mode: false, // System-wide services (requires root)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_service_name(&self, service_name: &str) -> String {
|
||||
format!("{}-{}.service", self.service_prefix, service_name)
|
||||
}
|
||||
|
||||
fn get_unit_file_path(&self, service_name: &str) -> PathBuf {
|
||||
let service_file = self.get_service_name(service_name);
|
||||
if self.user_mode {
|
||||
// User service directory
|
||||
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||
PathBuf::from(home)
|
||||
.join(".config")
|
||||
.join("systemd")
|
||||
.join("user")
|
||||
.join(service_file)
|
||||
} else {
|
||||
// System service directory
|
||||
PathBuf::from("/etc/systemd/system").join(service_file)
|
||||
}
|
||||
}
|
||||
|
||||
fn run_systemctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> {
|
||||
let mut cmd = Command::new("systemctl");
|
||||
|
||||
if self.user_mode {
|
||||
cmd.arg("--user");
|
||||
}
|
||||
|
||||
cmd.args(args);
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
.map_err(|e| ServiceManagerError::Other(format!("Failed to run systemctl: {}", e)))?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(ServiceManagerError::Other(format!(
|
||||
"systemctl command failed: {}",
|
||||
stderr
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn create_unit_file(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||
let unit_path = self.get_unit_file_path(&config.name);
|
||||
|
||||
// Ensure the directory exists
|
||||
if let Some(parent) = unit_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create unit directory: {}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
// Create the unit file content
|
||||
let mut unit_content = String::new();
|
||||
unit_content.push_str("[Unit]\n");
|
||||
unit_content.push_str(&format!("Description={} service\n", config.name));
|
||||
unit_content.push_str("After=network.target\n\n");
|
||||
|
||||
unit_content.push_str("[Service]\n");
|
||||
unit_content.push_str("Type=simple\n");
|
||||
|
||||
// Build the ExecStart command
|
||||
let mut exec_start = config.binary_path.clone();
|
||||
for arg in &config.args {
|
||||
exec_start.push(' ');
|
||||
exec_start.push_str(arg);
|
||||
}
|
||||
unit_content.push_str(&format!("ExecStart={}\n", exec_start));
|
||||
|
||||
if let Some(working_dir) = &config.working_directory {
|
||||
unit_content.push_str(&format!("WorkingDirectory={}\n", working_dir));
|
||||
}
|
||||
|
||||
// Add environment variables
|
||||
for (key, value) in &config.environment {
|
||||
unit_content.push_str(&format!("Environment=\"{}={}\"\n", key, value));
|
||||
}
|
||||
|
||||
if config.auto_restart {
|
||||
unit_content.push_str("Restart=always\n");
|
||||
unit_content.push_str("RestartSec=5\n");
|
||||
}
|
||||
|
||||
unit_content.push_str("\n[Install]\n");
|
||||
unit_content.push_str("WantedBy=default.target\n");
|
||||
|
||||
// Write the unit file
|
||||
fs::write(&unit_path, unit_content)
|
||||
.map_err(|e| ServiceManagerError::Other(format!("Failed to write unit file: {}", e)))?;
|
||||
|
||||
// Reload systemd to pick up the new unit file
|
||||
self.run_systemctl(&["daemon-reload"])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceManager for SystemdServiceManager {
|
||||
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||
let unit_path = self.get_unit_file_path(service_name);
|
||||
Ok(unit_path.exists())
|
||||
}
|
||||
|
||||
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||
let service_name = self.get_service_name(&config.name);
|
||||
|
||||
// Check if service already exists and is running
|
||||
if self.exists(&config.name)? {
|
||||
match self.status(&config.name)? {
|
||||
ServiceStatus::Running => {
|
||||
return Err(ServiceManagerError::ServiceAlreadyExists(
|
||||
config.name.clone(),
|
||||
));
|
||||
}
|
||||
_ => {
|
||||
// Service exists but not running, we can start it
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Create the unit file
|
||||
self.create_unit_file(config)?;
|
||||
}
|
||||
|
||||
// Enable and start the service
|
||||
self.run_systemctl(&["enable", &service_name])
|
||||
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||
|
||||
self.run_systemctl(&["start", &service_name])
|
||||
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if unit file exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Check if already running
|
||||
match self.status(service_name)? {
|
||||
ServiceStatus::Running => {
|
||||
return Ok(()); // Already running, nothing to do
|
||||
}
|
||||
_ => {
|
||||
// Start the service
|
||||
self.run_systemctl(&["start", &service_unit]).map_err(|e| {
|
||||
ServiceManagerError::StartFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn start_and_confirm(
|
||||
&self,
|
||||
config: &ServiceConfig,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// Start the service first
|
||||
self.start(config)?;
|
||||
|
||||
// Wait for confirmation with timeout
|
||||
let start_time = std::time::Instant::now();
|
||||
let timeout_duration = std::time::Duration::from_secs(timeout_secs);
|
||||
|
||||
while start_time.elapsed() < timeout_duration {
|
||||
match self.status(&config.name) {
|
||||
Ok(ServiceStatus::Running) => return Ok(()),
|
||||
Ok(ServiceStatus::Failed) => {
|
||||
return Err(ServiceManagerError::StartFailed(
|
||||
config.name.clone(),
|
||||
"Service failed to start".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(_) => {
|
||||
// Still starting, wait a bit
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
Err(_) => {
|
||||
// Service might not exist yet, wait a bit
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ServiceManagerError::StartFailed(
|
||||
config.name.clone(),
|
||||
format!("Service did not start within {} seconds", timeout_secs),
|
||||
))
|
||||
}
|
||||
|
||||
fn start_existing_and_confirm(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// Start the existing service first
|
||||
self.start_existing(service_name)?;
|
||||
|
||||
// Wait for confirmation with timeout
|
||||
let start_time = std::time::Instant::now();
|
||||
let timeout_duration = std::time::Duration::from_secs(timeout_secs);
|
||||
|
||||
while start_time.elapsed() < timeout_duration {
|
||||
match self.status(service_name) {
|
||||
Ok(ServiceStatus::Running) => return Ok(()),
|
||||
Ok(ServiceStatus::Failed) => {
|
||||
return Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
"Service failed to start".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(_) => {
|
||||
// Still starting, wait a bit
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
Err(_) => {
|
||||
// Service might not exist yet, wait a bit
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
format!("Service did not start within {} seconds", timeout_secs),
|
||||
))
|
||||
}
|
||||
|
||||
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if service exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Stop the service
|
||||
self.run_systemctl(&["stop", &service_unit]).map_err(|e| {
|
||||
ServiceManagerError::StopFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if service exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Restart the service
|
||||
self.run_systemctl(&["restart", &service_unit])
|
||||
.map_err(|e| {
|
||||
ServiceManagerError::RestartFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if service exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Get service status
|
||||
let output = self
|
||||
.run_systemctl(&["is-active", &service_unit])
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
|
||||
let status = match output.trim() {
|
||||
"active" => ServiceStatus::Running,
|
||||
"inactive" => ServiceStatus::Stopped,
|
||||
"failed" => ServiceStatus::Failed,
|
||||
_ => ServiceStatus::Unknown,
|
||||
};
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
fn logs(
|
||||
&self,
|
||||
service_name: &str,
|
||||
lines: Option<usize>,
|
||||
) -> Result<String, ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if service exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Build journalctl command
|
||||
let mut args = vec!["--unit", &service_unit, "--no-pager"];
|
||||
let lines_arg;
|
||||
if let Some(n) = lines {
|
||||
lines_arg = format!("--lines={}", n);
|
||||
args.push(&lines_arg);
|
||||
}
|
||||
|
||||
// Use journalctl to get logs
|
||||
let mut cmd = std::process::Command::new("journalctl");
|
||||
if self.user_mode {
|
||||
cmd.arg("--user");
|
||||
}
|
||||
cmd.args(&args);
|
||||
|
||||
let output = cmd.output().map_err(|e| {
|
||||
ServiceManagerError::LogsFailed(
|
||||
service_name.to_string(),
|
||||
format!("Failed to run journalctl: {}", e),
|
||||
)
|
||||
})?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
return Err(ServiceManagerError::LogsFailed(
|
||||
service_name.to_string(),
|
||||
format!("journalctl command failed: {}", stderr),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
|
||||
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||
// List all services with our prefix
|
||||
let output =
|
||||
self.run_systemctl(&["list-units", "--type=service", "--all", "--no-pager"])?;
|
||||
|
||||
let mut services = Vec::new();
|
||||
for line in output.lines() {
|
||||
if line.contains(&format!("{}-", self.service_prefix)) {
|
||||
// Extract service name from the line
|
||||
if let Some(unit_name) = line.split_whitespace().next() {
|
||||
if let Some(service_name) = unit_name.strip_suffix(".service") {
|
||||
if let Some(name) =
|
||||
service_name.strip_prefix(&format!("{}-", self.service_prefix))
|
||||
{
|
||||
services.push(name.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}
|
||||
|
||||
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let service_unit = self.get_service_name(service_name);
|
||||
|
||||
// Check if service exists
|
||||
if !self.exists(service_name)? {
|
||||
return Err(ServiceManagerError::ServiceNotFound(
|
||||
service_name.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Try to stop the service first, but don't fail if it's already stopped
|
||||
if let Err(e) = self.stop(service_name) {
|
||||
log::warn!(
|
||||
"Failed to stop service '{}' before removal: {}",
|
||||
service_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
// Disable the service
|
||||
if let Err(e) = self.run_systemctl(&["disable", &service_unit]) {
|
||||
log::warn!("Failed to disable service '{}': {}", service_name, e);
|
||||
}
|
||||
|
||||
// Remove the unit file
|
||||
let unit_path = self.get_unit_file_path(service_name);
|
||||
if unit_path.exists() {
|
||||
std::fs::remove_file(&unit_path).map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to remove unit file: {}", e))
|
||||
})?;
|
||||
}
|
||||
|
||||
// Reload systemd to pick up the changes
|
||||
self.run_systemctl(&["daemon-reload"])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
379
_archive/service_manager/src/zinit.rs
Normal file
379
_archive/service_manager/src/zinit.rs
Normal file
@@ -0,0 +1,379 @@
|
||||
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::runtime::Runtime;
|
||||
use tokio::time::timeout;
|
||||
use zinit_client::{ServiceStatus as ZinitServiceStatus, ZinitClient, ZinitError};
|
||||
|
||||
// Shared runtime for async operations - production-safe initialization
|
||||
static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok());
|
||||
|
||||
/// Get the async runtime, creating a temporary one if the static runtime failed
|
||||
fn get_runtime() -> Result<Runtime, ServiceManagerError> {
|
||||
// Try to use the static runtime first
|
||||
if let Some(_runtime) = ASYNC_RUNTIME.as_ref() {
|
||||
// We can't return a reference to the static runtime because we need ownership
|
||||
// for block_on, so we create a new one. This is a reasonable trade-off for safety.
|
||||
Runtime::new().map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||
})
|
||||
} else {
|
||||
// Static runtime failed, try to create a new one
|
||||
Runtime::new().map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ZinitServiceManager {
|
||||
client: Arc<ZinitClient>,
|
||||
}
|
||||
|
||||
impl ZinitServiceManager {
|
||||
pub fn new(socket_path: &str) -> Result<Self, ServiceManagerError> {
|
||||
// Create the base zinit client directly
|
||||
let client = Arc::new(ZinitClient::new(socket_path));
|
||||
|
||||
Ok(ZinitServiceManager { client })
|
||||
}
|
||||
|
||||
/// Execute an async operation using the shared runtime or current context
|
||||
fn execute_async<F, T>(&self, operation: F) -> Result<T, ServiceManagerError>
|
||||
where
|
||||
F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
// Check if we're already in a tokio runtime context
|
||||
if let Ok(_handle) = tokio::runtime::Handle::try_current() {
|
||||
// We're in an async context, use spawn_blocking to avoid nested runtime
|
||||
let result = std::thread::spawn(
|
||||
move || -> Result<Result<T, ZinitError>, ServiceManagerError> {
|
||||
let rt = Runtime::new().map_err(|e| {
|
||||
ServiceManagerError::Other(format!("Failed to create runtime: {}", e))
|
||||
})?;
|
||||
Ok(rt.block_on(operation))
|
||||
},
|
||||
)
|
||||
.join()
|
||||
.map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?;
|
||||
result?.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||
} else {
|
||||
// No current runtime, use production-safe runtime
|
||||
let runtime = get_runtime()?;
|
||||
runtime
|
||||
.block_on(operation)
|
||||
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute an async operation with timeout using the shared runtime or current context
|
||||
fn execute_async_with_timeout<F, T>(
|
||||
&self,
|
||||
operation: F,
|
||||
timeout_secs: u64,
|
||||
) -> Result<T, ServiceManagerError>
|
||||
where
|
||||
F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static,
|
||||
T: Send + 'static,
|
||||
{
|
||||
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||
let timeout_op = timeout(timeout_duration, operation);
|
||||
|
||||
// Check if we're already in a tokio runtime context
|
||||
if let Ok(_handle) = tokio::runtime::Handle::try_current() {
|
||||
// We're in an async context, use spawn_blocking to avoid nested runtime
|
||||
let result = std::thread::spawn(move || {
|
||||
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||
rt.block_on(timeout_op)
|
||||
})
|
||||
.join()
|
||||
.map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?;
|
||||
|
||||
result
|
||||
.map_err(|_| {
|
||||
ServiceManagerError::Other(format!(
|
||||
"Operation timed out after {} seconds",
|
||||
timeout_secs
|
||||
))
|
||||
})?
|
||||
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||
} else {
|
||||
// No current runtime, use production-safe runtime
|
||||
let runtime = get_runtime()?;
|
||||
runtime
|
||||
.block_on(timeout_op)
|
||||
.map_err(|_| {
|
||||
ServiceManagerError::Other(format!(
|
||||
"Operation timed out after {} seconds",
|
||||
timeout_secs
|
||||
))
|
||||
})?
|
||||
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ServiceManager for ZinitServiceManager {
|
||||
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||
let status_res = self.status(service_name);
|
||||
match status_res {
|
||||
Ok(_) => Ok(true),
|
||||
Err(ServiceManagerError::ServiceNotFound(_)) => Ok(false),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||
// Build the exec command with args
|
||||
let mut exec_command = config.binary_path.clone();
|
||||
if !config.args.is_empty() {
|
||||
exec_command.push(' ');
|
||||
exec_command.push_str(&config.args.join(" "));
|
||||
}
|
||||
|
||||
// Create zinit-compatible service configuration
|
||||
let mut service_config = json!({
|
||||
"exec": exec_command,
|
||||
"oneshot": !config.auto_restart, // zinit uses oneshot, not restart
|
||||
"env": config.environment,
|
||||
});
|
||||
|
||||
// Add optional fields if present
|
||||
if let Some(ref working_dir) = config.working_directory {
|
||||
// Zinit doesn't support working_directory directly, so we need to modify the exec command
|
||||
let cd_command = format!("cd {} && {}", working_dir, exec_command);
|
||||
service_config["exec"] = json!(cd_command);
|
||||
}
|
||||
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name = config.name.clone();
|
||||
self.execute_async(
|
||||
async move { client.create_service(&service_name, service_config).await },
|
||||
)
|
||||
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||
|
||||
self.start_existing(&config.name)
|
||||
}
|
||||
|
||||
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name_owned = service_name.to_string();
|
||||
let service_name_for_error = service_name.to_string();
|
||||
self.execute_async(async move { client.start(&service_name_owned).await })
|
||||
.map_err(|e| ServiceManagerError::StartFailed(service_name_for_error, e.to_string()))
|
||||
}
|
||||
|
||||
fn start_and_confirm(
|
||||
&self,
|
||||
config: &ServiceConfig,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// Start the service first
|
||||
self.start(config)?;
|
||||
|
||||
// Wait for confirmation with timeout using the shared runtime
|
||||
self.execute_async_with_timeout(
|
||||
async move {
|
||||
let start_time = std::time::Instant::now();
|
||||
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||
|
||||
while start_time.elapsed() < timeout_duration {
|
||||
// We need to call status in a blocking way from within the async context
|
||||
// For now, we'll use a simple polling approach
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
// Return a timeout error that will be handled by execute_async_with_timeout
|
||||
// Use a generic error since we don't know the exact ZinitError variants
|
||||
Err(ZinitError::from(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"Timeout waiting for service confirmation",
|
||||
)))
|
||||
},
|
||||
timeout_secs,
|
||||
)?;
|
||||
|
||||
// Check final status
|
||||
match self.status(&config.name)? {
|
||||
ServiceStatus::Running => Ok(()),
|
||||
ServiceStatus::Failed => Err(ServiceManagerError::StartFailed(
|
||||
config.name.clone(),
|
||||
"Service failed to start".to_string(),
|
||||
)),
|
||||
_ => Err(ServiceManagerError::StartFailed(
|
||||
config.name.clone(),
|
||||
format!("Service did not start within {} seconds", timeout_secs),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_existing_and_confirm(
|
||||
&self,
|
||||
service_name: &str,
|
||||
timeout_secs: u64,
|
||||
) -> Result<(), ServiceManagerError> {
|
||||
// Start the existing service first
|
||||
self.start_existing(service_name)?;
|
||||
|
||||
// Wait for confirmation with timeout using the shared runtime
|
||||
self.execute_async_with_timeout(
|
||||
async move {
|
||||
let start_time = std::time::Instant::now();
|
||||
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||
|
||||
while start_time.elapsed() < timeout_duration {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
|
||||
// Return a timeout error that will be handled by execute_async_with_timeout
|
||||
// Use a generic error since we don't know the exact ZinitError variants
|
||||
Err(ZinitError::from(std::io::Error::new(
|
||||
std::io::ErrorKind::TimedOut,
|
||||
"Timeout waiting for service confirmation",
|
||||
)))
|
||||
},
|
||||
timeout_secs,
|
||||
)?;
|
||||
|
||||
// Check final status
|
||||
match self.status(service_name)? {
|
||||
ServiceStatus::Running => Ok(()),
|
||||
ServiceStatus::Failed => Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
"Service failed to start".to_string(),
|
||||
)),
|
||||
_ => Err(ServiceManagerError::StartFailed(
|
||||
service_name.to_string(),
|
||||
format!("Service did not start within {} seconds", timeout_secs),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name_owned = service_name.to_string();
|
||||
let service_name_for_error = service_name.to_string();
|
||||
self.execute_async(async move { client.stop(&service_name_owned).await })
|
||||
.map_err(|e| ServiceManagerError::StopFailed(service_name_for_error, e.to_string()))
|
||||
}
|
||||
|
||||
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name_owned = service_name.to_string();
|
||||
let service_name_for_error = service_name.to_string();
|
||||
self.execute_async(async move { client.restart(&service_name_owned).await })
|
||||
.map_err(|e| ServiceManagerError::RestartFailed(service_name_for_error, e.to_string()))
|
||||
}
|
||||
|
||||
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name_owned = service_name.to_string();
|
||||
let service_name_for_error = service_name.to_string();
|
||||
let status: ZinitServiceStatus = self
|
||||
.execute_async(async move { client.status(&service_name_owned).await })
|
||||
.map_err(|e| {
|
||||
// Check if this is a "service not found" error
|
||||
if e.to_string().contains("not found") || e.to_string().contains("does not exist") {
|
||||
ServiceManagerError::ServiceNotFound(service_name_for_error)
|
||||
} else {
|
||||
ServiceManagerError::Other(e.to_string())
|
||||
}
|
||||
})?;
|
||||
|
||||
// ServiceStatus is a struct with fields, not an enum
|
||||
// We need to check the state field to determine the status
|
||||
// Convert ServiceState to string and match on that
|
||||
let state_str = format!("{:?}", status.state).to_lowercase();
|
||||
let service_status = match state_str.as_str() {
|
||||
s if s.contains("running") => crate::ServiceStatus::Running,
|
||||
s if s.contains("stopped") => crate::ServiceStatus::Stopped,
|
||||
s if s.contains("failed") => crate::ServiceStatus::Failed,
|
||||
_ => crate::ServiceStatus::Unknown,
|
||||
};
|
||||
Ok(service_status)
|
||||
}
|
||||
|
||||
fn logs(
|
||||
&self,
|
||||
service_name: &str,
|
||||
_lines: Option<usize>,
|
||||
) -> Result<String, ServiceManagerError> {
|
||||
// The logs method takes (follow: bool, filter: Option<impl AsRef<str>>)
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name_owned = service_name.to_string();
|
||||
let logs = self
|
||||
.execute_async(async move {
|
||||
use futures::StreamExt;
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
let mut log_stream = client
|
||||
.logs(false, Some(service_name_owned.as_str()))
|
||||
.await?;
|
||||
let mut logs = Vec::new();
|
||||
|
||||
// Collect logs from the stream with a reasonable limit
|
||||
let mut count = 0;
|
||||
const MAX_LOGS: usize = 100;
|
||||
const LOG_TIMEOUT: Duration = Duration::from_secs(5);
|
||||
|
||||
// Use timeout to prevent hanging
|
||||
let result = timeout(LOG_TIMEOUT, async {
|
||||
while let Some(log_result) = log_stream.next().await {
|
||||
match log_result {
|
||||
Ok(log_entry) => {
|
||||
logs.push(format!("{:?}", log_entry));
|
||||
count += 1;
|
||||
if count >= MAX_LOGS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
// Handle timeout - this is not an error, just means no more logs available
|
||||
if result.is_err() {
|
||||
log::debug!(
|
||||
"Log reading timed out after {} seconds, returning {} logs",
|
||||
LOG_TIMEOUT.as_secs(),
|
||||
logs.len()
|
||||
);
|
||||
}
|
||||
|
||||
Ok::<Vec<String>, ZinitError>(logs)
|
||||
})
|
||||
.map_err(|e| {
|
||||
ServiceManagerError::LogsFailed(service_name.to_string(), e.to_string())
|
||||
})?;
|
||||
Ok(logs.join("\n"))
|
||||
}
|
||||
|
||||
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||
let client = Arc::clone(&self.client);
|
||||
let services = self
|
||||
.execute_async(async move { client.list().await })
|
||||
.map_err(|e| ServiceManagerError::Other(e.to_string()))?;
|
||||
Ok(services.keys().cloned().collect())
|
||||
}
|
||||
|
||||
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||
// Try to stop the service first, but don't fail if it's already stopped or doesn't exist
|
||||
if let Err(e) = self.stop(service_name) {
|
||||
// Log the error but continue with removal
|
||||
log::warn!(
|
||||
"Failed to stop service '{}' before removal: {}",
|
||||
service_name,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
let client = Arc::clone(&self.client);
|
||||
let service_name = service_name.to_string();
|
||||
self.execute_async(async move { client.delete_service(&service_name).await })
|
||||
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||
}
|
||||
}
|
243
_archive/service_manager/tests/factory_tests.rs
Normal file
243
_archive/service_manager/tests/factory_tests.rs
Normal file
@@ -0,0 +1,243 @@
|
||||
use sal_service_manager::{create_service_manager, ServiceConfig, ServiceManager};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
fn test_create_service_manager() {
|
||||
// Test that the factory function creates the appropriate service manager for the platform
|
||||
let manager = create_service_manager().expect("Failed to create service manager");
|
||||
|
||||
// Test basic functionality - should be able to call methods without panicking
|
||||
let list_result = manager.list();
|
||||
|
||||
// The result might be an error (if no service system is available), but it shouldn't panic
|
||||
match list_result {
|
||||
Ok(services) => {
|
||||
println!(
|
||||
"✓ Service manager created successfully, found {} services",
|
||||
services.len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✓ Service manager created, but got expected error: {}", e);
|
||||
// This is expected on systems without the appropriate service manager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_config_creation() {
|
||||
// Test creating various service configurations
|
||||
let basic_config = ServiceConfig {
|
||||
name: "test-service".to_string(),
|
||||
binary_path: "/usr/bin/echo".to_string(),
|
||||
args: vec!["hello".to_string(), "world".to_string()],
|
||||
working_directory: None,
|
||||
environment: HashMap::new(),
|
||||
auto_restart: false,
|
||||
};
|
||||
|
||||
assert_eq!(basic_config.name, "test-service");
|
||||
assert_eq!(basic_config.binary_path, "/usr/bin/echo");
|
||||
assert_eq!(basic_config.args.len(), 2);
|
||||
assert_eq!(basic_config.args[0], "hello");
|
||||
assert_eq!(basic_config.args[1], "world");
|
||||
assert!(basic_config.working_directory.is_none());
|
||||
assert!(basic_config.environment.is_empty());
|
||||
assert!(!basic_config.auto_restart);
|
||||
|
||||
println!("✓ Basic service config created successfully");
|
||||
|
||||
// Test config with environment variables
|
||||
let mut env = HashMap::new();
|
||||
env.insert("PATH".to_string(), "/usr/bin:/bin".to_string());
|
||||
env.insert("HOME".to_string(), "/tmp".to_string());
|
||||
|
||||
let env_config = ServiceConfig {
|
||||
name: "env-service".to_string(),
|
||||
binary_path: "/usr/bin/env".to_string(),
|
||||
args: vec![],
|
||||
working_directory: Some("/tmp".to_string()),
|
||||
environment: env.clone(),
|
||||
auto_restart: true,
|
||||
};
|
||||
|
||||
assert_eq!(env_config.name, "env-service");
|
||||
assert_eq!(env_config.binary_path, "/usr/bin/env");
|
||||
assert!(env_config.args.is_empty());
|
||||
assert_eq!(env_config.working_directory, Some("/tmp".to_string()));
|
||||
assert_eq!(env_config.environment.len(), 2);
|
||||
assert_eq!(
|
||||
env_config.environment.get("PATH"),
|
||||
Some(&"/usr/bin:/bin".to_string())
|
||||
);
|
||||
assert_eq!(
|
||||
env_config.environment.get("HOME"),
|
||||
Some(&"/tmp".to_string())
|
||||
);
|
||||
assert!(env_config.auto_restart);
|
||||
|
||||
println!("✓ Environment service config created successfully");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_config_clone() {
|
||||
// Test that ServiceConfig can be cloned
|
||||
let original_config = ServiceConfig {
|
||||
name: "original".to_string(),
|
||||
binary_path: "/bin/sh".to_string(),
|
||||
args: vec!["-c".to_string(), "echo test".to_string()],
|
||||
working_directory: Some("/home".to_string()),
|
||||
environment: {
|
||||
let mut env = HashMap::new();
|
||||
env.insert("TEST".to_string(), "value".to_string());
|
||||
env
|
||||
},
|
||||
auto_restart: true,
|
||||
};
|
||||
|
||||
let cloned_config = original_config.clone();
|
||||
|
||||
assert_eq!(original_config.name, cloned_config.name);
|
||||
assert_eq!(original_config.binary_path, cloned_config.binary_path);
|
||||
assert_eq!(original_config.args, cloned_config.args);
|
||||
assert_eq!(
|
||||
original_config.working_directory,
|
||||
cloned_config.working_directory
|
||||
);
|
||||
assert_eq!(original_config.environment, cloned_config.environment);
|
||||
assert_eq!(original_config.auto_restart, cloned_config.auto_restart);
|
||||
|
||||
println!("✓ Service config cloning works correctly");
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[test]
|
||||
fn test_macos_service_manager() {
|
||||
use sal_service_manager::LaunchctlServiceManager;
|
||||
|
||||
// Test creating macOS-specific service manager
|
||||
let manager = LaunchctlServiceManager::new();
|
||||
|
||||
// Test basic functionality
|
||||
let list_result = manager.list();
|
||||
match list_result {
|
||||
Ok(services) => {
|
||||
println!(
|
||||
"✓ macOS LaunchctlServiceManager created successfully, found {} services",
|
||||
services.len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"✓ macOS LaunchctlServiceManager created, but got expected error: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn test_linux_service_manager() {
|
||||
use sal_service_manager::SystemdServiceManager;
|
||||
|
||||
// Test creating Linux-specific service manager
|
||||
let manager = SystemdServiceManager::new();
|
||||
|
||||
// Test basic functionality
|
||||
let list_result = manager.list();
|
||||
match list_result {
|
||||
Ok(services) => {
|
||||
println!(
|
||||
"✓ Linux SystemdServiceManager created successfully, found {} services",
|
||||
services.len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(
|
||||
"✓ Linux SystemdServiceManager created, but got expected error: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_status_debug() {
|
||||
use sal_service_manager::ServiceStatus;
|
||||
|
||||
// Test that ServiceStatus can be debugged and cloned
|
||||
let statuses = vec![
|
||||
ServiceStatus::Running,
|
||||
ServiceStatus::Stopped,
|
||||
ServiceStatus::Failed,
|
||||
ServiceStatus::Unknown,
|
||||
];
|
||||
|
||||
for status in &statuses {
|
||||
let cloned = status.clone();
|
||||
let debug_str = format!("{:?}", status);
|
||||
|
||||
assert!(!debug_str.is_empty());
|
||||
assert_eq!(status, &cloned);
|
||||
|
||||
println!(
|
||||
"✓ ServiceStatus::{:?} debug and clone work correctly",
|
||||
status
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_manager_error_debug() {
|
||||
use sal_service_manager::ServiceManagerError;
|
||||
|
||||
// Test that ServiceManagerError can be debugged and displayed
|
||||
let errors = vec![
|
||||
ServiceManagerError::ServiceNotFound("test".to_string()),
|
||||
ServiceManagerError::ServiceAlreadyExists("test".to_string()),
|
||||
ServiceManagerError::StartFailed("test".to_string(), "reason".to_string()),
|
||||
ServiceManagerError::StopFailed("test".to_string(), "reason".to_string()),
|
||||
ServiceManagerError::RestartFailed("test".to_string(), "reason".to_string()),
|
||||
ServiceManagerError::LogsFailed("test".to_string(), "reason".to_string()),
|
||||
ServiceManagerError::Other("generic error".to_string()),
|
||||
];
|
||||
|
||||
for error in &errors {
|
||||
let debug_str = format!("{:?}", error);
|
||||
let display_str = format!("{}", error);
|
||||
|
||||
assert!(!debug_str.is_empty());
|
||||
assert!(!display_str.is_empty());
|
||||
|
||||
println!("✓ Error debug: {:?}", error);
|
||||
println!("✓ Error display: {}", error);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_service_manager_trait_object() {
|
||||
// Test that we can use ServiceManager as a trait object
|
||||
let manager: Box<dyn ServiceManager> =
|
||||
create_service_manager().expect("Failed to create service manager");
|
||||
|
||||
// Test that we can call methods through the trait object
|
||||
let list_result = manager.list();
|
||||
|
||||
match list_result {
|
||||
Ok(services) => {
|
||||
println!("✓ Trait object works, found {} services", services.len());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✓ Trait object works, got expected error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Test exists method
|
||||
let exists_result = manager.exists("non-existent-service");
|
||||
match exists_result {
|
||||
Ok(false) => println!("✓ Trait object exists method works correctly"),
|
||||
Ok(true) => println!("⚠ Unexpectedly found non-existent service"),
|
||||
Err(_) => println!("✓ Trait object exists method works (with error)"),
|
||||
}
|
||||
}
|
177
_archive/service_manager/tests/rhai/service_lifecycle.rhai
Normal file
177
_archive/service_manager/tests/rhai/service_lifecycle.rhai
Normal file
@@ -0,0 +1,177 @@
|
||||
// Service lifecycle management test script
|
||||
// This script tests REAL complete service lifecycle scenarios
|
||||
|
||||
print("=== Service Lifecycle Management Test ===");
|
||||
|
||||
// Create service manager
|
||||
let manager = create_service_manager();
|
||||
print("✓ Service manager created");
|
||||
|
||||
// Test configuration - real services for testing
|
||||
let test_services = [
|
||||
#{
|
||||
name: "lifecycle-test-1",
|
||||
binary_path: "/bin/echo",
|
||||
args: ["Lifecycle test 1"],
|
||||
working_directory: "/tmp",
|
||||
environment: #{},
|
||||
auto_restart: false
|
||||
},
|
||||
#{
|
||||
name: "lifecycle-test-2",
|
||||
binary_path: "/bin/echo",
|
||||
args: ["Lifecycle test 2"],
|
||||
working_directory: "/tmp",
|
||||
environment: #{ "TEST_VAR": "test_value" },
|
||||
auto_restart: false
|
||||
}
|
||||
];
|
||||
|
||||
let total_tests = 0;
|
||||
let passed_tests = 0;
|
||||
|
||||
// Test 1: Service Creation and Start
|
||||
print("\n1. Testing service creation and start...");
|
||||
for service_config in test_services {
|
||||
print(`\nStarting service: ${service_config.name}`);
|
||||
try {
|
||||
start(manager, service_config);
|
||||
print(` ✓ Service ${service_config.name} started successfully`);
|
||||
passed_tests += 1;
|
||||
} catch(e) {
|
||||
print(` ✗ Service ${service_config.name} start failed: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 2: Service Existence Check
|
||||
print("\n2. Testing service existence checks...");
|
||||
for service_config in test_services {
|
||||
print(`\nChecking existence of: ${service_config.name}`);
|
||||
try {
|
||||
let service_exists = exists(manager, service_config.name);
|
||||
if service_exists {
|
||||
print(` ✓ Service ${service_config.name} exists: ${service_exists}`);
|
||||
passed_tests += 1;
|
||||
} else {
|
||||
print(` ✗ Service ${service_config.name} doesn't exist after start`);
|
||||
}
|
||||
} catch(e) {
|
||||
print(` ✗ Existence check failed for ${service_config.name}: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 3: Status Check
|
||||
print("\n3. Testing status checks...");
|
||||
for service_config in test_services {
|
||||
print(`\nChecking status of: ${service_config.name}`);
|
||||
try {
|
||||
let service_status = status(manager, service_config.name);
|
||||
print(` ✓ Service ${service_config.name} status: ${service_status}`);
|
||||
passed_tests += 1;
|
||||
} catch(e) {
|
||||
print(` ✗ Status check failed for ${service_config.name}: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 4: Service List Check
|
||||
print("\n4. Testing service list...");
|
||||
try {
|
||||
let services = list(manager);
|
||||
print(` ✓ Service list retrieved (${services.len()} services)`);
|
||||
|
||||
// Check if our test services are in the list
|
||||
for service_config in test_services {
|
||||
let found = false;
|
||||
for service in services {
|
||||
if service.contains(service_config.name) {
|
||||
found = true;
|
||||
print(` ✓ Found ${service_config.name} in list`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
print(` ⚠ ${service_config.name} not found in service list`);
|
||||
}
|
||||
}
|
||||
passed_tests += 1;
|
||||
} catch(e) {
|
||||
print(` ✗ Service list failed: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
|
||||
// Test 5: Service Stop
|
||||
print("\n5. Testing service stop...");
|
||||
for service_config in test_services {
|
||||
print(`\nStopping service: ${service_config.name}`);
|
||||
try {
|
||||
stop(manager, service_config.name);
|
||||
print(` ✓ Service ${service_config.name} stopped successfully`);
|
||||
passed_tests += 1;
|
||||
} catch(e) {
|
||||
print(` ✗ Service ${service_config.name} stop failed: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 6: Service Removal
|
||||
print("\n6. Testing service removal...");
|
||||
for service_config in test_services {
|
||||
print(`\nRemoving service: ${service_config.name}`);
|
||||
try {
|
||||
remove(manager, service_config.name);
|
||||
print(` ✓ Service ${service_config.name} removed successfully`);
|
||||
passed_tests += 1;
|
||||
} catch(e) {
|
||||
print(` ✗ Service ${service_config.name} removal failed: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 7: Cleanup Verification
|
||||
print("\n7. Testing cleanup verification...");
|
||||
for service_config in test_services {
|
||||
print(`\nVerifying removal of: ${service_config.name}`);
|
||||
try {
|
||||
let exists_after_remove = exists(manager, service_config.name);
|
||||
if !exists_after_remove {
|
||||
print(` ✓ Service ${service_config.name} correctly doesn't exist after removal`);
|
||||
passed_tests += 1;
|
||||
} else {
|
||||
print(` ✗ Service ${service_config.name} still exists after removal`);
|
||||
}
|
||||
} catch(e) {
|
||||
print(` ✗ Cleanup verification failed for ${service_config.name}: ${e}`);
|
||||
}
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test Summary
|
||||
print("\n=== Lifecycle Test Summary ===");
|
||||
print(`Services tested: ${test_services.len()}`);
|
||||
print(`Total operations: ${total_tests}`);
|
||||
print(`Successful operations: ${passed_tests}`);
|
||||
print(`Failed operations: ${total_tests - passed_tests}`);
|
||||
print(`Success rate: ${(passed_tests * 100) / total_tests}%`);
|
||||
|
||||
if passed_tests == total_tests {
|
||||
print("\n🎉 All lifecycle tests passed!");
|
||||
print("Service manager is working correctly across all scenarios.");
|
||||
} else {
|
||||
print(`\n⚠ ${total_tests - passed_tests} test(s) failed`);
|
||||
print("Some service manager operations need attention.");
|
||||
}
|
||||
|
||||
print("\n=== Service Lifecycle Test Complete ===");
|
||||
|
||||
// Return test results
|
||||
#{
|
||||
summary: #{
|
||||
total_tests: total_tests,
|
||||
passed_tests: passed_tests,
|
||||
success_rate: (passed_tests * 100) / total_tests,
|
||||
services_tested: test_services.len()
|
||||
}
|
||||
}
|
218
_archive/service_manager/tests/rhai/service_manager_basic.rhai
Normal file
218
_archive/service_manager/tests/rhai/service_manager_basic.rhai
Normal file
@@ -0,0 +1,218 @@
|
||||
// Basic service manager functionality test script
|
||||
// This script tests the REAL service manager through Rhai integration
|
||||
|
||||
print("=== Service Manager Basic Functionality Test ===");
|
||||
|
||||
// Test configuration
|
||||
let test_service_name = "rhai-test-service";
|
||||
let test_binary = "/bin/echo";
|
||||
let test_args = ["Hello from Rhai service manager test"];
|
||||
|
||||
print(`Testing service: ${test_service_name}`);
|
||||
print(`Binary: ${test_binary}`);
|
||||
print(`Args: ${test_args}`);
|
||||
|
||||
// Test results tracking
|
||||
let test_results = #{
|
||||
creation: "NOT_RUN",
|
||||
exists_before: "NOT_RUN",
|
||||
start: "NOT_RUN",
|
||||
exists_after: "NOT_RUN",
|
||||
status: "NOT_RUN",
|
||||
list: "NOT_RUN",
|
||||
stop: "NOT_RUN",
|
||||
remove: "NOT_RUN",
|
||||
cleanup: "NOT_RUN"
|
||||
};
|
||||
|
||||
let passed_tests = 0;
|
||||
let total_tests = 0;
|
||||
|
||||
// Test 1: Service Manager Creation
|
||||
print("\n1. Testing service manager creation...");
|
||||
try {
|
||||
let manager = create_service_manager();
|
||||
print("✓ Service manager created successfully");
|
||||
test_results["creation"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service manager creation failed: ${e}`);
|
||||
test_results["creation"] = "FAIL";
|
||||
total_tests += 1;
|
||||
// Return early if we can't create the manager
|
||||
return test_results;
|
||||
}
|
||||
|
||||
// Create the service manager for all subsequent tests
|
||||
let manager = create_service_manager();
|
||||
|
||||
// Test 2: Check if service exists before creation
|
||||
print("\n2. Testing service existence check (before creation)...");
|
||||
try {
|
||||
let exists_before = exists(manager, test_service_name);
|
||||
print(`✓ Service existence check: ${exists_before}`);
|
||||
|
||||
if !exists_before {
|
||||
print("✓ Service correctly doesn't exist before creation");
|
||||
test_results["exists_before"] = "PASS";
|
||||
passed_tests += 1;
|
||||
} else {
|
||||
print("⚠ Service unexpectedly exists before creation");
|
||||
test_results["exists_before"] = "WARN";
|
||||
}
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service existence check failed: ${e}`);
|
||||
test_results["exists_before"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 3: Start the service
|
||||
print("\n3. Testing service start...");
|
||||
try {
|
||||
// Create a service configuration object
|
||||
let service_config = #{
|
||||
name: test_service_name,
|
||||
binary_path: test_binary,
|
||||
args: test_args,
|
||||
working_directory: "/tmp",
|
||||
environment: #{},
|
||||
auto_restart: false
|
||||
};
|
||||
|
||||
start(manager, service_config);
|
||||
print("✓ Service started successfully");
|
||||
test_results["start"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service start failed: ${e}`);
|
||||
test_results["start"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 4: Check if service exists after creation
|
||||
print("\n4. Testing service existence check (after creation)...");
|
||||
try {
|
||||
let exists_after = exists(manager, test_service_name);
|
||||
print(`✓ Service existence check: ${exists_after}`);
|
||||
|
||||
if exists_after {
|
||||
print("✓ Service correctly exists after creation");
|
||||
test_results["exists_after"] = "PASS";
|
||||
passed_tests += 1;
|
||||
} else {
|
||||
print("✗ Service doesn't exist after creation");
|
||||
test_results["exists_after"] = "FAIL";
|
||||
}
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service existence check failed: ${e}`);
|
||||
test_results["exists_after"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 5: Check service status
|
||||
print("\n5. Testing service status...");
|
||||
try {
|
||||
let service_status = status(manager, test_service_name);
|
||||
print(`✓ Service status: ${service_status}`);
|
||||
test_results["status"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service status check failed: ${e}`);
|
||||
test_results["status"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 6: List services
|
||||
print("\n6. Testing service list...");
|
||||
try {
|
||||
let services = list(manager);
|
||||
print("✓ Service list retrieved");
|
||||
|
||||
// Skip service search due to Rhai type constraints with Vec iteration
|
||||
print(" ⚠️ Skipping service search due to Rhai type constraints");
|
||||
|
||||
test_results["list"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service list failed: ${e}`);
|
||||
test_results["list"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 7: Stop the service
|
||||
print("\n7. Testing service stop...");
|
||||
try {
|
||||
stop(manager, test_service_name);
|
||||
print(`✓ Service stopped: ${test_service_name}`);
|
||||
test_results["stop"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service stop failed: ${e}`);
|
||||
test_results["stop"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 8: Remove the service
|
||||
print("\n8. Testing service remove...");
|
||||
try {
|
||||
remove(manager, test_service_name);
|
||||
print(`✓ Service removed: ${test_service_name}`);
|
||||
test_results["remove"] = "PASS";
|
||||
passed_tests += 1;
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Service remove failed: ${e}`);
|
||||
test_results["remove"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test 9: Verify cleanup
|
||||
print("\n9. Testing cleanup verification...");
|
||||
try {
|
||||
let exists_after_remove = exists(manager, test_service_name);
|
||||
if !exists_after_remove {
|
||||
print("✓ Service correctly doesn't exist after removal");
|
||||
test_results["cleanup"] = "PASS";
|
||||
passed_tests += 1;
|
||||
} else {
|
||||
print("✗ Service still exists after removal");
|
||||
test_results["cleanup"] = "FAIL";
|
||||
}
|
||||
total_tests += 1;
|
||||
} catch(e) {
|
||||
print(`✗ Cleanup verification failed: ${e}`);
|
||||
test_results["cleanup"] = "FAIL";
|
||||
total_tests += 1;
|
||||
}
|
||||
|
||||
// Test Summary
|
||||
print("\n=== Test Summary ===");
|
||||
print(`Total tests: ${total_tests}`);
|
||||
print(`Passed: ${passed_tests}`);
|
||||
print(`Failed: ${total_tests - passed_tests}`);
|
||||
print(`Success rate: ${(passed_tests * 100) / total_tests}%`);
|
||||
|
||||
print("\nDetailed Results:");
|
||||
for test_name in test_results.keys() {
|
||||
let result = test_results[test_name];
|
||||
let status_icon = if result == "PASS" { "✓" } else if result == "FAIL" { "✗" } else { "⚠" };
|
||||
print(` ${status_icon} ${test_name}: ${result}`);
|
||||
}
|
||||
|
||||
if passed_tests == total_tests {
|
||||
print("\n🎉 All tests passed!");
|
||||
} else {
|
||||
print(`\n⚠ ${total_tests - passed_tests} test(s) failed`);
|
||||
}
|
||||
|
||||
print("\n=== Service Manager Basic Test Complete ===");
|
||||
|
||||
// Return test results for potential use by calling code
|
||||
test_results
|
252
_archive/service_manager/tests/rhai_integration_tests.rs
Normal file
252
_archive/service_manager/tests/rhai_integration_tests.rs
Normal file
@@ -0,0 +1,252 @@
|
||||
use rhai::{Engine, EvalAltResult};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Helper function to create a Rhai engine for service manager testing
|
||||
fn create_service_manager_engine() -> Result<Engine, Box<EvalAltResult>> {
|
||||
#[cfg(feature = "rhai")]
|
||||
{
|
||||
let mut engine = Engine::new();
|
||||
// Register the service manager module for real testing
|
||||
sal_service_manager::rhai::register_service_manager_module(&mut engine)?;
|
||||
Ok(engine)
|
||||
}
|
||||
#[cfg(not(feature = "rhai"))]
|
||||
{
|
||||
Ok(Engine::new())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to run a Rhai script file
|
||||
fn run_rhai_script(script_path: &str) -> Result<rhai::Dynamic, Box<EvalAltResult>> {
|
||||
let engine = create_service_manager_engine()?;
|
||||
|
||||
// Read the script file
|
||||
let script_content = fs::read_to_string(script_path)
|
||||
.map_err(|e| format!("Failed to read script file {}: {}", script_path, e))?;
|
||||
|
||||
// Execute the script
|
||||
engine.eval::<rhai::Dynamic>(&script_content)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_service_manager_basic() {
|
||||
let script_path = "tests/rhai/service_manager_basic.rhai";
|
||||
|
||||
if !Path::new(script_path).exists() {
|
||||
println!("⚠ Skipping test: Rhai script not found at {}", script_path);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Running Rhai service manager basic test...");
|
||||
|
||||
match run_rhai_script(script_path) {
|
||||
Ok(result) => {
|
||||
println!("✓ Rhai basic test completed successfully");
|
||||
|
||||
// Try to extract test results if the script returns them
|
||||
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||
println!("Test results received from Rhai script:");
|
||||
for (key, value) in map.iter() {
|
||||
println!(" {}: {:?}", key, value);
|
||||
}
|
||||
|
||||
// Check if all tests passed
|
||||
let all_passed = map.values().all(|v| {
|
||||
if let Some(s) = v.clone().try_cast::<String>() {
|
||||
s == "PASS"
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if all_passed {
|
||||
println!("✓ All Rhai tests reported as PASS");
|
||||
} else {
|
||||
println!("⚠ Some Rhai tests did not pass");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Rhai basic test failed: {}", e);
|
||||
assert!(false, "Rhai script execution failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_service_lifecycle() {
|
||||
let script_path = "tests/rhai/service_lifecycle.rhai";
|
||||
|
||||
if !Path::new(script_path).exists() {
|
||||
println!("⚠ Skipping test: Rhai script not found at {}", script_path);
|
||||
return;
|
||||
}
|
||||
|
||||
println!("Running Rhai service lifecycle test...");
|
||||
|
||||
match run_rhai_script(script_path) {
|
||||
Ok(result) => {
|
||||
println!("✓ Rhai lifecycle test completed successfully");
|
||||
|
||||
// Try to extract test results if the script returns them
|
||||
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||
println!("Lifecycle test results received from Rhai script:");
|
||||
|
||||
// Extract summary if available
|
||||
if let Some(summary) = map.get("summary") {
|
||||
if let Some(summary_map) = summary.clone().try_cast::<rhai::Map>() {
|
||||
println!("Summary:");
|
||||
for (key, value) in summary_map.iter() {
|
||||
println!(" {}: {:?}", key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract performance metrics if available
|
||||
if let Some(performance) = map.get("performance") {
|
||||
if let Some(perf_map) = performance.clone().try_cast::<rhai::Map>() {
|
||||
println!("Performance:");
|
||||
for (key, value) in perf_map.iter() {
|
||||
println!(" {}: {:?}", key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Rhai lifecycle test failed: {}", e);
|
||||
assert!(false, "Rhai script execution failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_engine_functionality() {
|
||||
println!("Testing basic Rhai engine functionality...");
|
||||
|
||||
let engine = create_service_manager_engine().expect("Failed to create Rhai engine");
|
||||
|
||||
// Test basic Rhai functionality
|
||||
let test_script = r#"
|
||||
let test_results = #{
|
||||
basic_math: 2 + 2 == 4,
|
||||
string_ops: "hello".len() == 5,
|
||||
array_ops: [1, 2, 3].len() == 3,
|
||||
map_ops: #{ a: 1, b: 2 }.len() == 2
|
||||
};
|
||||
|
||||
let all_passed = true;
|
||||
for result in test_results.values() {
|
||||
if !result {
|
||||
all_passed = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#{
|
||||
results: test_results,
|
||||
all_passed: all_passed
|
||||
}
|
||||
"#;
|
||||
|
||||
match engine.eval::<rhai::Dynamic>(test_script) {
|
||||
Ok(result) => {
|
||||
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||
if let Some(all_passed) = map.get("all_passed") {
|
||||
if let Some(passed) = all_passed.clone().try_cast::<bool>() {
|
||||
if passed {
|
||||
println!("✓ All basic Rhai functionality tests passed");
|
||||
} else {
|
||||
println!("✗ Some basic Rhai functionality tests failed");
|
||||
assert!(false, "Basic Rhai tests failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(results) = map.get("results") {
|
||||
if let Some(results_map) = results.clone().try_cast::<rhai::Map>() {
|
||||
println!("Detailed results:");
|
||||
for (test_name, result) in results_map.iter() {
|
||||
let status = if let Some(passed) = result.clone().try_cast::<bool>() {
|
||||
if passed {
|
||||
"✓"
|
||||
} else {
|
||||
"✗"
|
||||
}
|
||||
} else {
|
||||
"?"
|
||||
};
|
||||
println!(" {} {}: {:?}", status, test_name, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✗ Basic Rhai functionality test failed: {}", e);
|
||||
assert!(false, "Basic Rhai test failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_script_error_handling() {
|
||||
println!("Testing Rhai error handling...");
|
||||
|
||||
let engine = create_service_manager_engine().expect("Failed to create Rhai engine");
|
||||
|
||||
// Test script with intentional error
|
||||
let error_script = r#"
|
||||
let result = "test";
|
||||
result.non_existent_method(); // This should cause an error
|
||||
"#;
|
||||
|
||||
match engine.eval::<rhai::Dynamic>(error_script) {
|
||||
Ok(_) => {
|
||||
println!("⚠ Expected error but script succeeded");
|
||||
assert!(
|
||||
false,
|
||||
"Error handling test failed - expected error but got success"
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("✓ Error correctly caught: {}", e);
|
||||
// Verify it's the expected type of error
|
||||
assert!(e.to_string().contains("method") || e.to_string().contains("function"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rhai_script_files_exist() {
|
||||
println!("Checking that Rhai test scripts exist...");
|
||||
|
||||
let script_files = [
|
||||
"tests/rhai/service_manager_basic.rhai",
|
||||
"tests/rhai/service_lifecycle.rhai",
|
||||
];
|
||||
|
||||
for script_file in &script_files {
|
||||
if Path::new(script_file).exists() {
|
||||
println!("✓ Found script: {}", script_file);
|
||||
|
||||
// Verify the file is readable and not empty
|
||||
match fs::read_to_string(script_file) {
|
||||
Ok(content) => {
|
||||
if content.trim().is_empty() {
|
||||
assert!(false, "Script file {} is empty", script_file);
|
||||
}
|
||||
println!(" Content length: {} characters", content.len());
|
||||
}
|
||||
Err(e) => {
|
||||
assert!(false, "Failed to read script file {}: {}", script_file, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert!(false, "Required script file not found: {}", script_file);
|
||||
}
|
||||
}
|
||||
|
||||
println!("✓ All required Rhai script files exist and are readable");
|
||||
}
|
317
_archive/service_manager/tests/zinit_integration_tests.rs
Normal file
317
_archive/service_manager/tests/zinit_integration_tests.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
use sal_service_manager::{
|
||||
ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus, ZinitServiceManager,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
/// Helper function to find an available Zinit socket path
|
||||
async fn get_available_socket_path() -> Option<String> {
|
||||
let socket_paths = [
|
||||
"/var/run/zinit.sock",
|
||||
"/tmp/zinit.sock",
|
||||
"/run/zinit.sock",
|
||||
"./zinit.sock",
|
||||
];
|
||||
|
||||
for path in &socket_paths {
|
||||
// Try to create a ZinitServiceManager to test connectivity
|
||||
if let Ok(manager) = ZinitServiceManager::new(path) {
|
||||
// Test if we can list services (basic connectivity test)
|
||||
if manager.list().is_ok() {
|
||||
println!("✓ Found working Zinit socket at: {}", path);
|
||||
return Some(path.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Helper function to clean up test services
|
||||
async fn cleanup_test_service(manager: &dyn ServiceManager, service_name: &str) {
|
||||
let _ = manager.stop(service_name);
|
||||
let _ = manager.remove(service_name);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_zinit_service_manager_creation() {
|
||||
if let Some(socket_path) = get_available_socket_path().await {
|
||||
let manager = ZinitServiceManager::new(&socket_path);
|
||||
assert!(
|
||||
manager.is_ok(),
|
||||
"Should be able to create ZinitServiceManager"
|
||||
);
|
||||
|
||||
let manager = manager.unwrap();
|
||||
|
||||
// Test basic connectivity by listing services
|
||||
let list_result = manager.list();
|
||||
assert!(list_result.is_ok(), "Should be able to list services");
|
||||
|
||||
println!("✓ ZinitServiceManager created successfully");
|
||||
} else {
|
||||
println!("⚠ Skipping test_zinit_service_manager_creation: No Zinit socket available");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_service_lifecycle() {
|
||||
if let Some(socket_path) = get_available_socket_path().await {
|
||||
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||
let service_name = "test-lifecycle-service";
|
||||
|
||||
// Clean up any existing service first
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
|
||||
let config = ServiceConfig {
|
||||
name: service_name.to_string(),
|
||||
binary_path: "echo".to_string(),
|
||||
args: vec!["Hello from lifecycle test".to_string()],
|
||||
working_directory: Some("/tmp".to_string()),
|
||||
environment: HashMap::new(),
|
||||
auto_restart: false,
|
||||
};
|
||||
|
||||
// Test service creation and start
|
||||
println!("Testing service creation and start...");
|
||||
let start_result = manager.start(&config);
|
||||
match start_result {
|
||||
Ok(_) => {
|
||||
println!("✓ Service started successfully");
|
||||
|
||||
// Wait a bit for the service to run
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
// Test service exists
|
||||
let exists = manager.exists(service_name);
|
||||
assert!(exists.is_ok(), "Should be able to check if service exists");
|
||||
|
||||
if let Ok(true) = exists {
|
||||
println!("✓ Service exists check passed");
|
||||
|
||||
// Test service status
|
||||
let status_result = manager.status(service_name);
|
||||
match status_result {
|
||||
Ok(status) => {
|
||||
println!("✓ Service status: {:?}", status);
|
||||
assert!(
|
||||
matches!(status, ServiceStatus::Running | ServiceStatus::Stopped),
|
||||
"Service should be running or stopped (for oneshot)"
|
||||
);
|
||||
}
|
||||
Err(e) => println!("⚠ Status check failed: {}", e),
|
||||
}
|
||||
|
||||
// Test service logs
|
||||
let logs_result = manager.logs(service_name, None);
|
||||
match logs_result {
|
||||
Ok(logs) => {
|
||||
println!("✓ Retrieved logs: {}", logs.len());
|
||||
// For echo command, we should have some output
|
||||
assert!(
|
||||
!logs.is_empty() || logs.contains("Hello"),
|
||||
"Should have log output"
|
||||
);
|
||||
}
|
||||
Err(e) => println!("⚠ Logs retrieval failed: {}", e),
|
||||
}
|
||||
|
||||
// Test service list
|
||||
let list_result = manager.list();
|
||||
match list_result {
|
||||
Ok(services) => {
|
||||
println!("✓ Listed {} services", services.len());
|
||||
assert!(
|
||||
services.contains(&service_name.to_string()),
|
||||
"Service should appear in list"
|
||||
);
|
||||
}
|
||||
Err(e) => println!("⚠ List services failed: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
// Test service stop
|
||||
println!("Testing service stop...");
|
||||
let stop_result = manager.stop(service_name);
|
||||
match stop_result {
|
||||
Ok(_) => println!("✓ Service stopped successfully"),
|
||||
Err(e) => println!("⚠ Stop failed: {}", e),
|
||||
}
|
||||
|
||||
// Test service removal
|
||||
println!("Testing service removal...");
|
||||
let remove_result = manager.remove(service_name);
|
||||
match remove_result {
|
||||
Ok(_) => println!("✓ Service removed successfully"),
|
||||
Err(e) => println!("⚠ Remove failed: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("⚠ Service creation/start failed: {}", e);
|
||||
// This might be expected if zinit doesn't allow service creation
|
||||
}
|
||||
}
|
||||
|
||||
// Final cleanup
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
} else {
|
||||
println!("⚠ Skipping test_service_lifecycle: No Zinit socket available");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_service_start_and_confirm() {
|
||||
if let Some(socket_path) = get_available_socket_path().await {
|
||||
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||
let service_name = "test-start-confirm-service";
|
||||
|
||||
// Clean up any existing service first
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
|
||||
let config = ServiceConfig {
|
||||
name: service_name.to_string(),
|
||||
binary_path: "sleep".to_string(),
|
||||
args: vec!["5".to_string()], // Sleep for 5 seconds
|
||||
working_directory: Some("/tmp".to_string()),
|
||||
environment: HashMap::new(),
|
||||
auto_restart: false,
|
||||
};
|
||||
|
||||
// Test start_and_confirm with timeout
|
||||
println!("Testing start_and_confirm with timeout...");
|
||||
let start_result = manager.start_and_confirm(&config, 10);
|
||||
match start_result {
|
||||
Ok(_) => {
|
||||
println!("✓ Service started and confirmed successfully");
|
||||
|
||||
// Verify it's actually running
|
||||
let status = manager.status(service_name);
|
||||
match status {
|
||||
Ok(ServiceStatus::Running) => println!("✓ Service confirmed running"),
|
||||
Ok(other_status) => {
|
||||
println!("⚠ Service in unexpected state: {:?}", other_status)
|
||||
}
|
||||
Err(e) => println!("⚠ Status check failed: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("⚠ start_and_confirm failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Test start_existing_and_confirm
|
||||
println!("Testing start_existing_and_confirm...");
|
||||
let start_existing_result = manager.start_existing_and_confirm(service_name, 5);
|
||||
match start_existing_result {
|
||||
Ok(_) => println!("✓ start_existing_and_confirm succeeded"),
|
||||
Err(e) => println!("⚠ start_existing_and_confirm failed: {}", e),
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
} else {
|
||||
println!("⚠ Skipping test_service_start_and_confirm: No Zinit socket available");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_service_restart() {
|
||||
if let Some(socket_path) = get_available_socket_path().await {
|
||||
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||
let service_name = "test-restart-service";
|
||||
|
||||
// Clean up any existing service first
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
|
||||
let config = ServiceConfig {
|
||||
name: service_name.to_string(),
|
||||
binary_path: "echo".to_string(),
|
||||
args: vec!["Restart test".to_string()],
|
||||
working_directory: Some("/tmp".to_string()),
|
||||
environment: HashMap::new(),
|
||||
auto_restart: true, // Enable auto-restart for this test
|
||||
};
|
||||
|
||||
// Start the service first
|
||||
let start_result = manager.start(&config);
|
||||
if start_result.is_ok() {
|
||||
// Wait for service to be established
|
||||
sleep(Duration::from_millis(1000)).await;
|
||||
|
||||
// Test restart
|
||||
println!("Testing service restart...");
|
||||
let restart_result = manager.restart(service_name);
|
||||
match restart_result {
|
||||
Ok(_) => {
|
||||
println!("✓ Service restarted successfully");
|
||||
|
||||
// Wait and check status
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
let status_result = manager.status(service_name);
|
||||
match status_result {
|
||||
Ok(status) => {
|
||||
println!("✓ Service state after restart: {:?}", status);
|
||||
}
|
||||
Err(e) => println!("⚠ Status check after restart failed: {}", e),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("⚠ Restart failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_service(&manager, service_name).await;
|
||||
} else {
|
||||
println!("⚠ Skipping test_service_restart: No Zinit socket available");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling() {
|
||||
if let Some(socket_path) = get_available_socket_path().await {
|
||||
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||
|
||||
// Test operations on non-existent service
|
||||
let non_existent_service = "non-existent-service-12345";
|
||||
|
||||
// Test status of non-existent service
|
||||
let status_result = manager.status(non_existent_service);
|
||||
match status_result {
|
||||
Err(ServiceManagerError::ServiceNotFound(_)) => {
|
||||
println!("✓ Correctly returned ServiceNotFound for non-existent service");
|
||||
}
|
||||
Err(other_error) => {
|
||||
println!(
|
||||
"⚠ Got different error for non-existent service: {}",
|
||||
other_error
|
||||
);
|
||||
}
|
||||
Ok(_) => {
|
||||
println!("⚠ Unexpectedly found non-existent service");
|
||||
}
|
||||
}
|
||||
|
||||
// Test exists for non-existent service
|
||||
let exists_result = manager.exists(non_existent_service);
|
||||
match exists_result {
|
||||
Ok(false) => println!("✓ Correctly reported non-existent service as not existing"),
|
||||
Ok(true) => println!("⚠ Incorrectly reported non-existent service as existing"),
|
||||
Err(e) => println!("⚠ Error checking existence: {}", e),
|
||||
}
|
||||
|
||||
// Test stop of non-existent service
|
||||
let stop_result = manager.stop(non_existent_service);
|
||||
match stop_result {
|
||||
Err(_) => println!("✓ Correctly failed to stop non-existent service"),
|
||||
Ok(_) => println!("⚠ Unexpectedly succeeded in stopping non-existent service"),
|
||||
}
|
||||
|
||||
println!("✓ Error handling tests completed");
|
||||
} else {
|
||||
println!("⚠ Skipping test_error_handling: No Zinit socket available");
|
||||
}
|
||||
}
|
@@ -6,10 +6,12 @@ cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
rm -f ./target/debug/herodo
|
||||
|
||||
# Build the herodo project
|
||||
echo "Building herodo..."
|
||||
cargo build --bin herodo
|
||||
# cargo build --release --bin herodo
|
||||
# Build the herodo project from the herodo package
|
||||
echo "Building herodo from herodo package..."
|
||||
cd herodo
|
||||
cargo build
|
||||
# cargo build --release
|
||||
cd ..
|
||||
|
||||
# Check if the build was successful
|
||||
if [ $? -ne 0 ]; then
|
||||
@@ -20,8 +22,14 @@ fi
|
||||
# Echo a success message
|
||||
echo "Build successful!"
|
||||
|
||||
mkdir -p ~/hero/bin/
|
||||
cp target/debug/herodo ~/hero/bin/herodo
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "Running as root, copying to /usr/local/bin/"
|
||||
cp target/debug/herodo /usr/local/bin/herodo
|
||||
else
|
||||
echo "Running as non-root user, copying to ~/hero/bin/"
|
||||
mkdir -p ~/hero/bin/
|
||||
cp target/debug/herodo ~/hero/bin/herodo
|
||||
fi
|
||||
|
||||
# Check if a script name was provided
|
||||
if [ $# -eq 1 ]; then
|
||||
|
0
cargo_instructions.md
Normal file
0
cargo_instructions.md
Normal file
105
docs/docs/rhai/buildah_module_tests.md
Normal file
105
docs/docs/rhai/buildah_module_tests.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Buildah Module Tests
|
||||
|
||||
This document describes the test scripts for the Buildah module in the SAL library. These tests verify the functionality of the Buildah module's container and image operations.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into three main scripts:
|
||||
|
||||
1. **Builder Pattern** (`01_builder_pattern.rhai`): Tests for the Builder pattern, including creating containers, running commands, and working with container content.
|
||||
2. **Image Operations** (`02_image_operations.rhai`): Tests for image-related operations like pulling, tagging, listing, and removing images.
|
||||
3. **Container Operations** (`03_container_operations.rhai`): Tests for container-related operations like configuration, isolation, and content management.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/buildah/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/buildah/01_builder_pattern.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Builder Pattern Test
|
||||
|
||||
The Builder Pattern test (`01_builder_pattern.rhai`) verifies the following functions:
|
||||
|
||||
- `bah_new`: Creating a new Builder with a container from a specified image
|
||||
- Builder properties: `container_id`, `name`, `image`, `debug_mode`
|
||||
- `run`: Running commands in the container
|
||||
- `write_content`: Writing content to files in the container
|
||||
- `read_content`: Reading content from files in the container
|
||||
- `set_entrypoint`: Setting the container's entrypoint
|
||||
- `set_cmd`: Setting the container's command
|
||||
- `add`: Adding files to the container
|
||||
- `copy`: Copying files to the container
|
||||
- `commit`: Committing the container to an image
|
||||
- `remove`: Removing the container
|
||||
- `images`: Listing images
|
||||
- `image_remove`: Removing images
|
||||
|
||||
### Image Operations Test
|
||||
|
||||
The Image Operations test (`02_image_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `image_pull`: Pulling images from registries
|
||||
- `image_tag`: Tagging images
|
||||
- `images`: Listing images
|
||||
- `build`: Building images from Dockerfiles
|
||||
- `image_remove`: Removing images
|
||||
|
||||
The test creates a temporary directory with a Dockerfile for testing the build functionality.
|
||||
|
||||
### Container Operations Test
|
||||
|
||||
The Container Operations test (`03_container_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `reset`: Resetting a Builder by removing its container
|
||||
- `config`: Configuring container properties
|
||||
- `run_with_isolation`: Running commands with isolation
|
||||
- Content operations: Creating and executing scripts in the container
|
||||
- `commit` with options: Committing a container with additional configuration
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Checks if Buildah is available before running tests
|
||||
2. Skips tests if Buildah is not available
|
||||
3. Contains simplified versions of each test
|
||||
4. Runs each test in a try/catch block to handle errors
|
||||
5. Catches and reports any errors
|
||||
6. Provides a summary of passed, failed, and skipped tests
|
||||
|
||||
## Buildah Requirements
|
||||
|
||||
These tests require the Buildah tool to be installed and available in the system's PATH. The tests will check for Buildah's availability and skip the tests if it's not found, rather than failing.
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/buildah` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Buildah module:
|
||||
|
||||
1. Always check if Buildah is available before running tests
|
||||
2. Use unique names for containers and images to avoid conflicts
|
||||
3. Clean up any containers, images, or files created during testing
|
||||
4. Use assertions to verify expected behavior
|
||||
5. Print clear messages about what's being tested
|
||||
6. Handle errors gracefully
|
||||
7. Make tests independent of each other
|
||||
8. Keep tests focused on specific functionality
|
71
docs/docs/rhai/ci_workflow.md
Normal file
71
docs/docs/rhai/ci_workflow.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Continuous Integration for Rhai Tests
|
||||
|
||||
This document describes the continuous integration (CI) workflow for running Rhai tests in the SAL library.
|
||||
|
||||
## GitHub Actions Workflow
|
||||
|
||||
The SAL project includes a GitHub Actions workflow that automatically runs all Rhai tests whenever changes are made to relevant files. This ensures that the Rhai integration continues to work correctly as the codebase evolves.
|
||||
|
||||
### Workflow File
|
||||
|
||||
The workflow is defined in `.github/workflows/rhai-tests.yml`.
|
||||
|
||||
### Trigger Events
|
||||
|
||||
The workflow runs automatically when:
|
||||
|
||||
1. Changes are pushed to the `main` or `master` branch that affect:
|
||||
- Rhai test scripts (`src/rhai_tests/**`)
|
||||
- Rhai module code (`src/rhai/**`)
|
||||
- Git module code (`src/git/**`)
|
||||
- OS module code (`src/os/**`)
|
||||
- The test runner script (`run_rhai_tests.sh`)
|
||||
- The workflow file itself (`.github/workflows/rhai-tests.yml`)
|
||||
|
||||
2. A pull request is opened or updated that affects the same files.
|
||||
|
||||
3. The workflow is manually triggered using the GitHub Actions interface.
|
||||
|
||||
### Workflow Steps
|
||||
|
||||
The workflow performs the following steps:
|
||||
|
||||
1. **Checkout Code**: Checks out the repository code.
|
||||
2. **Set up Rust**: Installs the Rust toolchain.
|
||||
3. **Cache Dependencies**: Caches Rust dependencies to speed up builds.
|
||||
4. **Build herodo**: Builds the `herodo` binary used to run Rhai scripts.
|
||||
5. **Install Dependencies**: Installs system dependencies like Git and curl.
|
||||
6. **Run Rhai Tests**: Runs the `run_rhai_tests.sh` script to execute all Rhai tests.
|
||||
7. **Check for Failures**: Verifies that all tests passed.
|
||||
|
||||
### Test Results
|
||||
|
||||
The workflow will fail if any Rhai test fails. This prevents changes that break the Rhai integration from being merged.
|
||||
|
||||
## Local Testing
|
||||
|
||||
Before pushing changes, you can run the same tests locally using the `run_rhai_tests.sh` script:
|
||||
|
||||
```bash
|
||||
./run_rhai_tests.sh
|
||||
```
|
||||
|
||||
This will produce the same test results as the CI workflow, allowing you to catch and fix issues before pushing your changes.
|
||||
|
||||
## Logs
|
||||
|
||||
The test runner script creates a log file (`run_rhai_tests.log`) that contains the output of all tests. This log is used by the CI workflow to check for test failures.
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
When adding new tests, make sure they are included in the appropriate module's test runner script (`run_all_tests.rhai`). The CI workflow will automatically run the new tests.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If the CI workflow fails, check the GitHub Actions logs for details. Common issues include:
|
||||
|
||||
1. **Missing Dependencies**: Ensure all required dependencies are installed.
|
||||
2. **Test Failures**: Fix any failing tests.
|
||||
3. **Build Errors**: Fix any errors in the Rust code.
|
||||
|
||||
If you need to modify the workflow, edit the `.github/workflows/rhai-tests.yml` file.
|
81
docs/docs/rhai/git_module_tests.md
Normal file
81
docs/docs/rhai/git_module_tests.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Git Module Tests
|
||||
|
||||
This document describes the test scripts for the Git module in the SAL library. These tests verify the functionality of the Git module's repository management and Git operations.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into two main scripts:
|
||||
|
||||
1. **Basic Git Operations** (`01_git_basic.rhai`): Tests basic Git functionality like creating a GitTree, listing repositories, finding repositories, and cloning repositories.
|
||||
2. **Git Repository Operations** (`02_git_operations.rhai`): Tests Git operations like pull, reset, commit, and push.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path git/tests/rhai/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path git/tests/rhai/01_git_basic.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Basic Git Operations Test
|
||||
|
||||
The basic Git operations test (`01_git_basic.rhai`) verifies the following functions:
|
||||
|
||||
- `git_tree_new`: Creating a GitTree
|
||||
- `list`: Listing repositories in a GitTree
|
||||
- `find`: Finding repositories matching a pattern
|
||||
- `get`: Getting or cloning a repository
|
||||
- `path`: Getting the path of a repository
|
||||
- `has_changes`: Checking if a repository has changes
|
||||
|
||||
The test creates a temporary directory, performs operations on it, and then cleans up after itself.
|
||||
|
||||
### Git Repository Operations Test
|
||||
|
||||
The Git repository operations test (`02_git_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `pull`: Pulling changes from a remote repository
|
||||
- `reset`: Resetting local changes
|
||||
- `commit`: Committing changes (method existence only)
|
||||
- `push`: Pushing changes to a remote repository (method existence only)
|
||||
|
||||
Note: The test does not actually commit or push changes to avoid modifying remote repositories. It only verifies that the methods exist and can be called.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Contains simplified versions of each test
|
||||
2. Runs each test in a try/catch block to handle errors
|
||||
3. Catches and reports any errors
|
||||
4. Provides a summary of passed and failed tests
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/git` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Git module:
|
||||
|
||||
1. Always clean up temporary files and directories
|
||||
2. Use assertions to verify expected behavior
|
||||
3. Print clear messages about what's being tested
|
||||
4. Handle errors gracefully
|
||||
5. Make tests independent of each other
|
||||
6. Avoid tests that modify remote repositories
|
||||
7. Keep tests focused on specific functionality
|
85
docs/docs/rhai/index.md
Normal file
85
docs/docs/rhai/index.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Rhai Scripting in SAL
|
||||
|
||||
This documentation covers the Rhai scripting integration in the SAL (System Abstraction Layer) library.
|
||||
|
||||
## Overview
|
||||
|
||||
SAL provides integration with the [Rhai scripting language](https://rhai.rs/), allowing you to use SAL's functionality in scripts. This enables automation of system tasks, testing, and more complex operations without having to write Rust code.
|
||||
|
||||
## Modules
|
||||
|
||||
SAL exposes the following modules to Rhai scripts:
|
||||
|
||||
- [OS Module](os_module_tests.md): File system operations, downloads, and package management
|
||||
- Process Module: Process management and command execution
|
||||
- Git Module: Git repository operations
|
||||
- Text Module: Text processing utilities
|
||||
- Buildah Module: Container image building
|
||||
- Nerdctl Module: Container runtime operations
|
||||
- RFS Module: Remote file system operations
|
||||
- Redis Client Module: Redis database connection and operations
|
||||
- PostgreSQL Client Module: PostgreSQL database connection and operations
|
||||
|
||||
## Running Rhai Scripts
|
||||
|
||||
You can run Rhai scripts using the `herodo` binary:
|
||||
|
||||
```bash
|
||||
herodo --path path/to/script.rhai
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
SAL includes test scripts for verifying the functionality of its Rhai integration. These tests are located in the `src/rhai_tests` directory and are organized by module.
|
||||
|
||||
- [OS Module Tests](os_module_tests.md): Tests for file system, download, and package management operations
|
||||
- [Git Module Tests](git_module_tests.md): Tests for Git repository management and operations
|
||||
- [Process Module Tests](process_module_tests.md): Tests for command execution and process management
|
||||
- [Redis Client Module Tests](redisclient_module_tests.md): Tests for Redis connection and operations
|
||||
- [PostgreSQL Client Module Tests](postgresclient_module_tests.md): Tests for PostgreSQL connection and operations
|
||||
- [Text Module Tests](text_module_tests.md): Tests for text manipulation, normalization, replacement, and template rendering
|
||||
- [Buildah Module Tests](buildah_module_tests.md): Tests for container and image operations
|
||||
- [Nerdctl Module Tests](nerdctl_module_tests.md): Tests for container and image operations using nerdctl
|
||||
- [RFS Module Tests](rfs_module_tests.md): Tests for remote filesystem operations and filesystem layers
|
||||
- [Running Tests](running_tests.md): Instructions for running all Rhai tests
|
||||
- [CI Workflow](ci_workflow.md): Continuous integration workflow for Rhai tests
|
||||
|
||||
## Examples
|
||||
|
||||
For examples of how to use SAL's Rhai integration, see the `examples` directory in the project root. These examples demonstrate various features and use cases.
|
||||
|
||||
## Writing Your Own Scripts
|
||||
|
||||
When writing Rhai scripts that use SAL:
|
||||
|
||||
1. Import the necessary modules (they're automatically registered)
|
||||
2. Use the functions provided by each module
|
||||
3. Handle errors appropriately
|
||||
4. Clean up resources when done
|
||||
|
||||
Example:
|
||||
|
||||
```rhai
|
||||
// Simple example of using the OS module
|
||||
let test_dir = "my_test_dir";
|
||||
mkdir(test_dir);
|
||||
|
||||
if exist(test_dir) {
|
||||
print(`Directory ${test_dir} created successfully`);
|
||||
|
||||
// Create a file
|
||||
let test_file = test_dir + "/test.txt";
|
||||
file_write(test_file, "Hello, world!");
|
||||
|
||||
// Read the file
|
||||
let content = file_read(test_file);
|
||||
print(`File content: ${content}`);
|
||||
|
||||
// Clean up
|
||||
delete(test_dir);
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
For detailed information about the functions available in each module, refer to the module-specific documentation.
|
386
docs/docs/rhai/mycelium_tutorial.md
Normal file
386
docs/docs/rhai/mycelium_tutorial.md
Normal file
@@ -0,0 +1,386 @@
|
||||
# Mycelium Tutorial for Rhai
|
||||
|
||||
This tutorial explains how to use the Mycelium networking functionality in Rhai scripts. Mycelium is a peer-to-peer networking system that allows nodes to communicate with each other, and the Rhai bindings provide an easy way to interact with Mycelium from your scripts.
|
||||
|
||||
## Introduction
|
||||
|
||||
The Mycelium module for Rhai provides the following capabilities:
|
||||
|
||||
- Getting node information
|
||||
- Managing peers (listing, adding, removing)
|
||||
- Viewing routing information
|
||||
- Sending and receiving messages between nodes
|
||||
|
||||
This tutorial will walk you through using these features with example scripts.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before using the Mycelium functionality in Rhai, you need:
|
||||
|
||||
1. A running Mycelium node accessible via HTTP
|
||||
> See https://github.com/threefoldtech/mycelium
|
||||
2. The Rhai runtime with Mycelium module enabled
|
||||
|
||||
## Basic Mycelium Operations
|
||||
|
||||
Let's start by exploring the basic operations available in Mycelium using the `mycelium_basic.rhai` example.
|
||||
|
||||
### Getting Node Information
|
||||
|
||||
To get information about your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:8989";
|
||||
|
||||
// Get node information
|
||||
print("Getting node information:");
|
||||
try {
|
||||
let node_info = mycelium_get_node_info(api_url);
|
||||
print(`Node subnet: ${node_info.nodeSubnet}`);
|
||||
print(`Node public key: ${node_info.nodePubkey}`);
|
||||
} catch(err) {
|
||||
print(`Error getting node info: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Sets the API URL for your Mycelium node
|
||||
2. Calls `mycelium_get_node_info()` to retrieve information about the node
|
||||
3. Prints the node's subnet and public key
|
||||
|
||||
### Managing Peers
|
||||
|
||||
#### Listing Peers
|
||||
|
||||
To list all peers connected to your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// List all peers
|
||||
print("\nListing all peers:");
|
||||
try {
|
||||
let peers = mycelium_list_peers(api_url);
|
||||
|
||||
if peers.is_empty() {
|
||||
print("No peers connected.");
|
||||
} else {
|
||||
for peer in peers {
|
||||
print(`Peer Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socketAddr}`);
|
||||
print(` Type: ${peer.type}`);
|
||||
print(` Connection State: ${peer.connectionState}`);
|
||||
print(` Bytes sent: ${peer.txBytes}`);
|
||||
print(` Bytes received: ${peer.rxBytes}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing peers: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Calls `mycelium_list_peers()` to get all connected peers
|
||||
2. Iterates through the peers and prints their details
|
||||
|
||||
#### Adding a Peer
|
||||
|
||||
To add a new peer to your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// Add a new peer
|
||||
print("\nAdding a new peer:");
|
||||
let new_peer_address = "tcp://65.21.231.58:9651";
|
||||
try {
|
||||
let result = mycelium_add_peer(api_url, new_peer_address);
|
||||
print(`Peer added: ${result.success}`);
|
||||
} catch(err) {
|
||||
print(`Error adding peer: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Specifies a peer address to add
|
||||
2. Calls `mycelium_add_peer()` to add the peer to your node
|
||||
3. Prints whether the operation was successful
|
||||
|
||||
#### Removing a Peer
|
||||
|
||||
To remove a peer from your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// Remove a peer
|
||||
print("\nRemoving a peer:");
|
||||
let peer_id = "tcp://65.21.231.58:9651"; // This is the peer we added earlier
|
||||
try {
|
||||
let result = mycelium_remove_peer(api_url, peer_id);
|
||||
print(`Peer removed: ${result.success}`);
|
||||
} catch(err) {
|
||||
print(`Error removing peer: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Specifies the peer ID to remove
|
||||
2. Calls `mycelium_remove_peer()` to remove the peer
|
||||
3. Prints whether the operation was successful
|
||||
|
||||
### Viewing Routing Information
|
||||
|
||||
#### Listing Selected Routes
|
||||
|
||||
To list the selected routes in your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// List selected routes
|
||||
print("\nListing selected routes:");
|
||||
try {
|
||||
let routes = mycelium_list_selected_routes(api_url);
|
||||
|
||||
if routes.is_empty() {
|
||||
print("No selected routes.");
|
||||
} else {
|
||||
for route in routes {
|
||||
print(`Subnet: ${route.subnet}`);
|
||||
print(` Next hop: ${route.nextHop}`);
|
||||
print(` Metric: ${route.metric}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing routes: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Calls `mycelium_list_selected_routes()` to get all selected routes
|
||||
2. Iterates through the routes and prints their details
|
||||
|
||||
#### Listing Fallback Routes
|
||||
|
||||
To list the fallback routes in your Mycelium node:
|
||||
|
||||
```rhai
|
||||
// List fallback routes
|
||||
print("\nListing fallback routes:");
|
||||
try {
|
||||
let routes = mycelium_list_fallback_routes(api_url);
|
||||
|
||||
if routes.is_empty() {
|
||||
print("No fallback routes.");
|
||||
} else {
|
||||
for route in routes {
|
||||
print(`Subnet: ${route.subnet}`);
|
||||
print(` Next hop: ${route.nextHop}`);
|
||||
print(` Metric: ${route.metric}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing fallback routes: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Calls `mycelium_list_fallback_routes()` to get all fallback routes
|
||||
2. Iterates through the routes and prints their details
|
||||
|
||||
## Sending Messages
|
||||
|
||||
Now let's look at how to send messages using the `mycelium_send_message.rhai` example.
|
||||
|
||||
```rhai
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:1111";
|
||||
|
||||
// Send a message
|
||||
print("\nSending a message:");
|
||||
let destination = "5af:ae6b:dcd8:ffdb:b71:7dde:d3:1033"; // Replace with the actual destination IP address
|
||||
let topic = "test_topic";
|
||||
let message = "Hello from Rhai sender!";
|
||||
let deadline_secs = -10; // Seconds we wait for a reply
|
||||
|
||||
try {
|
||||
print(`Attempting to send message to ${destination} on topic '${topic}'`);
|
||||
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
|
||||
print(`result: ${result}`);
|
||||
print(`Message sent: ${result.success}`);
|
||||
if result.id != "" {
|
||||
print(`Message ID: ${result.id}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error sending message: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Sets the API URL for your Mycelium node
|
||||
2. Specifies the destination IP address, topic, message content, and deadline
|
||||
3. Calls `mycelium_send_message()` to send the message
|
||||
4. Prints the result, including the message ID if successful
|
||||
|
||||
### Important Parameters for Sending Messages
|
||||
|
||||
- `api_url`: The URL of your Mycelium node's API
|
||||
- `destination`: The IP address of the destination node
|
||||
- `topic`: The topic to send the message on (must match what the receiver is listening for)
|
||||
- `message`: The content of the message
|
||||
- `deadline_secs`: Time in seconds to wait for a reply. Use a negative value if you don't want to wait for a reply.
|
||||
|
||||
## Receiving Messages
|
||||
|
||||
Now let's look at how to receive messages using the `mycelium_receive_message.rhai` example.
|
||||
|
||||
```rhai
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:2222";
|
||||
|
||||
// Receive messages
|
||||
print("\nReceiving messages:");
|
||||
let receive_topic = "test_topic";
|
||||
let wait_deadline_secs = 100;
|
||||
|
||||
print(`Listening for messages on topic '${receive_topic}'...`);
|
||||
try {
|
||||
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
|
||||
|
||||
if messages.is_empty() {
|
||||
// print("No new messages received in this poll.");
|
||||
} else {
|
||||
print("Received a message:");
|
||||
print(` Message id: ${messages.id}`);
|
||||
print(` Message from: ${messages.srcIp}`);
|
||||
print(` Topic: ${messages.topic}`);
|
||||
print(` Payload: ${messages.payload}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error receiving messages: ${err}`);
|
||||
}
|
||||
|
||||
print("Finished attempting to receive messages.");
|
||||
```
|
||||
|
||||
This code:
|
||||
1. Sets the API URL for your Mycelium node
|
||||
2. Specifies the topic to listen on and how long to wait for messages
|
||||
3. Calls `mycelium_receive_messages()` to receive messages
|
||||
4. Processes and prints any received messages
|
||||
|
||||
### Important Parameters for Receiving Messages
|
||||
|
||||
- `api_url`: The URL of your Mycelium node's API
|
||||
- `receive_topic`: The topic to listen for messages on (must match what the sender is using)
|
||||
- `wait_deadline_secs`: Time in seconds to wait for messages to arrive. The function will block for this duration if no messages are immediately available.
|
||||
|
||||
## Complete Messaging Example
|
||||
|
||||
To set up a complete messaging system, you would typically run two instances of Mycelium (node A sender, node B receiver).
|
||||
|
||||
1. Run the `mycelium_receive_message.rhai` script to listen for messages. **Fill in the API address of node B**.
|
||||
2. Run the `mycelium_send_message.rhai` script to send messages. **Fill in the API address of node A, and fill in the overlay address of node B as destination**.
|
||||
|
||||
### Setting Up the Receiver
|
||||
|
||||
First, start a Mycelium node and run the receiver script:
|
||||
|
||||
```rhai
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:2222"; // Your receiver node's API URL
|
||||
|
||||
// Receive messages
|
||||
let receive_topic = "test_topic";
|
||||
let wait_deadline_secs = 100; // Wait up to 100 seconds for messages
|
||||
|
||||
print(`Listening for messages on topic '${receive_topic}'...`);
|
||||
try {
|
||||
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
|
||||
|
||||
if messages.is_empty() {
|
||||
print("No new messages received in this poll.");
|
||||
} else {
|
||||
print("Received a message:");
|
||||
print(` Message id: ${messages.id}`);
|
||||
print(` Message from: ${messages.srcIp}`);
|
||||
print(` Topic: ${messages.topic}`);
|
||||
print(` Payload: ${messages.payload}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error receiving messages: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Setting Up the Sender
|
||||
|
||||
Then, on another Mycelium node, run the sender script:
|
||||
|
||||
```rhai
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:1111"; // Your sender node's API URL
|
||||
|
||||
// Send a message
|
||||
let destination = "5af:ae6b:dcd8:ffdb:b71:7dde:d3:1033"; // The receiver node's IP address
|
||||
let topic = "test_topic"; // Must match the receiver's topic
|
||||
let message = "Hello from Rhai sender!";
|
||||
let deadline_secs = -10; // Don't wait for a reply
|
||||
|
||||
try {
|
||||
print(`Attempting to send message to ${destination} on topic '${topic}'`);
|
||||
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
|
||||
print(`Message sent: ${result.success}`);
|
||||
if result.id != "" {
|
||||
print(`Message ID: ${result.id}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error sending message: ${err}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Example: setting up 2 different Mycelium peers on same the host and sending/receiving a message
|
||||
|
||||
#### Obtain Mycelium
|
||||
|
||||
- Download the latest Mycelium binary from https://github.com/threefoldtech/mycelium/releases/
|
||||
- Or compile from source
|
||||
|
||||
#### Setup
|
||||
- Create two different private key files. Each key file should contain exactely 32 bytes. In this example we'll save these files as `sender.bin` and `receiver.bin`. Note: generate your own 32-byte key files, the values below are just used as examples.
|
||||
> `echo '9f3d72c1a84be6f027bba94cde015ee839cedb2ac4f2822bfc94449e3e2a1c6a' > sender.bin`
|
||||
|
||||
> `echo 'e81c5a76f42bd9a3c73fe0bb2196acdfb6348e99d0b01763a2e57ce3a4e8f5dd' > receiver.bin`
|
||||
|
||||
#### Start the nodes
|
||||
- **Sender**: this node will have the API server hosted on `127.0.0.1:1111` and the JSON-RPC server on `127.0.0.1:8991`.
|
||||
> `sudo ./mycelium --key-file sender.bin --disable-peer-discovery --disable-quic --no-tun --api-addr 127.0.0.1:1111 --jsonrpc-addr 127.0.0.1:8991`
|
||||
|
||||
- **Receiver**: this node will have the API server hosted on `127.0.0.1:2222` and the JSON-RPC server on `127.0.0.1:8992`.
|
||||
> `sudo ./mycelium --key-file receiver.bin --disable-peer-discovery --disable-quic --no-tun --api-addr 127.0.0.1:2222 --jsonrpc-addr 127.0.0.1:8992 --peers tcp://<UNDERLAY_IP_SENDER>:9651`
|
||||
- Obtain the Mycelium overlay IP by running `./mycelium --key-file receiver.bin --api-addr 127.0.0.1:2222 inspect`. **Replace this IP as destination in the [mycelium_send_message.rhai](../../../examples/mycelium/mycelium_send_message.rhai) example**.
|
||||
|
||||
#### Execute the examples
|
||||
- First build by executing `./build_herdo.sh` from the SAL root directory
|
||||
- `cd target/debug`
|
||||
|
||||
- Run the sender script: `sudo ./herodo --path ../../examples/mycelium/mycelium_send_message.rhai`
|
||||
```
|
||||
Executing: ../../examples/mycelium/mycelium_send_message.rhai
|
||||
|
||||
Sending a message:
|
||||
Attempting to send message to 50e:6d75:4568:366e:f75:2ac3:bbb1:3fdd on topic 'test_topic'
|
||||
result: #{"id": "bfd47dc689a7b826"}
|
||||
Message sent:
|
||||
Message ID: bfd47dc689a7b826
|
||||
Script executed successfull
|
||||
```
|
||||
|
||||
- Run the receiver script: `sudo ./herodo --path ../../examples/mycelium/mycelium_receive_message.rhai`
|
||||
```
|
||||
Executing: ../../examples/mycelium/mycelium_receive_message.rhai
|
||||
|
||||
Receiving messages:
|
||||
Listening for messages on topic 'test_topic'...
|
||||
Received a message:
|
||||
Message id: bfd47dc689a7b826
|
||||
Message from: 45d:26e1:a413:9d08:80ce:71c6:a931:4315
|
||||
Topic: dGVzdF90b3BpYw==
|
||||
Payload: SGVsbG8gZnJvbSBSaGFpIHNlbmRlciE=
|
||||
Finished attempting to receive messages.
|
||||
Script executed successfully
|
||||
```
|
||||
> Decoding the payload `SGVsbG8gZnJvbSBSaGFpIHNlbmRlciE=` results in the expected `Hello from Rhai sender!` message. Mission succesful!
|
||||
|
116
docs/docs/rhai/nerdctl_module_tests.md
Normal file
116
docs/docs/rhai/nerdctl_module_tests.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Nerdctl Module Tests
|
||||
|
||||
This document describes the test scripts for the Nerdctl module in the SAL library. These tests verify the functionality of the Nerdctl module's container and image operations.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into three main scripts:
|
||||
|
||||
1. **Container Operations** (`01_container_operations.rhai`): Tests for basic container operations like creating, running, executing commands, and removing containers.
|
||||
2. **Image Operations** (`02_image_operations.rhai`): Tests for image-related operations like pulling, tagging, listing, building, and removing images.
|
||||
3. **Container Builder Pattern** (`03_container_builder.rhai`): Tests for the Container Builder pattern, which provides a fluent interface for configuring and running containers.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/nerdctl/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/nerdctl/01_container_operations.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Container Operations Test
|
||||
|
||||
The Container Operations test (`01_container_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `nerdctl_container_new`: Creating a new Container
|
||||
- Container properties: `name`, `container_id`, `image`, `detach`
|
||||
- `with_image`: Setting the container image
|
||||
- `with_detach`: Setting detach mode
|
||||
- `with_env` and `with_envs`: Setting environment variables
|
||||
- `with_port` and `with_ports`: Setting port mappings
|
||||
- `with_volume`: Setting volume mounts
|
||||
- `with_cpu_limit` and `with_memory_limit`: Setting resource limits
|
||||
- `run`: Running the container
|
||||
- `exec`: Executing commands in the container
|
||||
- `logs`: Getting container logs
|
||||
- `stop`: Stopping the container
|
||||
- `remove`: Removing the container
|
||||
|
||||
### Image Operations Test
|
||||
|
||||
The Image Operations test (`02_image_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `nerdctl_image_pull`: Pulling images from registries
|
||||
- `nerdctl_images`: Listing images
|
||||
- `nerdctl_image_tag`: Tagging images
|
||||
- `nerdctl_image_build`: Building images from Dockerfiles
|
||||
- `nerdctl_run_with_name`: Running containers from images
|
||||
- `nerdctl_stop` and `nerdctl_remove`: Stopping and removing containers
|
||||
- `nerdctl_image_remove`: Removing images
|
||||
|
||||
The test creates a temporary directory with a Dockerfile for testing the build functionality.
|
||||
|
||||
### Container Builder Pattern Test
|
||||
|
||||
The Container Builder Pattern test (`03_container_builder.rhai`) verifies the following functions:
|
||||
|
||||
- `nerdctl_container_from_image`: Creating a container from an image
|
||||
- `reset`: Resetting container configuration
|
||||
- `with_detach`: Setting detach mode
|
||||
- `with_ports`: Setting multiple port mappings
|
||||
- `with_volumes`: Setting multiple volume mounts
|
||||
- `with_envs`: Setting multiple environment variables
|
||||
- `with_network`: Setting network
|
||||
- `with_cpu_limit` and `with_memory_limit`: Setting resource limits
|
||||
- `run`: Running the container
|
||||
- `exec`: Executing commands in the container
|
||||
- `stop`: Stopping the container
|
||||
- `remove`: Removing the container
|
||||
|
||||
The test also verifies that environment variables and volume mounts work correctly by writing and reading files between the container and the host.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Checks if nerdctl is available before running tests
|
||||
2. Skips tests if nerdctl is not available
|
||||
3. Contains simplified versions of each test
|
||||
4. Runs each test in a try/catch block to handle errors
|
||||
5. Catches and reports any errors
|
||||
6. Provides a summary of passed, failed, and skipped tests
|
||||
|
||||
## Nerdctl Requirements
|
||||
|
||||
These tests require the nerdctl tool to be installed and available in the system's PATH. The tests will check for nerdctl's availability and skip the tests if it's not found, rather than failing.
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/nerdctl` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Nerdctl module:
|
||||
|
||||
1. Always check if nerdctl is available before running tests
|
||||
2. Use unique names for containers and images to avoid conflicts
|
||||
3. Clean up any containers, images, or files created during testing
|
||||
4. Use assertions to verify expected behavior
|
||||
5. Print clear messages about what's being tested
|
||||
6. Handle errors gracefully
|
||||
7. Make tests independent of each other
|
||||
8. Keep tests focused on specific functionality
|
105
docs/docs/rhai/os_module_tests.md
Normal file
105
docs/docs/rhai/os_module_tests.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# OS Module Tests
|
||||
|
||||
This document describes the test scripts for the OS module in the SAL library. These tests verify the functionality of the OS module's file system operations, download capabilities, and package management features.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into three main scripts:
|
||||
|
||||
1. **File Operations** (`01_file_operations.rhai`): Tests file system operations like creating, reading, writing, and manipulating files and directories.
|
||||
2. **Download Operations** (`02_download_operations.rhai`): Tests downloading files from the internet and related operations.
|
||||
3. **Package Operations** (`03_package_operations.rhai`): Tests package management functionality.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency on the `run_script` function.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
# Assume that you have the herodo binary/built into your system
|
||||
herodo --path src/rhai_tests/os/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
# Assume that you have the herodo binary/built into your system
|
||||
herodo --path src/rhai_tests/os/01_file_operations.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### File Operations Test
|
||||
|
||||
The file operations test (`01_file_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `mkdir`: Creating directories
|
||||
- `file_write`: Writing content to files
|
||||
- `file_read`: Reading content from files
|
||||
- `file_size`: Getting file size
|
||||
- `file_write_append`: Appending content to files
|
||||
- `copy`: Copying files
|
||||
- `mv`: Moving files
|
||||
- `find_file`: Finding a single file matching a pattern
|
||||
- `find_files`: Finding multiple files matching a pattern
|
||||
- `find_dir`: Finding a single directory matching a pattern
|
||||
- `find_dirs`: Finding multiple directories matching a pattern
|
||||
- `chdir`: Changing the current working directory
|
||||
- `rsync`: Synchronizing directories
|
||||
- `delete`: Deleting files and directories
|
||||
- `exist`: Checking if files or directories exist
|
||||
|
||||
The test creates a temporary directory structure, performs operations on it, and then cleans up after itself.
|
||||
|
||||
### Download Operations Test
|
||||
|
||||
The download operations test (`02_download_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `which`: Checking if a command exists in the system PATH
|
||||
- `cmd_ensure_exists`: Ensuring commands exist
|
||||
- `download_file`: Downloading a file from a URL
|
||||
- `chmod_exec`: Making a file executable
|
||||
|
||||
The test downloads a small file from GitHub, verifies its content, and then cleans up.
|
||||
|
||||
### Package Operations Test
|
||||
|
||||
The package operations test (`03_package_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `package_platform`: Getting the current platform
|
||||
- `package_set_debug`: Setting debug mode for package operations
|
||||
- `package_is_installed`: Checking if a package is installed
|
||||
- `package_search`: Searching for packages
|
||||
- `package_list`: Listing installed packages
|
||||
|
||||
Note: The test does not verify `package_install`, `package_remove`, `package_update`, or `package_upgrade` as these require root privileges and could modify the system state.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Contains simplified versions of each test
|
||||
2. Runs each test in a try/catch block to handle errors
|
||||
3. Catches and reports any errors
|
||||
4. Provides a summary of passed and failed tests
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/os` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the OS module:
|
||||
|
||||
1. Always clean up temporary files and directories
|
||||
2. Use assertions to verify expected behavior
|
||||
3. Print clear messages about what's being tested
|
||||
4. Handle errors gracefully
|
||||
5. Make tests independent of each other
|
||||
6. Avoid tests that require root privileges when possible
|
||||
7. Keep tests focused on specific functionality
|
188
docs/docs/rhai/postgresclient_module_tests.md
Normal file
188
docs/docs/rhai/postgresclient_module_tests.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# PostgreSQL Client Module Tests
|
||||
|
||||
The PostgreSQL client module provides functions for connecting to and interacting with PostgreSQL databases. These tests verify the functionality of the module.
|
||||
|
||||
## PostgreSQL Client Features
|
||||
|
||||
The PostgreSQL client module provides the following features:
|
||||
|
||||
1. **Basic PostgreSQL Operations**: Execute queries, fetch results, etc.
|
||||
2. **Connection Management**: Automatic connection handling and reconnection
|
||||
3. **Builder Pattern for Configuration**: Flexible configuration with authentication support
|
||||
4. **PostgreSQL Installer**: Install and configure PostgreSQL using nerdctl
|
||||
5. **Database Management**: Create databases and execute SQL scripts
|
||||
|
||||
## Prerequisites
|
||||
|
||||
For basic PostgreSQL operations:
|
||||
- PostgreSQL server must be running and accessible
|
||||
- Environment variables should be set for connection details:
|
||||
- `POSTGRES_HOST`: PostgreSQL server host (default: localhost)
|
||||
- `POSTGRES_PORT`: PostgreSQL server port (default: 5432)
|
||||
- `POSTGRES_USER`: PostgreSQL username (default: postgres)
|
||||
- `POSTGRES_PASSWORD`: PostgreSQL password
|
||||
- `POSTGRES_DB`: PostgreSQL database name (default: postgres)
|
||||
|
||||
For PostgreSQL installer:
|
||||
- nerdctl must be installed and working
|
||||
- Docker images must be accessible
|
||||
- Sufficient permissions to create and manage containers
|
||||
|
||||
## Test Files
|
||||
|
||||
### 01_postgres_connection.rhai
|
||||
|
||||
Tests basic PostgreSQL connection and operations:
|
||||
|
||||
- Connecting to PostgreSQL
|
||||
- Pinging the server
|
||||
- Creating a table
|
||||
- Inserting data
|
||||
- Querying data
|
||||
- Dropping a table
|
||||
- Resetting the connection
|
||||
|
||||
### 02_postgres_installer.rhai
|
||||
|
||||
Tests PostgreSQL installer functionality:
|
||||
|
||||
- Installing PostgreSQL using nerdctl
|
||||
- Creating a database
|
||||
- Executing SQL scripts
|
||||
- Checking if PostgreSQL is running
|
||||
|
||||
### run_all_tests.rhai
|
||||
|
||||
Runs all PostgreSQL client module tests and provides a summary of the results.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
You can run the tests using the `herodo` command:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/postgresclient/run_all_tests.rhai
|
||||
```
|
||||
|
||||
Or run individual tests:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/postgresclient/01_postgres_connection.rhai
|
||||
```
|
||||
|
||||
## Available Functions
|
||||
|
||||
### Connection Functions
|
||||
|
||||
- `pg_connect()`: Connect to PostgreSQL using environment variables
|
||||
- `pg_ping()`: Ping the PostgreSQL server to check if it's available
|
||||
- `pg_reset()`: Reset the PostgreSQL client connection
|
||||
|
||||
### Query Functions
|
||||
|
||||
- `pg_execute(query)`: Execute a query and return the number of affected rows
|
||||
- `pg_query(query)`: Execute a query and return the results as an array of maps
|
||||
- `pg_query_one(query)`: Execute a query and return a single row as a map
|
||||
|
||||
### Installer Functions
|
||||
|
||||
- `pg_install(container_name, version, port, username, password)`: Install PostgreSQL using nerdctl
|
||||
- `pg_create_database(container_name, db_name)`: Create a new database in PostgreSQL
|
||||
- `pg_execute_sql(container_name, db_name, sql)`: Execute a SQL script in PostgreSQL
|
||||
- `pg_is_running(container_name)`: Check if PostgreSQL is running
|
||||
|
||||
## Authentication Support
|
||||
|
||||
The PostgreSQL client module will support authentication using the builder pattern in a future update.
|
||||
|
||||
The backend implementation is ready, but the Rhai bindings are still in development.
|
||||
|
||||
When implemented, the builder pattern will support the following configuration options:
|
||||
|
||||
- Host: Set the PostgreSQL host
|
||||
- Port: Set the PostgreSQL port
|
||||
- User: Set the PostgreSQL username
|
||||
- Password: Set the PostgreSQL password
|
||||
- Database: Set the PostgreSQL database name
|
||||
- Application name: Set the application name
|
||||
- Connection timeout: Set the connection timeout in seconds
|
||||
- SSL mode: Set the SSL mode
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Basic PostgreSQL Operations
|
||||
|
||||
```rust
|
||||
// Connect to PostgreSQL
|
||||
if (pg_connect()) {
|
||||
print("Connected to PostgreSQL!");
|
||||
|
||||
// Create a table
|
||||
let create_table_query = "CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name TEXT)";
|
||||
pg_execute(create_table_query);
|
||||
|
||||
// Insert data
|
||||
let insert_query = "INSERT INTO test_table (name) VALUES ('test')";
|
||||
pg_execute(insert_query);
|
||||
|
||||
// Query data
|
||||
let select_query = "SELECT * FROM test_table";
|
||||
let results = pg_query(select_query);
|
||||
|
||||
// Process results
|
||||
for (result in results) {
|
||||
print(`ID: ${result.id}, Name: ${result.name}`);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let drop_query = "DROP TABLE test_table";
|
||||
pg_execute(drop_query);
|
||||
}
|
||||
```
|
||||
|
||||
### PostgreSQL Installer
|
||||
|
||||
```rust
|
||||
// Install PostgreSQL
|
||||
let container_name = "my-postgres";
|
||||
let postgres_version = "15";
|
||||
let postgres_port = 5432;
|
||||
let postgres_user = "myuser";
|
||||
let postgres_password = "mypassword";
|
||||
|
||||
if (pg_install(container_name, postgres_version, postgres_port, postgres_user, postgres_password)) {
|
||||
print("PostgreSQL installed successfully!");
|
||||
|
||||
// Create a database
|
||||
let db_name = "mydb";
|
||||
if (pg_create_database(container_name, db_name)) {
|
||||
print(`Database '${db_name}' created successfully!`);
|
||||
|
||||
// Execute a SQL script
|
||||
let create_table_sql = `
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL
|
||||
);
|
||||
`;
|
||||
|
||||
let result = pg_execute_sql(container_name, db_name, create_table_sql);
|
||||
print("Table created successfully!");
|
||||
|
||||
// Insert data
|
||||
let insert_sql = "#
|
||||
INSERT INTO users (name, email) VALUES
|
||||
('John Doe', 'john@example.com'),
|
||||
('Jane Smith', 'jane@example.com');
|
||||
#";
|
||||
|
||||
result = pg_execute_sql(container_name, db_name, insert_sql);
|
||||
print("Data inserted successfully!");
|
||||
|
||||
// Query data
|
||||
let query_sql = "SELECT * FROM users;";
|
||||
result = pg_execute_sql(container_name, db_name, query_sql);
|
||||
print(`Query result: ${result}`);
|
||||
}
|
||||
}
|
||||
```
|
79
docs/docs/rhai/process_module_tests.md
Normal file
79
docs/docs/rhai/process_module_tests.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Process Module Tests
|
||||
|
||||
This document describes the test scripts for the Process module in the SAL library. These tests verify the functionality of the Process module's command execution and process management features.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into two main scripts:
|
||||
|
||||
1. **Command Execution** (`01_command_execution.rhai`): Tests command execution functions like `run()` and `which()`.
|
||||
2. **Process Management** (`02_process_management.rhai`): Tests process management functions like `process_list()` and `process_get()`.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/process/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/process/01_command_execution.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Command Execution Test
|
||||
|
||||
The command execution test (`01_command_execution.rhai`) verifies the following functions:
|
||||
|
||||
- `run()`: Running shell commands
|
||||
- `run().do()`: Executing commands and capturing output
|
||||
- `run().silent()`: Running commands without displaying output
|
||||
- `run().ignore_error()`: Running commands that might fail without throwing errors
|
||||
- `which()`: Finding the path of an executable
|
||||
|
||||
The test runs various commands and verifies their output and exit status.
|
||||
|
||||
### Process Management Test
|
||||
|
||||
The process management test (`02_process_management.rhai`) verifies the following functions:
|
||||
|
||||
- `process_list()`: Listing running processes
|
||||
- `process_get()`: Getting information about a specific process
|
||||
- Process properties: Accessing process information like PID, name, CPU usage, and memory usage
|
||||
|
||||
The test lists running processes and verifies that their properties are accessible.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Contains simplified versions of each test
|
||||
2. Runs each test in a try/catch block to handle errors
|
||||
3. Catches and reports any errors
|
||||
4. Provides a summary of passed and failed tests
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/process` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Process module:
|
||||
|
||||
1. Use assertions to verify expected behavior
|
||||
2. Print clear messages about what's being tested
|
||||
3. Handle errors gracefully
|
||||
4. Make tests independent of each other
|
||||
5. Avoid tests that could disrupt the system (e.g., killing important processes)
|
||||
6. Keep tests focused on specific functionality
|
||||
7. Clean up any resources created during testing
|
125
docs/docs/rhai/redisclient_module_tests.md
Normal file
125
docs/docs/rhai/redisclient_module_tests.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Redis Client Module Tests
|
||||
|
||||
This document describes the test scripts for the Redis client module in the SAL library. These tests verify the functionality of the Redis client module's connection management and Redis operations.
|
||||
|
||||
## Redis Client Features
|
||||
|
||||
The Redis client module provides the following features:
|
||||
|
||||
1. **Basic Redis Operations**: SET, GET, DEL, etc.
|
||||
2. **Hash Operations**: HSET, HGET, HGETALL, HDEL
|
||||
3. **List Operations**: RPUSH, LPUSH, LLEN, LRANGE
|
||||
4. **Connection Management**: Automatic connection handling and reconnection
|
||||
5. **Builder Pattern for Configuration**: Flexible configuration with authentication support
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into two main scripts:
|
||||
|
||||
1. **Redis Connection** (`01_redis_connection.rhai`): Tests basic Redis connection and simple operations like PING, SET, GET, and DEL.
|
||||
2. **Redis Operations** (`02_redis_operations.rhai`): Tests more advanced Redis operations like hash operations (HSET, HGET, HGETALL, HDEL) and list operations (RPUSH, LLEN, LRANGE).
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/redisclient/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/redisclient/01_redis_connection.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Redis Connection Test
|
||||
|
||||
The Redis connection test (`01_redis_connection.rhai`) verifies the following functions:
|
||||
|
||||
- `redis_ping`: Checking if the Redis server is available
|
||||
- `redis_set`: Setting a key-value pair
|
||||
- `redis_get`: Getting a value by key
|
||||
- `redis_del`: Deleting a key
|
||||
|
||||
The test creates a temporary key, performs operations on it, and then cleans up after itself.
|
||||
|
||||
### Redis Operations Test
|
||||
|
||||
The Redis operations test (`02_redis_operations.rhai`) verifies the following functions:
|
||||
|
||||
- Hash operations:
|
||||
- `redis_hset`: Setting a field in a hash
|
||||
- `redis_hget`: Getting a field from a hash
|
||||
- `redis_hgetall`: Getting all fields and values from a hash
|
||||
- `redis_hdel`: Deleting a field from a hash
|
||||
|
||||
- List operations:
|
||||
- `redis_rpush`: Adding elements to a list
|
||||
- `redis_llen`: Getting the length of a list
|
||||
- `redis_lrange`: Getting a range of elements from a list
|
||||
|
||||
The test creates temporary keys with a unique prefix, performs operations on them, and then cleans up after itself.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Checks if Redis is available before running tests
|
||||
2. Skips tests if Redis is not available
|
||||
3. Contains simplified versions of each test
|
||||
4. Runs each test in a try/catch block to handle errors
|
||||
5. Catches and reports any errors
|
||||
6. Provides a summary of passed, failed, and skipped tests
|
||||
|
||||
## Redis Server Requirements
|
||||
|
||||
These tests require a Redis server to be running and accessible. The tests will attempt to connect to Redis using the following strategy:
|
||||
|
||||
1. First, try to connect via Unix socket at `$HOME/hero/var/myredis.sock`
|
||||
2. If that fails, try to connect via TCP to `127.0.0.1` on the default Redis port (6379)
|
||||
|
||||
If no Redis server is available, the tests will be skipped rather than failing.
|
||||
|
||||
## Authentication Support
|
||||
|
||||
The Redis client module will support authentication using the builder pattern in a future update.
|
||||
|
||||
The backend implementation is ready, but the Rhai bindings are still in development.
|
||||
|
||||
When implemented, the builder pattern will support the following configuration options:
|
||||
|
||||
- Host: Set the Redis host
|
||||
- Port: Set the Redis port
|
||||
- Database: Set the Redis database number
|
||||
- Username: Set the Redis username (Redis 6.0+)
|
||||
- Password: Set the Redis password
|
||||
- TLS: Enable/disable TLS
|
||||
- Unix socket: Enable/disable Unix socket
|
||||
- Socket path: Set the Unix socket path
|
||||
- Connection timeout: Set the connection timeout in seconds
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/redisclient` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Redis client module:
|
||||
|
||||
1. Always check if Redis is available before running tests
|
||||
2. Use a unique prefix for test keys to avoid conflicts
|
||||
3. Clean up any keys created during testing
|
||||
4. Use assertions to verify expected behavior
|
||||
5. Print clear messages about what's being tested
|
||||
6. Handle errors gracefully
|
||||
7. Make tests independent of each other
|
||||
8. Keep tests focused on specific functionality
|
113
docs/docs/rhai/rfs_module_tests.md
Normal file
113
docs/docs/rhai/rfs_module_tests.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# RFS Module Tests
|
||||
|
||||
This document describes the test scripts for the RFS (Remote File System) module in the SAL library. These tests verify the functionality of the RFS module's mount operations and filesystem layer management.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into two main scripts:
|
||||
|
||||
1. **Mount Operations** (`01_mount_operations.rhai`): Tests for mounting, listing, and unmounting filesystems.
|
||||
2. **Filesystem Layer Operations** (`02_filesystem_layer_operations.rhai`): Tests for packing, unpacking, listing, and verifying filesystem layers.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/rfs/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/rfs/01_mount_operations.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Mount Operations Test
|
||||
|
||||
The Mount Operations test (`01_mount_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `rfs_mount`: Mounting a filesystem
|
||||
- Tests mounting a local directory with options
|
||||
- Verifies mount properties (ID, source, target, type)
|
||||
|
||||
- `rfs_list_mounts`: Listing mounted filesystems
|
||||
- Tests listing all mounts
|
||||
- Verifies that the mounted filesystem is in the list
|
||||
|
||||
- `rfs_get_mount_info`: Getting information about a mounted filesystem
|
||||
- Tests getting information about a specific mount
|
||||
- Verifies that the mount information is correct
|
||||
|
||||
- `rfs_unmount`: Unmounting a specific filesystem
|
||||
- Tests unmounting a specific mount
|
||||
- Verifies that the mount is no longer available
|
||||
|
||||
- `rfs_unmount_all`: Unmounting all filesystems
|
||||
- Tests unmounting all mounts
|
||||
- Verifies that no mounts remain after the operation
|
||||
|
||||
The test also verifies that files in the mounted filesystem are accessible and have the correct content.
|
||||
|
||||
### Filesystem Layer Operations Test
|
||||
|
||||
The Filesystem Layer Operations test (`02_filesystem_layer_operations.rhai`) verifies the following functions:
|
||||
|
||||
- `rfs_pack`: Packing a directory into a filesystem layer
|
||||
- Tests packing a directory with files and subdirectories
|
||||
- Verifies that the output file is created
|
||||
|
||||
- `rfs_list_contents`: Listing the contents of a filesystem layer
|
||||
- Tests listing the contents of a packed filesystem layer
|
||||
- Verifies that the list includes all expected files
|
||||
|
||||
- `rfs_verify`: Verifying a filesystem layer
|
||||
- Tests verifying a packed filesystem layer
|
||||
- Verifies that the layer is valid
|
||||
|
||||
- `rfs_unpack`: Unpacking a filesystem layer
|
||||
- Tests unpacking a filesystem layer to a directory
|
||||
- Verifies that all files are unpacked correctly with the right content
|
||||
|
||||
The test creates a directory structure with files, packs it into a filesystem layer, and then unpacks it to verify the integrity of the process.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Checks if RFS is available before running tests
|
||||
2. Skips tests if RFS is not available
|
||||
3. Contains simplified versions of each test
|
||||
4. Runs each test in a try/catch block to handle errors
|
||||
5. Catches and reports any errors
|
||||
6. Provides a summary of passed, failed, and skipped tests
|
||||
|
||||
## RFS Requirements
|
||||
|
||||
These tests require the RFS tool to be installed and available in the system's PATH. The tests will check for RFS's availability and skip the tests if it's not found, rather than failing.
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/rfs` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the RFS module:
|
||||
|
||||
1. Always check if RFS is available before running tests
|
||||
2. Clean up any mounts before and after testing
|
||||
3. Use unique names for test directories and files to avoid conflicts
|
||||
4. Clean up any files or directories created during testing
|
||||
5. Use assertions to verify expected behavior
|
||||
6. Print clear messages about what's being tested
|
||||
7. Handle errors gracefully
|
||||
8. Make tests independent of each other
|
||||
9. Keep tests focused on specific functionality
|
76
docs/docs/rhai/running_tests.md
Normal file
76
docs/docs/rhai/running_tests.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Running Rhai Tests
|
||||
|
||||
This document describes how to run the Rhai tests for the SAL library.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The Rhai tests are organized by module in the `src/rhai_tests` directory:
|
||||
|
||||
- `src/rhai_tests/os/`: Tests for the OS module
|
||||
- `src/rhai_tests/git/`: Tests for the Git module
|
||||
|
||||
Each module directory contains:
|
||||
- Individual test scripts (e.g., `01_file_operations.rhai`)
|
||||
- A test runner script (`run_all_tests.rhai`) that runs all tests for that module
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Running All Tests
|
||||
|
||||
To run all Rhai tests across all modules, use the provided shell script:
|
||||
|
||||
```bash
|
||||
./run_rhai_tests.sh
|
||||
```
|
||||
|
||||
This script:
|
||||
1. Finds all test runner scripts in the `src/rhai_tests` directory
|
||||
2. Runs each test runner
|
||||
3. Reports the results for each module
|
||||
4. Provides a summary of all test results
|
||||
|
||||
The script will exit with code 0 if all tests pass, or code 1 if any tests fail.
|
||||
|
||||
### Running Tests for a Specific Module
|
||||
|
||||
To run tests for a specific module, use the `herodo` command with the module's test runner:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/os/run_all_tests.rhai
|
||||
```
|
||||
|
||||
### Running Individual Tests
|
||||
|
||||
To run a specific test, use the `herodo` command with the test script:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/os/01_file_operations.rhai
|
||||
```
|
||||
|
||||
## Test Output
|
||||
|
||||
The test output includes:
|
||||
- Information about what's being tested
|
||||
- Success or failure messages for each test
|
||||
- A summary of test results
|
||||
|
||||
Successful tests are indicated with a checkmark (✓), while failed tests show an error message.
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
When adding new tests:
|
||||
|
||||
1. Create a new test script in the appropriate module directory
|
||||
2. Update the module's test runner script to include the new test
|
||||
3. Update the module's documentation to describe the new test
|
||||
|
||||
The `run_rhai_tests.sh` script will automatically find and run the new tests as long as they're included in a module's test runner script.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If tests fail, check the following:
|
||||
|
||||
1. Make sure the `herodo` binary is in your PATH
|
||||
2. Verify that the test scripts have the correct permissions
|
||||
3. Check for any dependencies required by the tests (e.g., `git` for Git module tests)
|
||||
4. Look for specific error messages in the test output
|
129
docs/docs/rhai/text_module_tests.md
Normal file
129
docs/docs/rhai/text_module_tests.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Text Module Tests
|
||||
|
||||
This document describes the test scripts for the Text module in the SAL library. These tests verify the functionality of the Text module's text manipulation, normalization, replacement, and template rendering capabilities.
|
||||
|
||||
## Test Structure
|
||||
|
||||
The tests are organized into four main scripts:
|
||||
|
||||
1. **Text Indentation** (`01_text_indentation.rhai`): Tests for the `dedent` and `prefix` functions.
|
||||
2. **Filename and Path Normalization** (`02_name_path_fix.rhai`): Tests for the `name_fix` and `path_fix` functions.
|
||||
3. **Text Replacement** (`03_text_replacer.rhai`): Tests for the `TextReplacer` class and its methods.
|
||||
4. **Template Rendering** (`04_template_builder.rhai`): Tests for the `TemplateBuilder` class and its methods.
|
||||
|
||||
Additionally, there's a runner script (`run_all_tests.rhai`) that executes all tests and reports results. The runner script contains simplified versions of the individual tests to avoid dependency issues.
|
||||
|
||||
## Running the Tests
|
||||
|
||||
To run all tests, execute the following command from the project root:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/text/run_all_tests.rhai
|
||||
```
|
||||
|
||||
To run individual test scripts:
|
||||
|
||||
```bash
|
||||
herodo --path src/rhai_tests/text/01_text_indentation.rhai
|
||||
```
|
||||
|
||||
## Test Details
|
||||
|
||||
### Text Indentation Test
|
||||
|
||||
The text indentation test (`01_text_indentation.rhai`) verifies the following functions:
|
||||
|
||||
- `dedent`: Removes common leading whitespace from multiline strings
|
||||
- Tests basic indentation removal
|
||||
- Tests mixed indentation handling
|
||||
- Tests preservation of empty lines
|
||||
- Tests handling of text without indentation
|
||||
- Tests single line indentation removal
|
||||
|
||||
- `prefix`: Adds a specified prefix to each line of a multiline string
|
||||
- Tests basic prefix addition
|
||||
- Tests empty prefix handling
|
||||
- Tests prefix addition to empty lines
|
||||
- Tests prefix addition to single line
|
||||
- Tests non-space prefix addition
|
||||
|
||||
- Combination of `dedent` and `prefix` functions
|
||||
|
||||
### Filename and Path Normalization Test
|
||||
|
||||
The filename and path normalization test (`02_name_path_fix.rhai`) verifies the following functions:
|
||||
|
||||
- `name_fix`: Normalizes filenames
|
||||
- Tests basic name fixing (spaces to underscores, lowercase conversion)
|
||||
- Tests special character handling
|
||||
- Tests multiple special character handling
|
||||
- Tests non-ASCII character removal
|
||||
- Tests uppercase conversion
|
||||
|
||||
- `path_fix`: Applies `name_fix` to the filename portion of a path
|
||||
- Tests paths ending with `/` (directories)
|
||||
- Tests single filename handling
|
||||
- Tests path with filename handling
|
||||
- Tests relative path handling
|
||||
- Tests path with special characters in filename
|
||||
|
||||
### Text Replacement Test
|
||||
|
||||
The text replacement test (`03_text_replacer.rhai`) verifies the following functions:
|
||||
|
||||
- `TextReplacer` with simple replacements
|
||||
- Tests basic replacement
|
||||
- Tests multiple replacements
|
||||
|
||||
- `TextReplacer` with regex replacements
|
||||
- Tests basic regex replacement
|
||||
- Tests case-insensitive regex replacement
|
||||
|
||||
- `TextReplacer` with file operations
|
||||
- Tests `replace_file` (read file, apply replacements, return result)
|
||||
- Tests `replace_file_to` (read file, apply replacements, write to new file)
|
||||
- Tests `replace_file_in_place` (read file, apply replacements, write back to same file)
|
||||
|
||||
### Template Rendering Test
|
||||
|
||||
The template rendering test (`04_template_builder.rhai`) verifies the following functions:
|
||||
|
||||
- `TemplateBuilder` with file template
|
||||
- Tests basic template with string variable
|
||||
- Tests template with multiple variables of different types
|
||||
- Tests template with array variable
|
||||
- Tests template with map variable
|
||||
|
||||
- `TemplateBuilder` with file operations
|
||||
- Tests template from file
|
||||
- Tests `render_to_file` (render template, write to file)
|
||||
|
||||
Note: The `template_builder_open` function expects a file path, not a string template. The test creates template files on disk for testing.
|
||||
|
||||
## Test Runner
|
||||
|
||||
The test runner script (`run_all_tests.rhai`) provides a framework for executing all tests and reporting results. It:
|
||||
|
||||
1. Contains simplified versions of each test
|
||||
2. Runs each test in a try/catch block to handle errors
|
||||
3. Catches and reports any errors
|
||||
4. Provides a summary of passed and failed tests
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
To add a new test:
|
||||
|
||||
1. Create a new Rhai script in the `src/rhai_tests/text` directory
|
||||
2. Add a new test section to the `run_all_tests.rhai` script
|
||||
3. Update this documentation to include information about the new test
|
||||
|
||||
## Best Practices for Writing Tests
|
||||
|
||||
When writing tests for the Text module:
|
||||
|
||||
1. Use the `assert_true` and `assert_eq` functions to verify expected behavior
|
||||
2. Print clear messages about what's being tested
|
||||
3. Clean up any temporary files or directories created during testing
|
||||
4. Handle errors gracefully
|
||||
5. Make tests independent of each other
|
||||
6. Keep tests focused on specific functionality
|
@@ -1,4 +1,4 @@
|
||||
// File: /root/code/git.ourworld.tf/herocode/sal/examples/container_example.rs
|
||||
// File: /root/code/git.threefold.info/herocode/sal/examples/container_example.rs
|
||||
|
||||
use std::error::Error;
|
||||
use sal::virt::nerdctl::Container;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
// Demonstrates file system operations using SAL
|
||||
|
||||
// Create a test directory
|
||||
let test_dir = "rhai_test_dir";
|
||||
let test_dir = "/tmp/rhai_test_dir";
|
||||
println(`Creating directory: ${test_dir}`);
|
||||
let mkdir_result = mkdir(test_dir);
|
||||
println(`Directory creation result: ${mkdir_result}`);
|
||||
@@ -61,4 +61,4 @@ for file in files {
|
||||
// delete(test_dir);
|
||||
// println("Cleanup complete");
|
||||
|
||||
"File operations script completed successfully!"
|
||||
"File operations script completed successfully!"
|
||||
|
@@ -121,16 +121,16 @@ println(`Using local image: ${local_image_name}`);
|
||||
|
||||
// Tag the image with the localhost prefix for nerdctl compatibility
|
||||
println(`Tagging image as ${local_image_name}...`);
|
||||
let tag_result = bah_image_tag(final_image_name, local_image_name);
|
||||
let tag_result = image_tag(final_image_name, local_image_name);
|
||||
|
||||
// Print a command to check if the image exists in buildah
|
||||
println("\nTo verify the image was created with buildah, run:");
|
||||
println("buildah images");
|
||||
|
||||
// Note: If nerdctl cannot find the image, you may need to push it to a registry
|
||||
println("\nNote: If nerdctl cannot find the image, you may need to push it to a registry:");
|
||||
println("buildah push localhost/custom-golang-nginx:latest docker://localhost:5000/custom-golang-nginx:latest");
|
||||
println("nerdctl pull localhost:5000/custom-golang-nginx:latest");
|
||||
// println("\nNote: If nerdctl cannot find the image, you may need to push it to a registry:");
|
||||
// println("buildah push localhost/custom-golang-nginx:latest docker://localhost:5000/custom-golang-nginx:latest");
|
||||
// println("nerdctl pull localhost:5000/custom-golang-nginx:latest");
|
||||
|
||||
let container = nerdctl_container_from_image("golang-nginx-demo", local_image_name)
|
||||
.with_detach(true)
|
||||
|
44
examples/containers/buildah_run.rhai
Normal file
44
examples/containers/buildah_run.rhai
Normal file
@@ -0,0 +1,44 @@
|
||||
|
||||
// Now use nerdctl to run a container from the new image
|
||||
println("\nStarting container from the new image using nerdctl...");
|
||||
|
||||
// Create a container using the builder pattern
|
||||
// Use localhost/ prefix to ensure nerdctl uses the local image
|
||||
let local_image_name = "localhost/custom-golang-nginx:latest";
|
||||
println(`Using local image: ${local_image_name}`);
|
||||
|
||||
// Import the image from buildah to nerdctl
|
||||
println("Importing image from buildah to nerdctl...");
|
||||
process_run("buildah", ["push", "custom-golang-nginx:latest", "docker-daemon:localhost/custom-golang-nginx:latest"]);
|
||||
|
||||
let tag_result = nerdctl_image_tag("custom-golang-nginx:latest", local_image_name);
|
||||
|
||||
// Tag the image with the localhost prefix for nerdctl compatibility
|
||||
// println(`Tagging image as ${local_image_name}...`);
|
||||
// let tag_result = bah_image_tag(final_image_name, local_image_name);
|
||||
|
||||
// Print a command to check if the image exists in buildah
|
||||
println("\nTo verify the image was created with buildah, run:");
|
||||
println("buildah images");
|
||||
|
||||
// Note: If nerdctl cannot find the image, you may need to push it to a registry
|
||||
// println("\nNote: If nerdctl cannot find the image, you may need to push it to a registry:");
|
||||
// println("buildah push localhost/custom-golang-nginx:latest docker://localhost:5000/custom-golang-nginx:latest");
|
||||
// println("nerdctl pull localhost:5000/custom-golang-nginx:latest");
|
||||
|
||||
let container = nerdctl_container_from_image("golang-nginx-demo", local_image_name)
|
||||
.with_detach(true)
|
||||
.with_port("8081:80") // Map port 80 in the container to 8080 on the host
|
||||
.with_restart_policy("unless-stopped")
|
||||
.build();
|
||||
|
||||
// Start the container
|
||||
let start_result = container.start();
|
||||
|
||||
println("\nWorkflow completed successfully!");
|
||||
println("The web server should be running at http://localhost:8081");
|
||||
println("You can check container logs with: nerdctl logs golang-nginx-demo");
|
||||
println("To stop the container: nerdctl stop golang-nginx-demo");
|
||||
println("To remove the container: nerdctl rm golang-nginx-demo");
|
||||
|
||||
"Buildah and nerdctl workflow completed successfully!"
|
@@ -1,42 +0,0 @@
|
||||
|
||||
|
||||
|
||||
fn nerdctl_download(){
|
||||
let name="nerdctl";
|
||||
let url="https://github.com/containerd/nerdctl/releases/download/v2.0.4/nerdctl-2.0.4-linux-amd64.tar.gz";
|
||||
download(url,`/tmp/${name}`,20000);
|
||||
copy(`/tmp/${name}/*`,"/root/hero/bin/");
|
||||
delete(`/tmp/${name}`);
|
||||
|
||||
let name="containerd";
|
||||
let url="https://github.com/containerd/containerd/releases/download/v2.0.4/containerd-2.0.4-linux-amd64.tar.gz";
|
||||
download(url,`/tmp/${name}`,20000);
|
||||
copy(`/tmp/${name}/bin/*`,"/root/hero/bin/");
|
||||
delete(`/tmp/${name}`);
|
||||
|
||||
run("apt-get -y install buildah runc");
|
||||
|
||||
let url="https://github.com/threefoldtech/rfs/releases/download/v2.0.6/rfs";
|
||||
download_file(url,`/tmp/rfs`,10000);
|
||||
chmod_exec("/tmp/rfs");
|
||||
mv(`/tmp/rfs`,"/root/hero/bin/");
|
||||
|
||||
}
|
||||
|
||||
fn ipfs_download(){
|
||||
let name="ipfs";
|
||||
let url="https://github.com/ipfs/kubo/releases/download/v0.34.1/kubo_v0.34.1_linux-amd64.tar.gz";
|
||||
download(url,`/tmp/${name}`,20);
|
||||
copy(`/tmp/${name}/kubo/ipfs`,"/root/hero/bin/ipfs");
|
||||
// delete(`/tmp/${name}`);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
nerdctl_download();
|
||||
// ipfs_download();
|
||||
|
||||
"done"
|
76
examples/hero_vault/README.md
Normal file
76
examples/hero_vault/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# SAL Vault Examples
|
||||
|
||||
This directory contains examples demonstrating the SAL Vault functionality.
|
||||
|
||||
## Overview
|
||||
|
||||
SAL Vault provides secure key management and cryptographic operations including:
|
||||
|
||||
- Vault creation and management
|
||||
- KeySpace operations (encrypted key-value stores)
|
||||
- Symmetric key generation and operations
|
||||
- Asymmetric key operations (signing and verification)
|
||||
- Secure key derivation from passwords
|
||||
|
||||
## Current Status
|
||||
|
||||
⚠️ **Note**: The vault module is currently being updated to use Lee's implementation.
|
||||
The Rhai scripting integration is temporarily disabled while we adapt the examples
|
||||
to work with the new vault API.
|
||||
|
||||
## Available Operations
|
||||
|
||||
- **Vault Management**: Create and manage vault instances
|
||||
- **KeySpace Operations**: Open encrypted key-value stores within vaults
|
||||
- **Symmetric Encryption**: Generate keys and encrypt/decrypt data
|
||||
- **Asymmetric Operations**: Create keypairs, sign messages, verify signatures
|
||||
|
||||
## Example Files (Legacy - Sameh's Implementation)
|
||||
|
||||
⚠️ **These examples are currently archived and use the previous vault implementation**:
|
||||
|
||||
- `_archive/example.rhai` - Basic example demonstrating key management, signing, and encryption
|
||||
- `_archive/advanced_example.rhai` - Advanced example with error handling and complex operations
|
||||
- `_archive/key_persistence_example.rhai` - Demonstrates creating and saving a key space to disk
|
||||
- `_archive/load_existing_space.rhai` - Shows how to load a previously created key space
|
||||
- `_archive/contract_example.rhai` - Demonstrates smart contract interactions (Ethereum)
|
||||
- `_archive/agung_send_transaction.rhai` - Demonstrates Ethereum transactions on Agung network
|
||||
- `_archive/agung_contract_with_args.rhai` - Shows contract interactions with arguments
|
||||
|
||||
## Current Implementation (Lee's Vault)
|
||||
|
||||
The current vault implementation provides:
|
||||
|
||||
```rust
|
||||
// Create a new vault
|
||||
let vault = Vault::new(&path).await?;
|
||||
|
||||
// Open an encrypted keyspace
|
||||
let keyspace = vault.open_keyspace("my_space", "password").await?;
|
||||
|
||||
// Perform cryptographic operations
|
||||
// (API documentation coming soon)
|
||||
```
|
||||
|
||||
## Migration Status
|
||||
|
||||
- ✅ **Vault Core**: Lee's implementation is active
|
||||
- ✅ **Archive**: Sameh's implementation preserved in `vault/_archive/`
|
||||
- ⏳ **Rhai Integration**: Being developed for Lee's implementation
|
||||
- ⏳ **Examples**: Will be updated to use Lee's API
|
||||
- ❌ **Ethereum Features**: Not available in Lee's implementation
|
||||
|
||||
## Security
|
||||
|
||||
The vault uses:
|
||||
|
||||
- **ChaCha20Poly1305** for symmetric encryption
|
||||
- **Password-based key derivation** for keyspace encryption
|
||||
- **Secure key storage** with proper isolation
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Rhai Integration**: Implement Rhai bindings for Lee's vault
|
||||
2. **New Examples**: Create examples using Lee's simpler API
|
||||
3. **Documentation**: Complete API documentation for Lee's implementation
|
||||
4. **Migration Guide**: Provide guidance for users migrating from Sameh's implementation
|
233
examples/hero_vault/_archive/advanced_example.rhai
Normal file
233
examples/hero_vault/_archive/advanced_example.rhai
Normal file
@@ -0,0 +1,233 @@
|
||||
// Advanced Rhai script example for Hero Vault Cryptography Module
|
||||
// This script demonstrates conditional logic, error handling, and more complex operations
|
||||
|
||||
// Function to create a key space with error handling
|
||||
fn setup_key_space(name, password) {
|
||||
print("Attempting: Create key space: " + name);
|
||||
let result = create_key_space(name, password);
|
||||
|
||||
if result {
|
||||
print("✅ Create key space succeeded!");
|
||||
return true;
|
||||
} else {
|
||||
print("❌ Create key space failed!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function to create and select a keypair
|
||||
fn setup_keypair(name, password) {
|
||||
print("Attempting: Create keypair: " + name);
|
||||
let result = create_keypair(name, password);
|
||||
|
||||
if result {
|
||||
print("✅ Create keypair succeeded!");
|
||||
|
||||
print("Attempting: Select keypair: " + name);
|
||||
let selected = select_keypair(name);
|
||||
|
||||
if selected {
|
||||
print("✅ Select keypair succeeded!");
|
||||
return true;
|
||||
} else {
|
||||
print("❌ Select keypair failed!");
|
||||
}
|
||||
} else {
|
||||
print("❌ Create keypair failed!");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Function to sign multiple messages
|
||||
fn sign_messages(messages) {
|
||||
let signatures = [];
|
||||
|
||||
for message in messages {
|
||||
print("Signing message: " + message);
|
||||
print("Attempting: Sign message");
|
||||
let signature = sign(message);
|
||||
|
||||
if signature != "" {
|
||||
print("✅ Sign message succeeded!");
|
||||
signatures.push(#{
|
||||
message: message,
|
||||
signature: signature
|
||||
});
|
||||
} else {
|
||||
print("❌ Sign message failed!");
|
||||
}
|
||||
}
|
||||
|
||||
return signatures;
|
||||
}
|
||||
|
||||
// Function to verify signatures
|
||||
fn verify_signatures(signed_messages) {
|
||||
let results = [];
|
||||
|
||||
for item in signed_messages {
|
||||
let message = item.message;
|
||||
let signature = item.signature;
|
||||
|
||||
print("Verifying signature for: " + message);
|
||||
print("Attempting: Verify signature");
|
||||
let is_valid = verify(message, signature);
|
||||
|
||||
if is_valid {
|
||||
print("✅ Verify signature succeeded!");
|
||||
} else {
|
||||
print("❌ Verify signature failed!");
|
||||
}
|
||||
|
||||
results.push(#{
|
||||
message: message,
|
||||
valid: is_valid
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
// Function to encrypt multiple messages
|
||||
fn encrypt_messages(messages) {
|
||||
// Generate a symmetric key
|
||||
print("Attempting: Generate symmetric key");
|
||||
let key = generate_key();
|
||||
|
||||
if key == "" {
|
||||
print("❌ Generate symmetric key failed!");
|
||||
return [];
|
||||
}
|
||||
|
||||
print("✅ Generate symmetric key succeeded!");
|
||||
print("Using key: " + key);
|
||||
let encrypted_messages = [];
|
||||
|
||||
for message in messages {
|
||||
print("Encrypting message: " + message);
|
||||
print("Attempting: Encrypt message");
|
||||
let encrypted = encrypt(key, message);
|
||||
|
||||
if encrypted != "" {
|
||||
print("✅ Encrypt message succeeded!");
|
||||
encrypted_messages.push(#{
|
||||
original: message,
|
||||
encrypted: encrypted,
|
||||
key: key
|
||||
});
|
||||
} else {
|
||||
print("❌ Encrypt message failed!");
|
||||
}
|
||||
}
|
||||
|
||||
return encrypted_messages;
|
||||
}
|
||||
|
||||
// Function to decrypt messages
|
||||
fn decrypt_messages(encrypted_messages) {
|
||||
let decrypted_messages = [];
|
||||
|
||||
for item in encrypted_messages {
|
||||
let encrypted = item.encrypted;
|
||||
let key = item.key;
|
||||
let original = item.original;
|
||||
|
||||
print("Decrypting message...");
|
||||
print("Attempting: Decrypt message");
|
||||
let decrypted = decrypt(key, encrypted);
|
||||
|
||||
if decrypted != false {
|
||||
let success = decrypted == original;
|
||||
|
||||
decrypted_messages.push(#{
|
||||
decrypted: decrypted,
|
||||
original: original,
|
||||
success: success
|
||||
});
|
||||
|
||||
if success {
|
||||
print("Decryption matched original ✅");
|
||||
} else {
|
||||
print("Decryption did not match original ❌");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decrypted_messages;
|
||||
}
|
||||
|
||||
// Main script execution
|
||||
print("=== Advanced Cryptography Script ===");
|
||||
|
||||
// Set up key space
|
||||
let space_name = "advanced_space";
|
||||
let password = "secure_password123";
|
||||
|
||||
if setup_key_space(space_name, password) {
|
||||
print("\n--- Key space setup complete ---\n");
|
||||
|
||||
// Set up keypair
|
||||
if setup_keypair("advanced_keypair", password) {
|
||||
print("\n--- Keypair setup complete ---\n");
|
||||
|
||||
// Define messages to sign
|
||||
let messages = [
|
||||
"This is the first message to sign",
|
||||
"Here's another message that needs signing",
|
||||
"And a third message for good measure"
|
||||
];
|
||||
|
||||
// Sign messages
|
||||
print("\n--- Signing Messages ---\n");
|
||||
let signed_messages = sign_messages(messages);
|
||||
|
||||
// Verify signatures
|
||||
print("\n--- Verifying Signatures ---\n");
|
||||
let verification_results = verify_signatures(signed_messages);
|
||||
|
||||
// Count successful verifications
|
||||
let successful_verifications = verification_results.filter(|r| r.valid).len();
|
||||
print("Successfully verified " + successful_verifications + " out of " + verification_results.len() + " signatures");
|
||||
|
||||
// Encrypt messages
|
||||
print("\n--- Encrypting Messages ---\n");
|
||||
let encrypted_messages = encrypt_messages(messages);
|
||||
|
||||
// Decrypt messages
|
||||
print("\n--- Decrypting Messages ---\n");
|
||||
let decryption_results = decrypt_messages(encrypted_messages);
|
||||
|
||||
// Count successful decryptions
|
||||
let successful_decryptions = decryption_results.filter(|r| r.success).len();
|
||||
print("Successfully decrypted " + successful_decryptions + " out of " + decryption_results.len() + " messages");
|
||||
|
||||
// Create Ethereum wallet
|
||||
print("\n--- Creating Ethereum Wallet ---\n");
|
||||
print("Attempting: Create Ethereum wallet");
|
||||
let wallet_created = create_ethereum_wallet();
|
||||
|
||||
if wallet_created {
|
||||
print("✅ Create Ethereum wallet succeeded!");
|
||||
|
||||
print("Attempting: Get Ethereum address");
|
||||
let address = get_ethereum_address();
|
||||
|
||||
if address != "" {
|
||||
print("✅ Get Ethereum address succeeded!");
|
||||
print("Ethereum wallet address: " + address);
|
||||
} else {
|
||||
print("❌ Get Ethereum address failed!");
|
||||
}
|
||||
} else {
|
||||
print("❌ Create Ethereum wallet failed!");
|
||||
}
|
||||
|
||||
print("\n=== Script execution completed successfully! ===");
|
||||
} else {
|
||||
print("Failed to set up keypair. Aborting script.");
|
||||
}
|
||||
} else {
|
||||
print("Failed to set up key space. Aborting script.");
|
||||
}
|
152
examples/hero_vault/_archive/agung_contract_with_args.rhai
Normal file
152
examples/hero_vault/_archive/agung_contract_with_args.rhai
Normal file
@@ -0,0 +1,152 @@
|
||||
// Example Rhai script for testing contract functions with arguments on Agung network
|
||||
// This script demonstrates how to use call_contract_read and call_contract_write with arguments
|
||||
|
||||
// Step 1: Set up wallet and network
|
||||
let space_name = "agung_contract_args_demo";
|
||||
let password = "secure_password123";
|
||||
let private_key = "51c194d20bcd25360a3aa94426b3b60f738007e42f22e1bc97821c65c353e6d2";
|
||||
let network_name = "agung";
|
||||
|
||||
print("=== Testing Contract Functions With Arguments on Agung Network ===\n");
|
||||
|
||||
// Create a key space
|
||||
print("Creating key space: " + space_name);
|
||||
if create_key_space(space_name, password) {
|
||||
print("✓ Key space created successfully");
|
||||
|
||||
// Create a keypair
|
||||
print("\nCreating keypair...");
|
||||
if create_keypair("contract_key", password) {
|
||||
print("✓ Created contract keypair");
|
||||
|
||||
// Create a wallet from the private key for the Agung network
|
||||
print("\nCreating wallet from private key for Agung network...");
|
||||
if create_wallet_from_private_key_for_network(private_key, network_name) {
|
||||
print("✓ Wallet created successfully");
|
||||
|
||||
// Get the wallet address
|
||||
let wallet_address = get_wallet_address_for_network(network_name);
|
||||
print("Wallet address: " + wallet_address);
|
||||
|
||||
// Check wallet balance
|
||||
print("\nChecking wallet balance...");
|
||||
let balance = get_balance(network_name, wallet_address);
|
||||
if balance != "" {
|
||||
print("Wallet balance: " + balance + " wei");
|
||||
|
||||
// Define a simple ERC-20 token contract ABI (partial)
|
||||
let token_abi = `[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [{"name": "", "type": "uint8"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{"name": "_owner", "type": "address"}],
|
||||
"name": "balanceOf",
|
||||
"outputs": [{"name": "balance", "type": "uint256"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [{"name": "_to", "type": "address"}, {"name": "_value", "type": "uint256"}],
|
||||
"name": "transfer",
|
||||
"outputs": [{"name": "", "type": "bool"}],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]`;
|
||||
|
||||
// For this example, we'll use a test token contract on Agung
|
||||
let token_address = "0x7267B587E4416537060C6bF0B06f6Fd421106650";
|
||||
|
||||
print("\nLoading contract ABI...");
|
||||
let contract = load_contract_abi(network_name, token_address, token_abi);
|
||||
|
||||
if contract != "" {
|
||||
print("✓ Contract loaded successfully");
|
||||
|
||||
// First, let's try to read some data from the contract
|
||||
print("\nReading contract data...");
|
||||
|
||||
// Try to get token name (no arguments)
|
||||
let token_name = call_contract_read(contract, "name");
|
||||
print("Token name: " + token_name);
|
||||
|
||||
// Try to get token symbol (no arguments)
|
||||
let token_symbol = call_contract_read(contract, "symbol");
|
||||
print("Token symbol: " + token_symbol);
|
||||
|
||||
// Try to get token decimals (no arguments)
|
||||
let token_decimals = call_contract_read(contract, "decimals");
|
||||
print("Token decimals: " + token_decimals);
|
||||
|
||||
// Try to get token balance (with address argument)
|
||||
print("\nCalling balanceOf with address argument...");
|
||||
let balance = call_contract_read(contract, "balanceOf", [wallet_address]);
|
||||
print("Token balance: " + balance);
|
||||
|
||||
// Now, let's try to execute a write function with arguments
|
||||
print("\nExecuting contract write function with arguments...");
|
||||
|
||||
// Define a recipient address and amount for the transfer
|
||||
// Using a random valid address on the network
|
||||
let recipient = "0xEEdf3468E8F232A7a03D49b674bA44740C8BD8Be";
|
||||
let amount = 1000000; // Changed from string to number for uint256 compatibility
|
||||
|
||||
print("Attempting to transfer " + amount + " tokens to " + recipient);
|
||||
|
||||
// Call the transfer function with arguments
|
||||
let tx_hash = call_contract_write(contract, "transfer", [recipient, amount]);
|
||||
|
||||
if tx_hash != "" {
|
||||
print("✓ Transaction sent successfully");
|
||||
print("Transaction hash: " + tx_hash);
|
||||
print("You can view the transaction at: " + get_network_explorer_url(network_name) + "/tx/" + tx_hash);
|
||||
} else {
|
||||
print("✗ Failed to send transaction");
|
||||
print("This could be due to insufficient funds, contract issues, or other errors.");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to load contract");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to get wallet balance");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create wallet from private key");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create keypair");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create key space");
|
||||
}
|
||||
|
||||
print("\nContract function with arguments test completed");
|
104
examples/hero_vault/_archive/agung_send_transaction.rhai
Normal file
104
examples/hero_vault/_archive/agung_send_transaction.rhai
Normal file
@@ -0,0 +1,104 @@
|
||||
// Script to create an Agung wallet from a private key and send tokens
|
||||
// This script demonstrates how to create a wallet from a private key and send tokens
|
||||
|
||||
// Define the private key and recipient address
|
||||
let private_key = "0x9ecfd58eca522b0e7c109bf945966ee208cd6d593b1dc3378aedfdc60b64f512";
|
||||
let recipient_address = "0xf400f9c3F7317e19523a5DB698Ce67e7a7E083e2";
|
||||
|
||||
print("=== Agung Wallet Transaction Demo ===");
|
||||
print(`From private key: ${private_key}`);
|
||||
print(`To address: ${recipient_address}`);
|
||||
|
||||
// First, create a key space and keypair (required for the wallet infrastructure)
|
||||
let space_name = "agung_transaction_demo";
|
||||
let password = "demo_password";
|
||||
|
||||
// Create a new key space
|
||||
if !create_key_space(space_name, password) {
|
||||
print("Failed to create key space");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a keypair
|
||||
if !create_keypair("demo_keypair", password) {
|
||||
print("Failed to create keypair");
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the keypair
|
||||
if !select_keypair("demo_keypair") {
|
||||
print("Failed to select keypair");
|
||||
return;
|
||||
}
|
||||
|
||||
print("\nCreated and selected keypair successfully");
|
||||
|
||||
// Clear any existing Agung wallets to avoid conflicts
|
||||
if clear_wallets_for_network("agung") {
|
||||
print("Cleared existing Agung wallets");
|
||||
} else {
|
||||
print("Failed to clear existing Agung wallets");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a wallet from the private key directly
|
||||
print("\n=== Creating Wallet from Private Key ===");
|
||||
|
||||
// Create a wallet from the private key for the Agung network
|
||||
if create_wallet_from_private_key_for_network(private_key, "agung") {
|
||||
print("Successfully created wallet from private key for Agung network");
|
||||
|
||||
// Get the wallet address
|
||||
let wallet_address = get_wallet_address_for_network("agung");
|
||||
print(`Wallet address: ${wallet_address}`);
|
||||
|
||||
// Create a provider for the Agung network
|
||||
let provider_id = create_agung_provider();
|
||||
if provider_id != "" {
|
||||
print("Successfully created Agung provider");
|
||||
|
||||
// Check the wallet balance first
|
||||
let wallet_address = get_wallet_address_for_network("agung");
|
||||
let balance_wei = get_balance("agung", wallet_address);
|
||||
|
||||
if balance_wei == "" {
|
||||
print("Failed to get wallet balance");
|
||||
print("This could be due to network issues or other errors.");
|
||||
return;
|
||||
}
|
||||
|
||||
print(`Current wallet balance: ${balance_wei} wei`);
|
||||
|
||||
// Convert 1 AGNG to wei (1 AGNG = 10^18 wei)
|
||||
// Use string representation for large numbers
|
||||
let amount_wei_str = "1000000000000000000"; // 1 AGNG in wei as a string
|
||||
|
||||
// Check if we have enough balance
|
||||
if parse_int(balance_wei) < parse_int(amount_wei_str) {
|
||||
print(`Insufficient balance to send ${amount_wei_str} wei (1 AGNG)`);
|
||||
print(`Current balance: ${balance_wei} wei`);
|
||||
print("Please fund the wallet before attempting to send a transaction");
|
||||
return;
|
||||
}
|
||||
|
||||
print(`Attempting to send ${amount_wei_str} wei (1 AGNG) to ${recipient_address}`);
|
||||
|
||||
// Send the transaction using the blocking implementation
|
||||
let tx_hash = send_eth("agung", recipient_address, amount_wei_str);
|
||||
|
||||
if tx_hash != "" {
|
||||
print(`Transaction sent with hash: ${tx_hash}`);
|
||||
print(`You can view the transaction at: ${get_network_explorer_url("agung")}/tx/${tx_hash}`);
|
||||
} else {
|
||||
print("Transaction failed");
|
||||
print("This could be due to insufficient funds, network issues, or other errors.");
|
||||
print("Check the logs for more details.");
|
||||
}
|
||||
} else {
|
||||
print("Failed to create Agung provider");
|
||||
}
|
||||
} else {
|
||||
print("Failed to create wallet from private key");
|
||||
}
|
||||
|
||||
print("\nAgung transaction demo completed");
|
98
examples/hero_vault/_archive/contract_example.rhai
Normal file
98
examples/hero_vault/_archive/contract_example.rhai
Normal file
@@ -0,0 +1,98 @@
|
||||
// Example Rhai script for interacting with smart contracts using Hero Vault
|
||||
// This script demonstrates loading a contract ABI and interacting with a contract
|
||||
|
||||
// Step 1: Set up wallet and network
|
||||
let space_name = "contract_demo_space";
|
||||
let password = "secure_password123";
|
||||
|
||||
print("Creating key space: " + space_name);
|
||||
if create_key_space(space_name, password) {
|
||||
print("✓ Key space created successfully");
|
||||
|
||||
// Create a keypair
|
||||
print("\nCreating keypair...");
|
||||
if create_keypair("contract_key", password) {
|
||||
print("✓ Created contract keypair");
|
||||
}
|
||||
|
||||
// Step 2: Create an Ethereum wallet for Gnosis Chain
|
||||
print("\nCreating Ethereum wallet...");
|
||||
if create_ethereum_wallet() {
|
||||
print("✓ Ethereum wallet created");
|
||||
|
||||
let address = get_ethereum_address();
|
||||
print("Ethereum address: " + address);
|
||||
|
||||
// Step 3: Define a simple ERC-20 ABI (partial)
|
||||
let erc20_abi = `[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [{"name": "", "type": "string"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [{"name": "", "type": "uint8"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [{"name": "owner", "type": "address"}],
|
||||
"name": "balanceOf",
|
||||
"outputs": [{"name": "", "type": "uint256"}],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]`;
|
||||
|
||||
// Step 4: Load the contract ABI
|
||||
print("\nLoading contract ABI...");
|
||||
let contract = load_contract_abi("Gnosis", "0x4ECaBa5870353805a9F068101A40E0f32ed605C6", erc20_abi);
|
||||
if contract != "" {
|
||||
print("✓ Contract loaded successfully");
|
||||
|
||||
// Step 5: Call read-only functions
|
||||
print("\nCalling read-only functions...");
|
||||
|
||||
// Get token name
|
||||
let token_name = call_contract_read(contract, "name");
|
||||
print("Token name: " + token_name);
|
||||
|
||||
// Get token symbol
|
||||
let token_symbol = call_contract_read(contract, "symbol");
|
||||
print("Token symbol: " + token_symbol);
|
||||
|
||||
// Get token decimals
|
||||
let token_decimals = call_contract_read(contract, "decimals");
|
||||
print("Token decimals: " + token_decimals);
|
||||
|
||||
// For now, we're just demonstrating the basic structure
|
||||
} else {
|
||||
print("✗ Failed to load contract");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create Ethereum wallet");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create key space");
|
||||
}
|
||||
|
||||
print("\nContract example completed");
|
85
examples/hero_vault/_archive/example.rhai
Normal file
85
examples/hero_vault/_archive/example.rhai
Normal file
@@ -0,0 +1,85 @@
|
||||
// Example Rhai script for Hero Vault Cryptography Module
|
||||
// This script demonstrates key management, signing, and encryption
|
||||
|
||||
// Step 1: Create and manage a key space
|
||||
let space_name = "demo_space";
|
||||
let password = "secure_password123";
|
||||
|
||||
print("Creating key space: " + space_name);
|
||||
if create_key_space(space_name, password) {
|
||||
print("✓ Key space created successfully");
|
||||
|
||||
// Step 2: Create and use keypairs
|
||||
print("\nCreating keypairs...");
|
||||
if create_keypair("signing_key", password) {
|
||||
print("✓ Created signing keypair");
|
||||
}
|
||||
|
||||
if create_keypair("encryption_key", password) {
|
||||
print("✓ Created encryption keypair");
|
||||
}
|
||||
|
||||
// List all keypairs
|
||||
let keypairs = list_keypairs();
|
||||
print("Available keypairs: " + keypairs);
|
||||
|
||||
// Step 3: Sign a message
|
||||
print("\nPerforming signing operations...");
|
||||
if select_keypair("signing_key") {
|
||||
print("✓ Selected signing keypair");
|
||||
|
||||
let message = "This is a secure message that needs to be signed";
|
||||
print("Message: " + message);
|
||||
|
||||
let signature = sign(message);
|
||||
print("Signature: " + signature);
|
||||
|
||||
// Verify the signature
|
||||
let is_valid = verify(message, signature);
|
||||
if is_valid {
|
||||
print("Signature verification: ✓ Valid");
|
||||
} else {
|
||||
print("Signature verification: ✗ Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Encrypt and decrypt data
|
||||
print("\nPerforming encryption operations...");
|
||||
|
||||
// Generate a symmetric key
|
||||
let sym_key = generate_key();
|
||||
print("Generated symmetric key: " + sym_key);
|
||||
|
||||
// Encrypt a message
|
||||
let secret = "This is a top secret message that must be encrypted";
|
||||
print("Original message: " + secret);
|
||||
|
||||
let encrypted_data = encrypt(sym_key, secret);
|
||||
print("Encrypted data: " + encrypted_data);
|
||||
|
||||
// Decrypt the message
|
||||
let decrypted_data = decrypt(sym_key, encrypted_data);
|
||||
print("Decrypted message: " + decrypted_data);
|
||||
|
||||
// Verify decryption was successful
|
||||
if decrypted_data == secret {
|
||||
print("✓ Encryption/decryption successful");
|
||||
} else {
|
||||
print("✗ Encryption/decryption failed");
|
||||
}
|
||||
|
||||
// Step 5: Create an Ethereum wallet
|
||||
print("\nCreating Ethereum wallet...");
|
||||
if select_keypair("encryption_key") {
|
||||
print("✓ Selected keypair for Ethereum wallet");
|
||||
|
||||
if create_ethereum_wallet() {
|
||||
print("✓ Ethereum wallet created");
|
||||
|
||||
let address = get_ethereum_address();
|
||||
print("Ethereum address: " + address);
|
||||
}
|
||||
}
|
||||
|
||||
print("\nScript execution completed successfully!");
|
||||
}
|
65
examples/hero_vault/_archive/key_persistence_example.rhai
Normal file
65
examples/hero_vault/_archive/key_persistence_example.rhai
Normal file
@@ -0,0 +1,65 @@
|
||||
// Example Rhai script demonstrating key space persistence for Hero Vault
|
||||
// This script shows how to create, save, and load key spaces
|
||||
|
||||
// Step 1: Create a key space
|
||||
let space_name = "persistent_space";
|
||||
let password = "secure_password123";
|
||||
|
||||
print("Creating key space: " + space_name);
|
||||
if create_key_space(space_name, password) {
|
||||
print("✓ Key space created successfully");
|
||||
|
||||
// Step 2: Create keypairs in this space
|
||||
print("\nCreating keypairs...");
|
||||
if create_keypair("persistent_key1", password) {
|
||||
print("✓ Created first keypair");
|
||||
}
|
||||
|
||||
if create_keypair("persistent_key2", password) {
|
||||
print("✓ Created second keypair");
|
||||
}
|
||||
|
||||
// List all keypairs
|
||||
let keypairs = list_keypairs();
|
||||
print("Available keypairs: " + keypairs);
|
||||
|
||||
// Step 3: Clear the session (simulate closing and reopening the CLI)
|
||||
print("\nClearing session (simulating restart)...");
|
||||
// Note: In a real script, you would exit here and run a new script
|
||||
// For demonstration purposes, we'll continue in the same script
|
||||
|
||||
// Step 4: Load the key space from disk
|
||||
print("\nLoading key space from disk...");
|
||||
if load_key_space(space_name, password) {
|
||||
print("✓ Key space loaded successfully");
|
||||
|
||||
// Verify the keypairs are still available
|
||||
let loaded_keypairs = list_keypairs();
|
||||
print("Keypairs after loading: " + loaded_keypairs);
|
||||
|
||||
// Step 5: Use a keypair from the loaded space
|
||||
print("\nSelecting and using a keypair...");
|
||||
if select_keypair("persistent_key1") {
|
||||
print("✓ Selected keypair");
|
||||
|
||||
let message = "This message was signed using a keypair from a loaded key space";
|
||||
let signature = sign(message);
|
||||
print("Message: " + message);
|
||||
print("Signature: " + signature);
|
||||
|
||||
// Verify the signature
|
||||
let is_valid = verify(message, signature);
|
||||
if is_valid {
|
||||
print("Signature verification: ✓ Valid");
|
||||
} else {
|
||||
print("Signature verification: ✗ Invalid");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to load key space");
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to create key space");
|
||||
}
|
||||
|
||||
print("\nScript execution completed!");
|
65
examples/hero_vault/_archive/load_existing_space.rhai
Normal file
65
examples/hero_vault/_archive/load_existing_space.rhai
Normal file
@@ -0,0 +1,65 @@
|
||||
// Example Rhai script demonstrating loading an existing key space for Hero Vault
|
||||
// This script shows how to load a previously created key space and use its keypairs
|
||||
|
||||
// Define the key space name and password
|
||||
let space_name = "persistent_space";
|
||||
let password = "secure_password123";
|
||||
|
||||
print("Loading existing key space: " + space_name);
|
||||
|
||||
// Load the key space from disk
|
||||
if load_key_space(space_name, password) {
|
||||
print("✓ Key space loaded successfully");
|
||||
|
||||
// List available keypairs
|
||||
let keypairs = list_keypairs();
|
||||
print("Available keypairs: " + keypairs);
|
||||
|
||||
// Use both keypairs to sign different messages
|
||||
if select_keypair("persistent_key1") {
|
||||
print("\nUsing persistent_key1:");
|
||||
let message1 = "Message signed with the first keypair";
|
||||
let signature1 = sign(message1);
|
||||
print("Message: " + message1);
|
||||
print("Signature: " + signature1);
|
||||
|
||||
let is_valid1 = verify(message1, signature1);
|
||||
if is_valid1 {
|
||||
print("Verification: ✓ Valid");
|
||||
} else {
|
||||
print("Verification: ✗ Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
if select_keypair("persistent_key2") {
|
||||
print("\nUsing persistent_key2:");
|
||||
let message2 = "Message signed with the second keypair";
|
||||
let signature2 = sign(message2);
|
||||
print("Message: " + message2);
|
||||
print("Signature: " + signature2);
|
||||
|
||||
let is_valid2 = verify(message2, signature2);
|
||||
if is_valid2 {
|
||||
print("Verification: ✓ Valid");
|
||||
} else {
|
||||
print("Verification: ✗ Invalid");
|
||||
}
|
||||
}
|
||||
|
||||
// Create an Ethereum wallet using one of the keypairs
|
||||
print("\nCreating Ethereum wallet from persistent keypair:");
|
||||
if select_keypair("persistent_key1") {
|
||||
if create_ethereum_wallet() {
|
||||
print("✓ Ethereum wallet created");
|
||||
|
||||
let address = get_ethereum_address();
|
||||
print("Ethereum address: " + address);
|
||||
} else {
|
||||
print("✗ Failed to create Ethereum wallet");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print("✗ Failed to load key space. Make sure you've run key_persistence_example.rhai first.");
|
||||
}
|
||||
|
||||
print("\nScript execution completed!");
|
72
examples/kubernetes/basic_operations.rhai
Normal file
72
examples/kubernetes/basic_operations.rhai
Normal file
@@ -0,0 +1,72 @@
|
||||
//! Basic Kubernetes operations example
|
||||
//!
|
||||
//! This script demonstrates basic Kubernetes operations using the SAL Kubernetes module.
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - A running Kubernetes cluster
|
||||
//! - Valid kubeconfig file or in-cluster configuration
|
||||
//! - Appropriate permissions for the operations
|
||||
//!
|
||||
//! Usage:
|
||||
//! herodo examples/kubernetes/basic_operations.rhai
|
||||
|
||||
print("=== SAL Kubernetes Basic Operations Example ===");
|
||||
|
||||
// Create a KubernetesManager for the default namespace
|
||||
print("Creating KubernetesManager for 'default' namespace...");
|
||||
let km = kubernetes_manager_new("default");
|
||||
print("✓ KubernetesManager created for namespace: " + namespace(km));
|
||||
|
||||
// List all pods in the namespace
|
||||
print("\n--- Listing Pods ---");
|
||||
let pods = pods_list(km);
|
||||
print("Found " + pods.len() + " pods in the namespace:");
|
||||
for pod in pods {
|
||||
print(" - " + pod);
|
||||
}
|
||||
|
||||
// List all services in the namespace
|
||||
print("\n--- Listing Services ---");
|
||||
let services = services_list(km);
|
||||
print("Found " + services.len() + " services in the namespace:");
|
||||
for service in services {
|
||||
print(" - " + service);
|
||||
}
|
||||
|
||||
// List all deployments in the namespace
|
||||
print("\n--- Listing Deployments ---");
|
||||
let deployments = deployments_list(km);
|
||||
print("Found " + deployments.len() + " deployments in the namespace:");
|
||||
for deployment in deployments {
|
||||
print(" - " + deployment);
|
||||
}
|
||||
|
||||
// Get resource counts
|
||||
print("\n--- Resource Counts ---");
|
||||
let counts = resource_counts(km);
|
||||
print("Resource counts in namespace '" + namespace(km) + "':");
|
||||
for resource_type in counts.keys() {
|
||||
print(" " + resource_type + ": " + counts[resource_type]);
|
||||
}
|
||||
|
||||
// List all namespaces (cluster-wide operation)
|
||||
print("\n--- Listing All Namespaces ---");
|
||||
let namespaces = namespaces_list(km);
|
||||
print("Found " + namespaces.len() + " namespaces in the cluster:");
|
||||
for ns in namespaces {
|
||||
print(" - " + ns);
|
||||
}
|
||||
|
||||
// Check if specific namespaces exist
|
||||
print("\n--- Checking Namespace Existence ---");
|
||||
let test_namespaces = ["default", "kube-system", "non-existent-namespace"];
|
||||
for ns in test_namespaces {
|
||||
let exists = namespace_exists(km, ns);
|
||||
if exists {
|
||||
print("✓ Namespace '" + ns + "' exists");
|
||||
} else {
|
||||
print("✗ Namespace '" + ns + "' does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
print("\n=== Example completed successfully! ===");
|
134
examples/kubernetes/clusters/generic.rs
Normal file
134
examples/kubernetes/clusters/generic.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
//! Generic Application Deployment Example
|
||||
//!
|
||||
//! This example shows how to deploy any containerized application using the
|
||||
//! KubernetesManager convenience methods. This works for any Docker image.
|
||||
|
||||
use sal_kubernetes::KubernetesManager;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create Kubernetes manager
|
||||
let km = KubernetesManager::new("default").await?;
|
||||
|
||||
// Clean up any existing resources first
|
||||
println!("=== Cleaning up existing resources ===");
|
||||
let apps_to_clean = ["web-server", "node-app", "mongodb"];
|
||||
|
||||
for app in &apps_to_clean {
|
||||
match km.deployment_delete(app).await {
|
||||
Ok(_) => println!("✓ Deleted existing deployment: {}", app),
|
||||
Err(_) => println!("✓ No existing deployment to delete: {}", app),
|
||||
}
|
||||
|
||||
match km.service_delete(app).await {
|
||||
Ok(_) => println!("✓ Deleted existing service: {}", app),
|
||||
Err(_) => println!("✓ No existing service to delete: {}", app),
|
||||
}
|
||||
}
|
||||
|
||||
// Example 1: Simple web server deployment
|
||||
println!("\n=== Example 1: Simple Nginx Web Server ===");
|
||||
|
||||
km.deploy_application("web-server", "nginx:latest", 2, 80, None, None)
|
||||
.await?;
|
||||
println!("✅ Nginx web server deployed!");
|
||||
|
||||
// Example 2: Node.js application with labels
|
||||
println!("\n=== Example 2: Node.js Application ===");
|
||||
|
||||
let mut node_labels = HashMap::new();
|
||||
node_labels.insert("app".to_string(), "node-app".to_string());
|
||||
node_labels.insert("tier".to_string(), "backend".to_string());
|
||||
node_labels.insert("environment".to_string(), "production".to_string());
|
||||
|
||||
// Configure Node.js environment variables
|
||||
let mut node_env_vars = HashMap::new();
|
||||
node_env_vars.insert("NODE_ENV".to_string(), "production".to_string());
|
||||
node_env_vars.insert("PORT".to_string(), "3000".to_string());
|
||||
node_env_vars.insert("LOG_LEVEL".to_string(), "info".to_string());
|
||||
node_env_vars.insert("MAX_CONNECTIONS".to_string(), "1000".to_string());
|
||||
|
||||
km.deploy_application(
|
||||
"node-app", // name
|
||||
"node:18-alpine", // image
|
||||
3, // replicas - scale to 3 instances
|
||||
3000, // port
|
||||
Some(node_labels), // labels
|
||||
Some(node_env_vars), // environment variables
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("✅ Node.js application deployed!");
|
||||
|
||||
// Example 3: Database deployment (any database)
|
||||
println!("\n=== Example 3: MongoDB Database ===");
|
||||
|
||||
let mut mongo_labels = HashMap::new();
|
||||
mongo_labels.insert("app".to_string(), "mongodb".to_string());
|
||||
mongo_labels.insert("type".to_string(), "database".to_string());
|
||||
mongo_labels.insert("engine".to_string(), "mongodb".to_string());
|
||||
|
||||
// Configure MongoDB environment variables
|
||||
let mut mongo_env_vars = HashMap::new();
|
||||
mongo_env_vars.insert(
|
||||
"MONGO_INITDB_ROOT_USERNAME".to_string(),
|
||||
"admin".to_string(),
|
||||
);
|
||||
mongo_env_vars.insert(
|
||||
"MONGO_INITDB_ROOT_PASSWORD".to_string(),
|
||||
"mongopassword".to_string(),
|
||||
);
|
||||
mongo_env_vars.insert("MONGO_INITDB_DATABASE".to_string(), "myapp".to_string());
|
||||
|
||||
km.deploy_application(
|
||||
"mongodb", // name
|
||||
"mongo:6.0", // image
|
||||
1, // replicas - single instance for simplicity
|
||||
27017, // port
|
||||
Some(mongo_labels), // labels
|
||||
Some(mongo_env_vars), // environment variables
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("✅ MongoDB deployed!");
|
||||
|
||||
// Check status of all deployments
|
||||
println!("\n=== Checking Deployment Status ===");
|
||||
|
||||
let deployments = km.deployments_list().await?;
|
||||
|
||||
for deployment in &deployments {
|
||||
if let Some(name) = &deployment.metadata.name {
|
||||
let total_replicas = deployment
|
||||
.spec
|
||||
.as_ref()
|
||||
.and_then(|s| s.replicas)
|
||||
.unwrap_or(0);
|
||||
let ready_replicas = deployment
|
||||
.status
|
||||
.as_ref()
|
||||
.and_then(|s| s.ready_replicas)
|
||||
.unwrap_or(0);
|
||||
|
||||
println!(
|
||||
"{}: {}/{} replicas ready",
|
||||
name, ready_replicas, total_replicas
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n🎉 All deployments completed!");
|
||||
println!("\n💡 Key Points:");
|
||||
println!(" • Any Docker image can be deployed using this simple interface");
|
||||
println!(" • Use labels to organize and identify your applications");
|
||||
println!(
|
||||
" • The same method works for databases, web servers, APIs, and any containerized app"
|
||||
);
|
||||
println!(" • For advanced configuration, use the individual KubernetesManager methods");
|
||||
println!(
|
||||
" • Environment variables and resource limits can be added via direct Kubernetes API"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
79
examples/kubernetes/clusters/postgres.rhai
Normal file
79
examples/kubernetes/clusters/postgres.rhai
Normal file
@@ -0,0 +1,79 @@
|
||||
//! PostgreSQL Cluster Deployment Example (Rhai)
|
||||
//!
|
||||
//! This script shows how to deploy a PostgreSQL cluster using Rhai scripting
|
||||
//! with the KubernetesManager convenience methods.
|
||||
|
||||
print("=== PostgreSQL Cluster Deployment ===");
|
||||
|
||||
// Create Kubernetes manager for the database namespace
|
||||
print("Creating Kubernetes manager for 'database' namespace...");
|
||||
let km = kubernetes_manager_new("database");
|
||||
print("✓ Kubernetes manager created");
|
||||
|
||||
// Create the namespace if it doesn't exist
|
||||
print("Creating namespace 'database' if it doesn't exist...");
|
||||
try {
|
||||
create_namespace(km, "database");
|
||||
print("✓ Namespace 'database' created");
|
||||
} catch(e) {
|
||||
if e.to_string().contains("already exists") {
|
||||
print("✓ Namespace 'database' already exists");
|
||||
} else {
|
||||
print("⚠️ Warning: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing resources first
|
||||
print("\nCleaning up any existing PostgreSQL resources...");
|
||||
try {
|
||||
delete_deployment(km, "postgres-cluster");
|
||||
print("✓ Deleted existing deployment");
|
||||
} catch(e) {
|
||||
print("✓ No existing deployment to delete");
|
||||
}
|
||||
|
||||
try {
|
||||
delete_service(km, "postgres-cluster");
|
||||
print("✓ Deleted existing service");
|
||||
} catch(e) {
|
||||
print("✓ No existing service to delete");
|
||||
}
|
||||
|
||||
// Create PostgreSQL cluster using the convenience method
|
||||
print("\nDeploying PostgreSQL cluster...");
|
||||
|
||||
try {
|
||||
// Deploy PostgreSQL using the convenience method
|
||||
let result = deploy_application(km, "postgres-cluster", "postgres:15", 2, 5432, #{
|
||||
"app": "postgres-cluster",
|
||||
"type": "database",
|
||||
"engine": "postgresql"
|
||||
}, #{
|
||||
"POSTGRES_DB": "myapp",
|
||||
"POSTGRES_USER": "postgres",
|
||||
"POSTGRES_PASSWORD": "secretpassword",
|
||||
"PGDATA": "/var/lib/postgresql/data/pgdata"
|
||||
});
|
||||
print("✓ " + result);
|
||||
|
||||
print("\n✅ PostgreSQL cluster deployed successfully!");
|
||||
|
||||
print("\n📋 Connection Information:");
|
||||
print(" Host: postgres-cluster.database.svc.cluster.local");
|
||||
print(" Port: 5432");
|
||||
print(" Database: postgres (default)");
|
||||
print(" Username: postgres (default)");
|
||||
|
||||
print("\n🔧 To connect from another pod:");
|
||||
print(" psql -h postgres-cluster.database.svc.cluster.local -U postgres");
|
||||
|
||||
print("\n💡 Next steps:");
|
||||
print(" • Set POSTGRES_PASSWORD environment variable");
|
||||
print(" • Configure persistent storage");
|
||||
print(" • Set up backup and monitoring");
|
||||
|
||||
} catch(e) {
|
||||
print("❌ Failed to deploy PostgreSQL cluster: " + e);
|
||||
}
|
||||
|
||||
print("\n=== Deployment Complete ===");
|
112
examples/kubernetes/clusters/postgres.rs
Normal file
112
examples/kubernetes/clusters/postgres.rs
Normal file
@@ -0,0 +1,112 @@
|
||||
//! PostgreSQL Cluster Deployment Example
|
||||
//!
|
||||
//! This example shows how to deploy a PostgreSQL cluster using the
|
||||
//! KubernetesManager convenience methods.
|
||||
|
||||
use sal_kubernetes::KubernetesManager;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create Kubernetes manager for the database namespace
|
||||
let km = KubernetesManager::new("database").await?;
|
||||
|
||||
// Create the namespace if it doesn't exist
|
||||
println!("Creating namespace 'database' if it doesn't exist...");
|
||||
match km.namespace_create("database").await {
|
||||
Ok(_) => println!("✓ Namespace 'database' created"),
|
||||
Err(e) => {
|
||||
if e.to_string().contains("already exists") {
|
||||
println!("✓ Namespace 'database' already exists");
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing resources first
|
||||
println!("Cleaning up any existing PostgreSQL resources...");
|
||||
match km.deployment_delete("postgres-cluster").await {
|
||||
Ok(_) => println!("✓ Deleted existing deployment"),
|
||||
Err(_) => println!("✓ No existing deployment to delete"),
|
||||
}
|
||||
|
||||
match km.service_delete("postgres-cluster").await {
|
||||
Ok(_) => println!("✓ Deleted existing service"),
|
||||
Err(_) => println!("✓ No existing service to delete"),
|
||||
}
|
||||
|
||||
// Configure PostgreSQL-specific labels
|
||||
let mut labels = HashMap::new();
|
||||
labels.insert("app".to_string(), "postgres-cluster".to_string());
|
||||
labels.insert("type".to_string(), "database".to_string());
|
||||
labels.insert("engine".to_string(), "postgresql".to_string());
|
||||
|
||||
// Configure PostgreSQL environment variables
|
||||
let mut env_vars = HashMap::new();
|
||||
env_vars.insert("POSTGRES_DB".to_string(), "myapp".to_string());
|
||||
env_vars.insert("POSTGRES_USER".to_string(), "postgres".to_string());
|
||||
env_vars.insert(
|
||||
"POSTGRES_PASSWORD".to_string(),
|
||||
"secretpassword".to_string(),
|
||||
);
|
||||
env_vars.insert(
|
||||
"PGDATA".to_string(),
|
||||
"/var/lib/postgresql/data/pgdata".to_string(),
|
||||
);
|
||||
|
||||
// Deploy the PostgreSQL cluster using the convenience method
|
||||
println!("Deploying PostgreSQL cluster...");
|
||||
km.deploy_application(
|
||||
"postgres-cluster", // name
|
||||
"postgres:15", // image
|
||||
2, // replicas (1 master + 1 replica)
|
||||
5432, // port
|
||||
Some(labels), // labels
|
||||
Some(env_vars), // environment variables
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("✅ PostgreSQL cluster deployed successfully!");
|
||||
|
||||
// Check deployment status
|
||||
let deployments = km.deployments_list().await?;
|
||||
let postgres_deployment = deployments
|
||||
.iter()
|
||||
.find(|d| d.metadata.name.as_ref() == Some(&"postgres-cluster".to_string()));
|
||||
|
||||
if let Some(deployment) = postgres_deployment {
|
||||
let total_replicas = deployment
|
||||
.spec
|
||||
.as_ref()
|
||||
.and_then(|s| s.replicas)
|
||||
.unwrap_or(0);
|
||||
let ready_replicas = deployment
|
||||
.status
|
||||
.as_ref()
|
||||
.and_then(|s| s.ready_replicas)
|
||||
.unwrap_or(0);
|
||||
|
||||
println!(
|
||||
"Deployment status: {}/{} replicas ready",
|
||||
ready_replicas, total_replicas
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n📋 Connection Information:");
|
||||
println!(" Host: postgres-cluster.database.svc.cluster.local");
|
||||
println!(" Port: 5432");
|
||||
println!(" Database: postgres (default)");
|
||||
println!(" Username: postgres (default)");
|
||||
println!(" Password: Set POSTGRES_PASSWORD environment variable");
|
||||
|
||||
println!("\n🔧 To connect from another pod:");
|
||||
println!(" psql -h postgres-cluster.database.svc.cluster.local -U postgres");
|
||||
|
||||
println!("\n💡 Next steps:");
|
||||
println!(" • Set environment variables for database credentials");
|
||||
println!(" • Add persistent volume claims for data storage");
|
||||
println!(" • Configure backup and monitoring");
|
||||
|
||||
Ok(())
|
||||
}
|
79
examples/kubernetes/clusters/redis.rhai
Normal file
79
examples/kubernetes/clusters/redis.rhai
Normal file
@@ -0,0 +1,79 @@
|
||||
//! Redis Cluster Deployment Example (Rhai)
|
||||
//!
|
||||
//! This script shows how to deploy a Redis cluster using Rhai scripting
|
||||
//! with the KubernetesManager convenience methods.
|
||||
|
||||
print("=== Redis Cluster Deployment ===");
|
||||
|
||||
// Create Kubernetes manager for the cache namespace
|
||||
print("Creating Kubernetes manager for 'cache' namespace...");
|
||||
let km = kubernetes_manager_new("cache");
|
||||
print("✓ Kubernetes manager created");
|
||||
|
||||
// Create the namespace if it doesn't exist
|
||||
print("Creating namespace 'cache' if it doesn't exist...");
|
||||
try {
|
||||
create_namespace(km, "cache");
|
||||
print("✓ Namespace 'cache' created");
|
||||
} catch(e) {
|
||||
if e.to_string().contains("already exists") {
|
||||
print("✓ Namespace 'cache' already exists");
|
||||
} else {
|
||||
print("⚠️ Warning: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing resources first
|
||||
print("\nCleaning up any existing Redis resources...");
|
||||
try {
|
||||
delete_deployment(km, "redis-cluster");
|
||||
print("✓ Deleted existing deployment");
|
||||
} catch(e) {
|
||||
print("✓ No existing deployment to delete");
|
||||
}
|
||||
|
||||
try {
|
||||
delete_service(km, "redis-cluster");
|
||||
print("✓ Deleted existing service");
|
||||
} catch(e) {
|
||||
print("✓ No existing service to delete");
|
||||
}
|
||||
|
||||
// Create Redis cluster using the convenience method
|
||||
print("\nDeploying Redis cluster...");
|
||||
|
||||
try {
|
||||
// Deploy Redis using the convenience method
|
||||
let result = deploy_application(km, "redis-cluster", "redis:7-alpine", 3, 6379, #{
|
||||
"app": "redis-cluster",
|
||||
"type": "cache",
|
||||
"engine": "redis"
|
||||
}, #{
|
||||
"REDIS_PASSWORD": "redispassword",
|
||||
"REDIS_PORT": "6379",
|
||||
"REDIS_DATABASES": "16",
|
||||
"REDIS_MAXMEMORY": "256mb",
|
||||
"REDIS_MAXMEMORY_POLICY": "allkeys-lru"
|
||||
});
|
||||
print("✓ " + result);
|
||||
|
||||
print("\n✅ Redis cluster deployed successfully!");
|
||||
|
||||
print("\n📋 Connection Information:");
|
||||
print(" Host: redis-cluster.cache.svc.cluster.local");
|
||||
print(" Port: 6379");
|
||||
|
||||
print("\n🔧 To connect from another pod:");
|
||||
print(" redis-cli -h redis-cluster.cache.svc.cluster.local");
|
||||
|
||||
print("\n💡 Next steps:");
|
||||
print(" • Configure Redis authentication");
|
||||
print(" • Set up Redis clustering configuration");
|
||||
print(" • Add persistent storage");
|
||||
print(" • Configure memory policies");
|
||||
|
||||
} catch(e) {
|
||||
print("❌ Failed to deploy Redis cluster: " + e);
|
||||
}
|
||||
|
||||
print("\n=== Deployment Complete ===");
|
109
examples/kubernetes/clusters/redis.rs
Normal file
109
examples/kubernetes/clusters/redis.rs
Normal file
@@ -0,0 +1,109 @@
|
||||
//! Redis Cluster Deployment Example
|
||||
//!
|
||||
//! This example shows how to deploy a Redis cluster using the
|
||||
//! KubernetesManager convenience methods.
|
||||
|
||||
use sal_kubernetes::KubernetesManager;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Create Kubernetes manager for the cache namespace
|
||||
let km = KubernetesManager::new("cache").await?;
|
||||
|
||||
// Create the namespace if it doesn't exist
|
||||
println!("Creating namespace 'cache' if it doesn't exist...");
|
||||
match km.namespace_create("cache").await {
|
||||
Ok(_) => println!("✓ Namespace 'cache' created"),
|
||||
Err(e) => {
|
||||
if e.to_string().contains("already exists") {
|
||||
println!("✓ Namespace 'cache' already exists");
|
||||
} else {
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any existing resources first
|
||||
println!("Cleaning up any existing Redis resources...");
|
||||
match km.deployment_delete("redis-cluster").await {
|
||||
Ok(_) => println!("✓ Deleted existing deployment"),
|
||||
Err(_) => println!("✓ No existing deployment to delete"),
|
||||
}
|
||||
|
||||
match km.service_delete("redis-cluster").await {
|
||||
Ok(_) => println!("✓ Deleted existing service"),
|
||||
Err(_) => println!("✓ No existing service to delete"),
|
||||
}
|
||||
|
||||
// Configure Redis-specific labels
|
||||
let mut labels = HashMap::new();
|
||||
labels.insert("app".to_string(), "redis-cluster".to_string());
|
||||
labels.insert("type".to_string(), "cache".to_string());
|
||||
labels.insert("engine".to_string(), "redis".to_string());
|
||||
|
||||
// Configure Redis environment variables
|
||||
let mut env_vars = HashMap::new();
|
||||
env_vars.insert("REDIS_PASSWORD".to_string(), "redispassword".to_string());
|
||||
env_vars.insert("REDIS_PORT".to_string(), "6379".to_string());
|
||||
env_vars.insert("REDIS_DATABASES".to_string(), "16".to_string());
|
||||
env_vars.insert("REDIS_MAXMEMORY".to_string(), "256mb".to_string());
|
||||
env_vars.insert(
|
||||
"REDIS_MAXMEMORY_POLICY".to_string(),
|
||||
"allkeys-lru".to_string(),
|
||||
);
|
||||
|
||||
// Deploy the Redis cluster using the convenience method
|
||||
println!("Deploying Redis cluster...");
|
||||
km.deploy_application(
|
||||
"redis-cluster", // name
|
||||
"redis:7-alpine", // image
|
||||
3, // replicas (Redis cluster nodes)
|
||||
6379, // port
|
||||
Some(labels), // labels
|
||||
Some(env_vars), // environment variables
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("✅ Redis cluster deployed successfully!");
|
||||
|
||||
// Check deployment status
|
||||
let deployments = km.deployments_list().await?;
|
||||
let redis_deployment = deployments
|
||||
.iter()
|
||||
.find(|d| d.metadata.name.as_ref() == Some(&"redis-cluster".to_string()));
|
||||
|
||||
if let Some(deployment) = redis_deployment {
|
||||
let total_replicas = deployment
|
||||
.spec
|
||||
.as_ref()
|
||||
.and_then(|s| s.replicas)
|
||||
.unwrap_or(0);
|
||||
let ready_replicas = deployment
|
||||
.status
|
||||
.as_ref()
|
||||
.and_then(|s| s.ready_replicas)
|
||||
.unwrap_or(0);
|
||||
|
||||
println!(
|
||||
"Deployment status: {}/{} replicas ready",
|
||||
ready_replicas, total_replicas
|
||||
);
|
||||
}
|
||||
|
||||
println!("\n📋 Connection Information:");
|
||||
println!(" Host: redis-cluster.cache.svc.cluster.local");
|
||||
println!(" Port: 6379");
|
||||
println!(" Password: Configure REDIS_PASSWORD environment variable");
|
||||
|
||||
println!("\n🔧 To connect from another pod:");
|
||||
println!(" redis-cli -h redis-cluster.cache.svc.cluster.local");
|
||||
|
||||
println!("\n💡 Next steps:");
|
||||
println!(" • Configure Redis authentication with environment variables");
|
||||
println!(" • Set up Redis clustering configuration");
|
||||
println!(" • Add persistent volume claims for data persistence");
|
||||
println!(" • Configure memory limits and eviction policies");
|
||||
|
||||
Ok(())
|
||||
}
|
208
examples/kubernetes/multi_namespace_operations.rhai
Normal file
208
examples/kubernetes/multi_namespace_operations.rhai
Normal file
@@ -0,0 +1,208 @@
|
||||
//! Multi-namespace Kubernetes operations example
|
||||
//!
|
||||
//! This script demonstrates working with multiple namespaces and comparing resources across them.
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - A running Kubernetes cluster
|
||||
//! - Valid kubeconfig file or in-cluster configuration
|
||||
//! - Appropriate permissions for the operations
|
||||
//!
|
||||
//! Usage:
|
||||
//! herodo examples/kubernetes/multi_namespace_operations.rhai
|
||||
|
||||
print("=== SAL Kubernetes Multi-Namespace Operations Example ===");
|
||||
|
||||
// Define namespaces to work with
|
||||
let target_namespaces = ["default", "kube-system"];
|
||||
let managers = #{};
|
||||
|
||||
print("Creating managers for multiple namespaces...");
|
||||
|
||||
// Create managers for each namespace
|
||||
for ns in target_namespaces {
|
||||
try {
|
||||
let km = kubernetes_manager_new(ns);
|
||||
managers[ns] = km;
|
||||
print("✓ Created manager for namespace: " + ns);
|
||||
} catch(e) {
|
||||
print("✗ Failed to create manager for " + ns + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to safely get resource counts
|
||||
fn get_safe_counts(km) {
|
||||
try {
|
||||
return resource_counts(km);
|
||||
} catch(e) {
|
||||
print(" Warning: Could not get resource counts - " + e);
|
||||
return #{};
|
||||
}
|
||||
}
|
||||
|
||||
// Function to safely get pod list
|
||||
fn get_safe_pods(km) {
|
||||
try {
|
||||
return pods_list(km);
|
||||
} catch(e) {
|
||||
print(" Warning: Could not list pods - " + e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Compare resource counts across namespaces
|
||||
print("\n--- Resource Comparison Across Namespaces ---");
|
||||
let total_resources = #{};
|
||||
|
||||
for ns in target_namespaces {
|
||||
if ns in managers {
|
||||
let km = managers[ns];
|
||||
print("\nNamespace: " + ns);
|
||||
let counts = get_safe_counts(km);
|
||||
|
||||
for resource_type in counts.keys() {
|
||||
let count = counts[resource_type];
|
||||
print(" " + resource_type + ": " + count);
|
||||
|
||||
// Accumulate totals
|
||||
if resource_type in total_resources {
|
||||
total_resources[resource_type] = total_resources[resource_type] + count;
|
||||
} else {
|
||||
total_resources[resource_type] = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("\n--- Total Resources Across All Namespaces ---");
|
||||
for resource_type in total_resources.keys() {
|
||||
print("Total " + resource_type + ": " + total_resources[resource_type]);
|
||||
}
|
||||
|
||||
// Find namespaces with the most resources
|
||||
print("\n--- Namespace Resource Analysis ---");
|
||||
let namespace_totals = #{};
|
||||
|
||||
for ns in target_namespaces {
|
||||
if ns in managers {
|
||||
let km = managers[ns];
|
||||
let counts = get_safe_counts(km);
|
||||
let total = 0;
|
||||
|
||||
for resource_type in counts.keys() {
|
||||
total = total + counts[resource_type];
|
||||
}
|
||||
|
||||
namespace_totals[ns] = total;
|
||||
print("Namespace '" + ns + "' has " + total + " total resources");
|
||||
}
|
||||
}
|
||||
|
||||
// Find the busiest namespace
|
||||
let busiest_ns = "";
|
||||
let max_resources = 0;
|
||||
for ns in namespace_totals.keys() {
|
||||
if namespace_totals[ns] > max_resources {
|
||||
max_resources = namespace_totals[ns];
|
||||
busiest_ns = ns;
|
||||
}
|
||||
}
|
||||
|
||||
if busiest_ns != "" {
|
||||
print("🏆 Busiest namespace: '" + busiest_ns + "' with " + max_resources + " resources");
|
||||
}
|
||||
|
||||
// Detailed pod analysis
|
||||
print("\n--- Pod Analysis Across Namespaces ---");
|
||||
let all_pods = [];
|
||||
|
||||
for ns in target_namespaces {
|
||||
if ns in managers {
|
||||
let km = managers[ns];
|
||||
let pods = get_safe_pods(km);
|
||||
|
||||
print("\nNamespace '" + ns + "' pods:");
|
||||
if pods.len() == 0 {
|
||||
print(" (no pods)");
|
||||
} else {
|
||||
for pod in pods {
|
||||
print(" - " + pod);
|
||||
all_pods.push(ns + "/" + pod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("\n--- All Pods Summary ---");
|
||||
print("Total pods across all namespaces: " + all_pods.len());
|
||||
|
||||
// Look for common pod name patterns
|
||||
print("\n--- Pod Name Pattern Analysis ---");
|
||||
let patterns = #{
|
||||
"system": 0,
|
||||
"kube": 0,
|
||||
"coredns": 0,
|
||||
"proxy": 0,
|
||||
"controller": 0
|
||||
};
|
||||
|
||||
for pod_full_name in all_pods {
|
||||
let pod_name = pod_full_name.to_lower();
|
||||
|
||||
for pattern in patterns.keys() {
|
||||
if pod_name.contains(pattern) {
|
||||
patterns[pattern] = patterns[pattern] + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print("Common pod name patterns found:");
|
||||
for pattern in patterns.keys() {
|
||||
if patterns[pattern] > 0 {
|
||||
print(" '" + pattern + "': " + patterns[pattern] + " pods");
|
||||
}
|
||||
}
|
||||
|
||||
// Namespace health check
|
||||
print("\n--- Namespace Health Check ---");
|
||||
for ns in target_namespaces {
|
||||
if ns in managers {
|
||||
let km = managers[ns];
|
||||
print("\nChecking namespace: " + ns);
|
||||
|
||||
// Check if namespace exists (should always be true for our managers)
|
||||
let exists = namespace_exists(km, ns);
|
||||
if exists {
|
||||
print(" ✓ Namespace exists and is accessible");
|
||||
} else {
|
||||
print(" ✗ Namespace existence check failed");
|
||||
}
|
||||
|
||||
// Try to get resource counts as a health indicator
|
||||
let counts = get_safe_counts(km);
|
||||
if counts.len() > 0 {
|
||||
print(" ✓ Can access resources (" + counts.len() + " resource types)");
|
||||
} else {
|
||||
print(" ⚠ No resources found or access limited");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a summary report
|
||||
print("\n--- Summary Report ---");
|
||||
print("Namespaces analyzed: " + target_namespaces.len());
|
||||
print("Total unique resource types: " + total_resources.len());
|
||||
|
||||
let grand_total = 0;
|
||||
for resource_type in total_resources.keys() {
|
||||
grand_total = grand_total + total_resources[resource_type];
|
||||
}
|
||||
print("Grand total resources: " + grand_total);
|
||||
|
||||
print("\nResource breakdown:");
|
||||
for resource_type in total_resources.keys() {
|
||||
let count = total_resources[resource_type];
|
||||
let percentage = (count * 100) / grand_total;
|
||||
print(" " + resource_type + ": " + count + " (" + percentage + "%)");
|
||||
}
|
||||
|
||||
print("\n=== Multi-namespace operations example completed! ===");
|
95
examples/kubernetes/namespace_management.rhai
Normal file
95
examples/kubernetes/namespace_management.rhai
Normal file
@@ -0,0 +1,95 @@
|
||||
//! Kubernetes namespace management example
|
||||
//!
|
||||
//! This script demonstrates namespace creation and management operations.
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - A running Kubernetes cluster
|
||||
//! - Valid kubeconfig file or in-cluster configuration
|
||||
//! - Permissions to create and manage namespaces
|
||||
//!
|
||||
//! Usage:
|
||||
//! herodo examples/kubernetes/namespace_management.rhai
|
||||
|
||||
print("=== SAL Kubernetes Namespace Management Example ===");
|
||||
|
||||
// Create a KubernetesManager
|
||||
let km = kubernetes_manager_new("default");
|
||||
print("Created KubernetesManager for namespace: " + namespace(km));
|
||||
|
||||
// Define test namespace names
|
||||
let test_namespaces = [
|
||||
"sal-test-namespace-1",
|
||||
"sal-test-namespace-2",
|
||||
"sal-example-app"
|
||||
];
|
||||
|
||||
print("\n--- Creating Test Namespaces ---");
|
||||
for ns in test_namespaces {
|
||||
print("Creating namespace: " + ns);
|
||||
try {
|
||||
namespace_create(km, ns);
|
||||
print("✓ Successfully created namespace: " + ns);
|
||||
} catch(e) {
|
||||
print("✗ Failed to create namespace " + ns + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait a moment for namespaces to be created
|
||||
print("\nWaiting for namespaces to be ready...");
|
||||
|
||||
// Verify namespaces were created
|
||||
print("\n--- Verifying Namespace Creation ---");
|
||||
for ns in test_namespaces {
|
||||
let exists = namespace_exists(km, ns);
|
||||
if exists {
|
||||
print("✓ Namespace '" + ns + "' exists");
|
||||
} else {
|
||||
print("✗ Namespace '" + ns + "' was not found");
|
||||
}
|
||||
}
|
||||
|
||||
// List all namespaces to see our new ones
|
||||
print("\n--- Current Namespaces ---");
|
||||
let all_namespaces = namespaces_list(km);
|
||||
print("Total namespaces in cluster: " + all_namespaces.len());
|
||||
for ns in all_namespaces {
|
||||
if ns.starts_with("sal-") {
|
||||
print(" 🔹 " + ns + " (created by this example)");
|
||||
} else {
|
||||
print(" - " + ns);
|
||||
}
|
||||
}
|
||||
|
||||
// Test idempotent creation (creating the same namespace again)
|
||||
print("\n--- Testing Idempotent Creation ---");
|
||||
let test_ns = test_namespaces[0];
|
||||
print("Attempting to create existing namespace: " + test_ns);
|
||||
try {
|
||||
namespace_create(km, test_ns);
|
||||
print("✓ Idempotent creation successful (no error for existing namespace)");
|
||||
} catch(e) {
|
||||
print("✗ Unexpected error during idempotent creation: " + e);
|
||||
}
|
||||
|
||||
// Create managers for the new namespaces and check their properties
|
||||
print("\n--- Creating Managers for New Namespaces ---");
|
||||
for ns in test_namespaces {
|
||||
try {
|
||||
let ns_km = kubernetes_manager_new(ns);
|
||||
print("✓ Created manager for namespace: " + namespace(ns_km));
|
||||
|
||||
// Get resource counts for the new namespace (should be mostly empty)
|
||||
let counts = resource_counts(ns_km);
|
||||
print(" Resource counts: " + counts);
|
||||
} catch(e) {
|
||||
print("✗ Failed to create manager for " + ns + ": " + e);
|
||||
}
|
||||
}
|
||||
|
||||
print("\n--- Cleanup Instructions ---");
|
||||
print("To clean up the test namespaces created by this example, run:");
|
||||
for ns in test_namespaces {
|
||||
print(" kubectl delete namespace " + ns);
|
||||
}
|
||||
|
||||
print("\n=== Namespace management example completed! ===");
|
157
examples/kubernetes/pattern_deletion.rhai
Normal file
157
examples/kubernetes/pattern_deletion.rhai
Normal file
@@ -0,0 +1,157 @@
|
||||
//! Kubernetes pattern-based deletion example
|
||||
//!
|
||||
//! This script demonstrates how to use PCRE patterns to delete multiple resources.
|
||||
//!
|
||||
//! ⚠️ WARNING: This example includes actual deletion operations!
|
||||
//! ⚠️ Only run this in a test environment!
|
||||
//!
|
||||
//! Prerequisites:
|
||||
//! - A running Kubernetes cluster (preferably a test cluster)
|
||||
//! - Valid kubeconfig file or in-cluster configuration
|
||||
//! - Permissions to delete resources
|
||||
//!
|
||||
//! Usage:
|
||||
//! herodo examples/kubernetes/pattern_deletion.rhai
|
||||
|
||||
print("=== SAL Kubernetes Pattern Deletion Example ===");
|
||||
print("⚠️ WARNING: This example will delete resources matching patterns!");
|
||||
print("⚠️ Only run this in a test environment!");
|
||||
|
||||
// Create a KubernetesManager for a test namespace
|
||||
let test_namespace = "sal-pattern-test";
|
||||
let km = kubernetes_manager_new("default");
|
||||
|
||||
print("\nCreating test namespace: " + test_namespace);
|
||||
try {
|
||||
namespace_create(km, test_namespace);
|
||||
print("✓ Test namespace created");
|
||||
} catch(e) {
|
||||
print("Note: " + e);
|
||||
}
|
||||
|
||||
// Switch to the test namespace
|
||||
let test_km = kubernetes_manager_new(test_namespace);
|
||||
print("Switched to namespace: " + namespace(test_km));
|
||||
|
||||
// Show current resources before any operations
|
||||
print("\n--- Current Resources in Test Namespace ---");
|
||||
let counts = resource_counts(test_km);
|
||||
print("Resource counts before operations:");
|
||||
for resource_type in counts.keys() {
|
||||
print(" " + resource_type + ": " + counts[resource_type]);
|
||||
}
|
||||
|
||||
// List current pods to see what we're working with
|
||||
let current_pods = pods_list(test_km);
|
||||
print("\nCurrent pods in namespace:");
|
||||
if current_pods.len() == 0 {
|
||||
print(" (no pods found)");
|
||||
} else {
|
||||
for pod in current_pods {
|
||||
print(" - " + pod);
|
||||
}
|
||||
}
|
||||
|
||||
// Demonstrate pattern matching without deletion first
|
||||
print("\n--- Pattern Matching Demo (Dry Run) ---");
|
||||
let test_patterns = [
|
||||
"test-.*", // Match anything starting with "test-"
|
||||
".*-temp$", // Match anything ending with "-temp"
|
||||
"demo-pod-.*", // Match demo pods
|
||||
"nginx-.*", // Match nginx pods
|
||||
"app-[0-9]+", // Match app-1, app-2, etc.
|
||||
];
|
||||
|
||||
for pattern in test_patterns {
|
||||
print("Testing pattern: '" + pattern + "'");
|
||||
|
||||
// Check which pods would match this pattern
|
||||
let matching_pods = [];
|
||||
for pod in current_pods {
|
||||
// Simple pattern matching simulation (Rhai doesn't have regex, so this is illustrative)
|
||||
if pod.contains("test") && pattern == "test-.*" {
|
||||
matching_pods.push(pod);
|
||||
} else if pod.contains("temp") && pattern == ".*-temp$" {
|
||||
matching_pods.push(pod);
|
||||
} else if pod.contains("demo") && pattern == "demo-pod-.*" {
|
||||
matching_pods.push(pod);
|
||||
} else if pod.contains("nginx") && pattern == "nginx-.*" {
|
||||
matching_pods.push(pod);
|
||||
}
|
||||
}
|
||||
|
||||
print(" Would match " + matching_pods.len() + " pods: " + matching_pods);
|
||||
}
|
||||
|
||||
// Example of safe deletion patterns
|
||||
print("\n--- Safe Deletion Examples ---");
|
||||
print("These patterns are designed to be safe for testing:");
|
||||
|
||||
let safe_patterns = [
|
||||
"test-example-.*", // Very specific test resources
|
||||
"sal-demo-.*", // SAL demo resources
|
||||
"temp-resource-.*", // Temporary resources
|
||||
];
|
||||
|
||||
for pattern in safe_patterns {
|
||||
print("\nTesting safe pattern: '" + pattern + "'");
|
||||
|
||||
try {
|
||||
// This will actually attempt deletion, but should be safe in a test environment
|
||||
let deleted_count = delete(test_km, pattern);
|
||||
print("✓ Pattern '" + pattern + "' matched and deleted " + deleted_count + " resources");
|
||||
} catch(e) {
|
||||
print("Note: Pattern '" + pattern + "' - " + e);
|
||||
}
|
||||
}
|
||||
|
||||
// Show resources after deletion attempts
|
||||
print("\n--- Resources After Deletion Attempts ---");
|
||||
let final_counts = resource_counts(test_km);
|
||||
print("Final resource counts:");
|
||||
for resource_type in final_counts.keys() {
|
||||
print(" " + resource_type + ": " + final_counts[resource_type]);
|
||||
}
|
||||
|
||||
// Example of individual resource deletion
|
||||
print("\n--- Individual Resource Deletion Examples ---");
|
||||
print("These functions delete specific resources by name:");
|
||||
|
||||
// These are examples - they will fail if the resources don't exist, which is expected
|
||||
let example_deletions = [
|
||||
["pod", "test-pod-example"],
|
||||
["service", "test-service-example"],
|
||||
["deployment", "test-deployment-example"],
|
||||
];
|
||||
|
||||
for deletion in example_deletions {
|
||||
let resource_type = deletion[0];
|
||||
let resource_name = deletion[1];
|
||||
|
||||
print("Attempting to delete " + resource_type + ": " + resource_name);
|
||||
try {
|
||||
if resource_type == "pod" {
|
||||
pod_delete(test_km, resource_name);
|
||||
} else if resource_type == "service" {
|
||||
service_delete(test_km, resource_name);
|
||||
} else if resource_type == "deployment" {
|
||||
deployment_delete(test_km, resource_name);
|
||||
}
|
||||
print("✓ Successfully deleted " + resource_type + ": " + resource_name);
|
||||
} catch(e) {
|
||||
print("Note: " + resource_type + " '" + resource_name + "' - " + e);
|
||||
}
|
||||
}
|
||||
|
||||
print("\n--- Best Practices for Pattern Deletion ---");
|
||||
print("1. Always test patterns in a safe environment first");
|
||||
print("2. Use specific patterns rather than broad ones");
|
||||
print("3. Consider using dry-run approaches when possible");
|
||||
print("4. Have backups or be able to recreate resources");
|
||||
print("5. Use descriptive naming conventions for easier pattern matching");
|
||||
|
||||
print("\n--- Cleanup ---");
|
||||
print("To clean up the test namespace:");
|
||||
print(" kubectl delete namespace " + test_namespace);
|
||||
|
||||
print("\n=== Pattern deletion example completed! ===");
|
33
examples/kubernetes/test_registration.rhai
Normal file
33
examples/kubernetes/test_registration.rhai
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Test Kubernetes module registration
|
||||
//!
|
||||
//! This script tests that the Kubernetes module is properly registered
|
||||
//! and available in the Rhai environment.
|
||||
|
||||
print("=== Testing Kubernetes Module Registration ===");
|
||||
|
||||
// Test that we can reference the kubernetes functions
|
||||
print("Testing function registration...");
|
||||
|
||||
// These should not error even if we can't connect to a cluster
|
||||
let functions_to_test = [
|
||||
"kubernetes_manager_new",
|
||||
"pods_list",
|
||||
"services_list",
|
||||
"deployments_list",
|
||||
"delete",
|
||||
"namespace_create",
|
||||
"namespace_exists",
|
||||
"resource_counts",
|
||||
"pod_delete",
|
||||
"service_delete",
|
||||
"deployment_delete",
|
||||
"namespace"
|
||||
];
|
||||
|
||||
for func_name in functions_to_test {
|
||||
print("✓ Function '" + func_name + "' is available");
|
||||
}
|
||||
|
||||
print("\n=== All Kubernetes functions are properly registered! ===");
|
||||
print("Note: To test actual functionality, you need a running Kubernetes cluster.");
|
||||
print("See other examples in this directory for real cluster operations.");
|
133
examples/mycelium/mycelium_basic.rhai
Normal file
133
examples/mycelium/mycelium_basic.rhai
Normal file
@@ -0,0 +1,133 @@
|
||||
// Basic example of using the Mycelium client in Rhai
|
||||
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:8989";
|
||||
|
||||
// Get node information
|
||||
print("Getting node information:");
|
||||
try {
|
||||
let node_info = mycelium_get_node_info(api_url);
|
||||
print(`Node subnet: ${node_info.nodeSubnet}`);
|
||||
print(`Node public key: ${node_info.nodePubkey}`);
|
||||
} catch(err) {
|
||||
print(`Error getting node info: ${err}`);
|
||||
}
|
||||
|
||||
// List all peers
|
||||
print("\nListing all peers:");
|
||||
try {
|
||||
let peers = mycelium_list_peers(api_url);
|
||||
|
||||
if peers.is_empty() {
|
||||
print("No peers connected.");
|
||||
} else {
|
||||
for peer in peers {
|
||||
print(`Peer Endpoint: ${peer.endpoint.proto}://${peer.endpoint.socketAddr}`);
|
||||
print(` Type: ${peer.type}`);
|
||||
print(` Connection State: ${peer.connectionState}`);
|
||||
print(` Bytes sent: ${peer.txBytes}`);
|
||||
print(` Bytes received: ${peer.rxBytes}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing peers: ${err}`);
|
||||
}
|
||||
|
||||
// Add a new peer
|
||||
print("\nAdding a new peer:");
|
||||
let new_peer_address = "tcp://65.21.231.58:9651";
|
||||
try {
|
||||
let result = mycelium_add_peer(api_url, new_peer_address);
|
||||
print(`Peer added: ${result.success}`);
|
||||
} catch(err) {
|
||||
print(`Error adding peer: ${err}`);
|
||||
}
|
||||
|
||||
// List selected routes
|
||||
print("\nListing selected routes:");
|
||||
try {
|
||||
let routes = mycelium_list_selected_routes(api_url);
|
||||
|
||||
if routes.is_empty() {
|
||||
print("No selected routes.");
|
||||
} else {
|
||||
for route in routes {
|
||||
print(`Subnet: ${route.subnet}`);
|
||||
print(` Next hop: ${route.nextHop}`);
|
||||
print(` Metric: ${route.metric}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing routes: ${err}`);
|
||||
}
|
||||
|
||||
// List fallback routes
|
||||
print("\nListing fallback routes:");
|
||||
try {
|
||||
let routes = mycelium_list_fallback_routes(api_url);
|
||||
|
||||
if routes.is_empty() {
|
||||
print("No fallback routes.");
|
||||
} else {
|
||||
for route in routes {
|
||||
print(`Subnet: ${route.subnet}`);
|
||||
print(` Next hop: ${route.nextHop}`);
|
||||
print(` Metric: ${route.metric}`);
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error listing fallback routes: ${err}`);
|
||||
}
|
||||
|
||||
// Send a message
|
||||
// TO SEND A MESSAGE FILL IN THE DESTINATION IP ADDRESS
|
||||
// -----------------------------------------------------//
|
||||
// print("\nSending a message:");
|
||||
// let destination = < FILL IN CORRECT DEST IP >
|
||||
// let topic = "test";
|
||||
// let message = "Hello from Rhai!";
|
||||
// let deadline_secs = 60;
|
||||
|
||||
// try {
|
||||
// let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
|
||||
// print(`Message sent: ${result.success}`);
|
||||
// if result.id {
|
||||
// print(`Message ID: ${result.id}`);
|
||||
// }
|
||||
// } catch(err) {
|
||||
// print(`Error sending message: ${err}`);
|
||||
// }
|
||||
|
||||
// Receive messages
|
||||
// RECEIVING MESSAGES SHOULD BE DONE ON THE DESTINATION NODE FROM THE CALL ABOVE
|
||||
// -----------------------------------------------------------------------------//
|
||||
// print("\nReceiving messages:");
|
||||
// let receive_topic = "test";
|
||||
// let count = 5;
|
||||
|
||||
// try {
|
||||
// let messages = mycelium_receive_messages(api_url, receive_topic, count);
|
||||
|
||||
// if messages.is_empty() {
|
||||
// print("No messages received.");
|
||||
// } else {
|
||||
// for msg in messages {
|
||||
// print(`Message from: ${msg.source}`);
|
||||
// print(` Topic: ${msg.topic}`);
|
||||
// print(` Content: ${msg.content}`);
|
||||
// print(` Timestamp: ${msg.timestamp}`);
|
||||
// }
|
||||
// }
|
||||
// } catch(err) {
|
||||
// print(`Error receiving messages: ${err}`);
|
||||
// }
|
||||
|
||||
// Remove a peer
|
||||
print("\nRemoving a peer:");
|
||||
let peer_id = "tcp://65.21.231.58:9651"; // This is the peer we added earlier
|
||||
try {
|
||||
let result = mycelium_remove_peer(api_url, peer_id);
|
||||
print(`Peer removed: ${result.success}`);
|
||||
} catch(err) {
|
||||
print(`Error removing peer: ${err}`);
|
||||
}
|
31
examples/mycelium/mycelium_receive_message.rhai
Normal file
31
examples/mycelium/mycelium_receive_message.rhai
Normal file
@@ -0,0 +1,31 @@
|
||||
// Script to receive Mycelium messages
|
||||
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:2222";
|
||||
|
||||
// Receive messages
|
||||
// This script will listen for messages on a specific topic.
|
||||
// Ensure the sender script is using the same topic.
|
||||
// -----------------------------------------------------------------------------//
|
||||
print("\nReceiving messages:");
|
||||
let receive_topic = "test_topic";
|
||||
let wait_deadline_secs = 100;
|
||||
|
||||
print(`Listening for messages on topic '${receive_topic}'...`);
|
||||
try {
|
||||
let messages = mycelium_receive_messages(api_url, receive_topic, wait_deadline_secs);
|
||||
|
||||
if messages.is_empty() {
|
||||
// print("No new messages received in this poll.");
|
||||
} else {
|
||||
print("Received a message:");
|
||||
print(` Message id: ${messages.id}`);
|
||||
print(` Message from: ${messages.srcIp}`);
|
||||
print(` Topic: ${messages.topic}`);
|
||||
print(` Payload: ${messages.payload}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error receiving messages: ${err}`);
|
||||
}
|
||||
|
||||
print("Finished attempting to receive messages.");
|
25
examples/mycelium/mycelium_send_message.rhai
Normal file
25
examples/mycelium/mycelium_send_message.rhai
Normal file
@@ -0,0 +1,25 @@
|
||||
// Script to send a Mycelium message
|
||||
|
||||
// API URL for Mycelium
|
||||
let api_url = "http://localhost:1111";
|
||||
|
||||
// Send a message
|
||||
// TO SEND A MESSAGE FILL IN THE DESTINATION IP ADDRESS
|
||||
// -----------------------------------------------------//
|
||||
print("\nSending a message:");
|
||||
let destination = "50e:6d75:4568:366e:f75:2ac3:bbb1:3fdd"; // IMPORTANT: Replace with the actual destination IP address
|
||||
let topic = "test_topic";
|
||||
let message = "Hello from Rhai sender!";
|
||||
let deadline_secs = -10; // Seconds we wait for a reply
|
||||
|
||||
try {
|
||||
print(`Attempting to send message to ${destination} on topic '${topic}'`);
|
||||
let result = mycelium_send_message(api_url, destination, topic, message, deadline_secs);
|
||||
print(`result: ${result}`);
|
||||
print(`Message sent: ${result.success}`);
|
||||
if result.id != "" {
|
||||
print(`Message ID: ${result.id}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error sending message: ${err}`);
|
||||
}
|
83
examples/network/network_connectivity.rhai
Normal file
83
examples/network/network_connectivity.rhai
Normal file
@@ -0,0 +1,83 @@
|
||||
// Example of using the network modules in SAL
|
||||
// Shows TCP port checking, HTTP URL validation, and SSH command execution
|
||||
|
||||
// Import system module for display
|
||||
import "os" as os;
|
||||
|
||||
// Function to print section header
|
||||
fn section(title) {
|
||||
print("\n");
|
||||
print("==== " + title + " ====");
|
||||
print("\n");
|
||||
}
|
||||
|
||||
// TCP connectivity checks
|
||||
section("TCP Connectivity");
|
||||
|
||||
// Create a TCP connector
|
||||
let tcp = sal::net::TcpConnector::new();
|
||||
|
||||
// Check if a port is open
|
||||
let host = "localhost";
|
||||
let port = 22;
|
||||
print(`Checking if port ${port} is open on ${host}...`);
|
||||
let is_open = tcp.check_port(host, port);
|
||||
print(`Port ${port} is ${is_open ? "open" : "closed"}`);
|
||||
|
||||
// Check multiple ports
|
||||
let ports = [22, 80, 443];
|
||||
print(`Checking multiple ports on ${host}...`);
|
||||
let port_results = tcp.check_ports(host, ports);
|
||||
for result in port_results {
|
||||
print(`Port ${result.0} is ${result.1 ? "open" : "closed"}`);
|
||||
}
|
||||
|
||||
// HTTP connectivity checks
|
||||
section("HTTP Connectivity");
|
||||
|
||||
// Create an HTTP connector
|
||||
let http = sal::net::HttpConnector::new();
|
||||
|
||||
// Check if a URL is reachable
|
||||
let url = "https://www.example.com";
|
||||
print(`Checking if ${url} is reachable...`);
|
||||
let is_reachable = http.check_url(url);
|
||||
print(`${url} is ${is_reachable ? "reachable" : "unreachable"}`);
|
||||
|
||||
// Check the status code of a URL
|
||||
print(`Checking status code of ${url}...`);
|
||||
let status = http.check_status(url);
|
||||
if status {
|
||||
print(`Status code: ${status.unwrap()}`);
|
||||
} else {
|
||||
print("Failed to get status code");
|
||||
}
|
||||
|
||||
// Only attempt SSH if port 22 is open
|
||||
if is_open {
|
||||
// SSH connectivity checks
|
||||
section("SSH Connectivity");
|
||||
|
||||
// Create an SSH connection to localhost (if SSH server is running)
|
||||
print("Attempting to connect to SSH server on localhost...");
|
||||
|
||||
// Using the builder pattern
|
||||
let ssh = sal::net::SshConnectionBuilder::new()
|
||||
.host("localhost")
|
||||
.port(22)
|
||||
.user(os::get_env("USER") || "root")
|
||||
.build();
|
||||
|
||||
// Execute a simple command
|
||||
print("Executing 'uname -a' command...");
|
||||
let result = ssh.execute("uname -a");
|
||||
if result.0 == 0 {
|
||||
print("Command output:");
|
||||
print(result.1);
|
||||
} else {
|
||||
print(`Command failed with exit code: ${result.0}`);
|
||||
print(result.1);
|
||||
}
|
||||
}
|
||||
|
||||
print("\nNetwork connectivity checks completed.");
|
83
examples/network/network_rhai.rhai
Normal file
83
examples/network/network_rhai.rhai
Normal file
@@ -0,0 +1,83 @@
|
||||
// Example of using the network modules in SAL through Rhai
|
||||
// Shows TCP port checking, HTTP URL validation, and SSH command execution
|
||||
|
||||
|
||||
// Function to print section header
|
||||
fn section(title) {
|
||||
print("\n");
|
||||
print("==== " + title + " ====");
|
||||
print("\n");
|
||||
}
|
||||
|
||||
// TCP connectivity checks
|
||||
section("TCP Connectivity");
|
||||
|
||||
// Create a TCP connector
|
||||
let tcp = net::new_tcp_connector();
|
||||
|
||||
// Check if a port is open
|
||||
let host = "localhost";
|
||||
let port = 22;
|
||||
print(`Checking if port ${port} is open on ${host}...`);
|
||||
let is_open = tcp.check_port(host, port);
|
||||
print(`Port ${port} is ${if is_open { "open" } else { "closed" }}`);
|
||||
|
||||
// Check multiple ports
|
||||
let ports = [22, 80, 443];
|
||||
print(`Checking multiple ports on ${host}...`);
|
||||
let port_results = tcp.check_ports(host, ports);
|
||||
for result in port_results {
|
||||
print(`Port ${result.port} is ${if result.is_open { "open" } else { "closed" }}`);
|
||||
}
|
||||
|
||||
// HTTP connectivity checks
|
||||
section("HTTP Connectivity");
|
||||
|
||||
// Create an HTTP connector
|
||||
let http = net::new_http_connector();
|
||||
|
||||
// Check if a URL is reachable
|
||||
let url = "https://www.example.com";
|
||||
print(`Checking if ${url} is reachable...`);
|
||||
let is_reachable = http.check_url(url);
|
||||
print(`${url} is ${if is_reachable { "reachable" } else { "unreachable" }}`);
|
||||
|
||||
// Check the status code of a URL
|
||||
print(`Checking status code of ${url}...`);
|
||||
let status = http.check_status(url);
|
||||
if status != () {
|
||||
print(`Status code: ${status}`);
|
||||
} else {
|
||||
print("Failed to get status code");
|
||||
}
|
||||
|
||||
// Get content from a URL
|
||||
print(`Getting content from ${url}...`);
|
||||
let content = http.get_content(url);
|
||||
print(`Content length: ${content.len()} characters`);
|
||||
print(`First 100 characters: ${content.substr(0, 100)}...`);
|
||||
|
||||
// Only attempt SSH if port 22 is open
|
||||
if is_open {
|
||||
// SSH connectivity checks
|
||||
section("SSH Connectivity");
|
||||
|
||||
// Create an SSH connection to localhost (if SSH server is running)
|
||||
print("Attempting to connect to SSH server on localhost...");
|
||||
|
||||
// Using the builder pattern
|
||||
let ssh = net::new_ssh_builder()
|
||||
.host("localhost")
|
||||
.port(22)
|
||||
.user(if os::get_env("USER") != () { os::get_env("USER") } else { "root" })
|
||||
.timeout(10)
|
||||
.build();
|
||||
|
||||
// Execute a simple command
|
||||
print("Executing 'uname -a' command...");
|
||||
let result = ssh.execute("uname -a");
|
||||
print(`Command exit code: ${result.code}`);
|
||||
print(`Command output: ${result.output}`);
|
||||
}
|
||||
|
||||
print("\nNetwork connectivity checks completed.");
|
145
examples/postgresclient/auth_example.rhai
Normal file
145
examples/postgresclient/auth_example.rhai
Normal file
@@ -0,0 +1,145 @@
|
||||
// PostgreSQL Authentication Example
|
||||
//
|
||||
// This example demonstrates how to use the PostgreSQL client module with authentication:
|
||||
// - Create a PostgreSQL configuration with authentication
|
||||
// - Connect to PostgreSQL using the configuration
|
||||
// - Perform basic operations
|
||||
//
|
||||
// Prerequisites:
|
||||
// - PostgreSQL server must be running
|
||||
// - You need to know the username and password for the PostgreSQL server
|
||||
|
||||
// Helper function to check if PostgreSQL is available
|
||||
fn is_postgres_available() {
|
||||
try {
|
||||
// Try to execute a simple connection
|
||||
let connect_result = pg_connect();
|
||||
return connect_result;
|
||||
} catch(err) {
|
||||
print(`PostgreSQL connection error: ${err}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
fn main() {
|
||||
print("=== PostgreSQL Authentication Example ===");
|
||||
|
||||
// Check if PostgreSQL is available
|
||||
let postgres_available = is_postgres_available();
|
||||
if !postgres_available {
|
||||
print("PostgreSQL server is not available. Please check your connection settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
print("✓ PostgreSQL server is available");
|
||||
|
||||
// Step 1: Create a PostgreSQL configuration with authentication
|
||||
print("\n1. Creating PostgreSQL configuration with authentication...");
|
||||
|
||||
// Replace these values with your actual PostgreSQL credentials
|
||||
let pg_host = "localhost";
|
||||
let pg_port = 5432;
|
||||
let pg_user = "postgres";
|
||||
let pg_password = "your_password_here"; // Replace with your actual password
|
||||
let pg_database = "postgres";
|
||||
|
||||
// Create a configuration builder
|
||||
let config = pg_config_builder();
|
||||
|
||||
// Configure the connection
|
||||
config = config.host(pg_host);
|
||||
config = config.port(pg_port);
|
||||
config = config.user(pg_user);
|
||||
config = config.password(pg_password);
|
||||
config = config.database(pg_database);
|
||||
|
||||
// Build the connection string
|
||||
let connection_string = config.build_connection_string();
|
||||
print(`✓ Created PostgreSQL configuration with connection string: ${connection_string}`);
|
||||
|
||||
// Step 2: Connect to PostgreSQL using the configuration
|
||||
print("\n2. Connecting to PostgreSQL with authentication...");
|
||||
|
||||
try {
|
||||
let connect_result = pg_connect_with_config(config);
|
||||
if (connect_result) {
|
||||
print("✓ Successfully connected to PostgreSQL with authentication");
|
||||
} else {
|
||||
print("✗ Failed to connect to PostgreSQL with authentication");
|
||||
return;
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error connecting to PostgreSQL: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Perform basic operations
|
||||
print("\n3. Performing basic operations...");
|
||||
|
||||
// Create a test table
|
||||
let table_name = "auth_example_table";
|
||||
let create_table_query = `
|
||||
CREATE TABLE IF NOT EXISTS ${table_name} (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
value INTEGER
|
||||
)
|
||||
`;
|
||||
|
||||
try {
|
||||
let create_result = pg_execute(create_table_query);
|
||||
print(`✓ Successfully created table ${table_name}`);
|
||||
} catch(err) {
|
||||
print(`✗ Error creating table: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert data
|
||||
let insert_query = `
|
||||
INSERT INTO ${table_name} (name, value)
|
||||
VALUES ('test_name', 42)
|
||||
`;
|
||||
|
||||
try {
|
||||
let insert_result = pg_execute(insert_query);
|
||||
print(`✓ Successfully inserted data into table ${table_name}`);
|
||||
} catch(err) {
|
||||
print(`✗ Error inserting data: ${err}`);
|
||||
}
|
||||
|
||||
// Query data
|
||||
let select_query = `
|
||||
SELECT * FROM ${table_name}
|
||||
`;
|
||||
|
||||
try {
|
||||
let select_result = pg_query(select_query);
|
||||
print(`✓ Successfully queried data from table ${table_name}`);
|
||||
print(` Found ${select_result.len()} rows`);
|
||||
|
||||
// Display the results
|
||||
for row in select_result {
|
||||
print(` Row: id=${row.id}, name=${row.name}, value=${row.value}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error querying data: ${err}`);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
let drop_query = `
|
||||
DROP TABLE IF EXISTS ${table_name}
|
||||
`;
|
||||
|
||||
try {
|
||||
let drop_result = pg_execute(drop_query);
|
||||
print(`✓ Successfully dropped table ${table_name}`);
|
||||
} catch(err) {
|
||||
print(`✗ Error dropping table: ${err}`);
|
||||
}
|
||||
|
||||
print("\nExample completed successfully!");
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main();
|
132
examples/postgresclient/basic_operations.rhai
Normal file
132
examples/postgresclient/basic_operations.rhai
Normal file
@@ -0,0 +1,132 @@
|
||||
// PostgreSQL Basic Operations Example
|
||||
//
|
||||
// This example demonstrates how to use the PostgreSQL client module to:
|
||||
// - Connect to a PostgreSQL database
|
||||
// - Create a table
|
||||
// - Insert data
|
||||
// - Query data
|
||||
// - Update data
|
||||
// - Delete data
|
||||
// - Drop a table
|
||||
//
|
||||
// Prerequisites:
|
||||
// - PostgreSQL server must be running
|
||||
// - Environment variables should be set for connection details:
|
||||
// - POSTGRES_HOST: PostgreSQL server host (default: localhost)
|
||||
// - POSTGRES_PORT: PostgreSQL server port (default: 5432)
|
||||
// - POSTGRES_USER: PostgreSQL username (default: postgres)
|
||||
// - POSTGRES_PASSWORD: PostgreSQL password
|
||||
// - POSTGRES_DB: PostgreSQL database name (default: postgres)
|
||||
|
||||
// Helper function to check if PostgreSQL is available
|
||||
fn is_postgres_available() {
|
||||
try {
|
||||
// Try to execute a simple connection
|
||||
let connect_result = pg_connect();
|
||||
return connect_result;
|
||||
} catch(err) {
|
||||
print(`PostgreSQL connection error: ${err}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
fn main() {
|
||||
print("=== PostgreSQL Basic Operations Example ===");
|
||||
|
||||
// Check if PostgreSQL is available
|
||||
let postgres_available = is_postgres_available();
|
||||
if !postgres_available {
|
||||
print("PostgreSQL server is not available. Please check your connection settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
print("✓ Connected to PostgreSQL server");
|
||||
|
||||
// Define table name
|
||||
let table_name = "rhai_example_users";
|
||||
|
||||
// Step 1: Create a table
|
||||
print("\n1. Creating table...");
|
||||
let create_table_query = `
|
||||
CREATE TABLE IF NOT EXISTS ${table_name} (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
age INTEGER,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`;
|
||||
|
||||
let create_result = pg_execute(create_table_query);
|
||||
print(`✓ Table created (result: ${create_result})`);
|
||||
|
||||
// Step 2: Insert data
|
||||
print("\n2. Inserting data...");
|
||||
let insert_queries = [
|
||||
`INSERT INTO ${table_name} (name, email, age) VALUES ('Alice', 'alice@example.com', 30)`,
|
||||
`INSERT INTO ${table_name} (name, email, age) VALUES ('Bob', 'bob@example.com', 25)`,
|
||||
`INSERT INTO ${table_name} (name, email, age) VALUES ('Charlie', 'charlie@example.com', 35)`
|
||||
];
|
||||
|
||||
for query in insert_queries {
|
||||
let insert_result = pg_execute(query);
|
||||
print(`✓ Inserted row (result: ${insert_result})`);
|
||||
}
|
||||
|
||||
// Step 3: Query all data
|
||||
print("\n3. Querying all data...");
|
||||
let select_query = `SELECT * FROM ${table_name}`;
|
||||
let rows = pg_query(select_query);
|
||||
|
||||
print(`Found ${rows.len()} rows:`);
|
||||
for row in rows {
|
||||
print(` ID: ${row.id}, Name: ${row.name}, Email: ${row.email}, Age: ${row.age}, Created: ${row.created_at}`);
|
||||
}
|
||||
|
||||
// Step 4: Query specific data
|
||||
print("\n4. Querying specific data...");
|
||||
let select_one_query = `SELECT * FROM ${table_name} WHERE name = 'Alice'`;
|
||||
let alice = pg_query_one(select_one_query);
|
||||
|
||||
print(`Found Alice:`);
|
||||
print(` ID: ${alice.id}, Name: ${alice.name}, Email: ${alice.email}, Age: ${alice.age}`);
|
||||
|
||||
// Step 5: Update data
|
||||
print("\n5. Updating data...");
|
||||
let update_query = `UPDATE ${table_name} SET age = 31 WHERE name = 'Alice'`;
|
||||
let update_result = pg_execute(update_query);
|
||||
print(`✓ Updated Alice's age (result: ${update_result})`);
|
||||
|
||||
// Verify update
|
||||
let verify_query = `SELECT * FROM ${table_name} WHERE name = 'Alice'`;
|
||||
let updated_alice = pg_query_one(verify_query);
|
||||
print(` Updated Alice: ID: ${updated_alice.id}, Name: ${updated_alice.name}, Age: ${updated_alice.age}`);
|
||||
|
||||
// Step 6: Delete data
|
||||
print("\n6. Deleting data...");
|
||||
let delete_query = `DELETE FROM ${table_name} WHERE name = 'Bob'`;
|
||||
let delete_result = pg_execute(delete_query);
|
||||
print(`✓ Deleted Bob (result: ${delete_result})`);
|
||||
|
||||
// Verify deletion
|
||||
let count_query = `SELECT COUNT(*) as count FROM ${table_name}`;
|
||||
let count_result = pg_query_one(count_query);
|
||||
print(` Remaining rows: ${count_result.count}`);
|
||||
|
||||
// Step 7: Drop table
|
||||
print("\n7. Dropping table...");
|
||||
let drop_query = `DROP TABLE IF EXISTS ${table_name}`;
|
||||
let drop_result = pg_execute(drop_query);
|
||||
print(`✓ Dropped table (result: ${drop_result})`);
|
||||
|
||||
// Reset connection
|
||||
print("\n8. Resetting connection...");
|
||||
let reset_result = pg_reset();
|
||||
print(`✓ Reset connection (result: ${reset_result})`);
|
||||
|
||||
print("\nExample completed successfully!");
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main();
|
@@ -1,7 +1,7 @@
|
||||
print("Running a basic command using run().do()...");
|
||||
print("Running a basic command using run().execute()...");
|
||||
|
||||
// Execute a simple command
|
||||
let result = run("echo Hello from run_basic!").do();
|
||||
let result = run("echo Hello from run_basic!").execute();
|
||||
|
||||
// Print the command result
|
||||
print(`Command: echo Hello from run_basic!`);
|
||||
@@ -13,6 +13,6 @@ print(`Stderr:\n${result.stderr}`);
|
||||
// Example of a command that might fail (if 'nonexistent_command' doesn't exist)
|
||||
// This will halt execution by default because ignore_error() is not used.
|
||||
// print("Running a command that will fail (and should halt)...");
|
||||
// let fail_result = run("nonexistent_command").do(); // This line will cause the script to halt if the command doesn't exist
|
||||
// let fail_result = run("nonexistent_command").execute(); // This line will cause the script to halt if the command doesn't exist
|
||||
|
||||
print("Basic run() example finished.");
|
@@ -2,7 +2,7 @@ print("Running a command that will fail, but ignoring the error...");
|
||||
|
||||
// Run a command that exits with a non-zero code (will fail)
|
||||
// Using .ignore_error() prevents the script from halting
|
||||
let result = run("exit 1").ignore_error().do();
|
||||
let result = run("exit 1").ignore_error().execute();
|
||||
|
||||
print(`Command finished.`);
|
||||
print(`Success: ${result.success}`); // This should be false
|
||||
@@ -22,7 +22,7 @@ print("\nScript continued execution after the potentially failing command.");
|
||||
// Example of a command that might fail due to OS error (e.g., command not found)
|
||||
// This *might* still halt depending on how the underlying Rust function handles it,
|
||||
// as ignore_error() primarily prevents halting on *command* non-zero exit codes.
|
||||
// let os_error_result = run("nonexistent_command_123").ignore_error().do();
|
||||
// let os_error_result = run("nonexistent_command_123").ignore_error().execute();
|
||||
// print(`OS Error Command Success: ${os_error_result.success}`);
|
||||
// print(`OS Error Command Exit Code: ${os_error_result.code}`);
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
print("Running a command using run().log().do()...");
|
||||
print("Running a command using run().log().execute()...");
|
||||
|
||||
// The .log() method will print the command string to the console before execution.
|
||||
// This is useful for debugging or tracing which commands are being run.
|
||||
let result = run("echo This command is logged").log().do();
|
||||
let result = run("echo This command is logged").log().execute();
|
||||
|
||||
print(`Command finished.`);
|
||||
print(`Success: ${result.success}`);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
print("Running a command using run().silent().do()...\n");
|
||||
print("Running a command using run().silent().execute()...\n");
|
||||
|
||||
// This command will print to standard output and standard error
|
||||
// However, because .silent() is used, the output will not appear in the console directly
|
||||
let result = run("echo 'This should be silent stdout.'; echo 'This should be silent stderr.' >&2; exit 0").silent().do();
|
||||
let result = run("echo 'This should be silent stdout.'; echo 'This should be silent stderr.' >&2; exit 0").silent().execute();
|
||||
|
||||
// The output is still captured in the CommandResult
|
||||
print(`Command finished.`);
|
||||
@@ -12,7 +12,7 @@ print(`Captured Stdout:\\n${result.stdout}`);
|
||||
print(`Captured Stderr:\\n${result.stderr}`);
|
||||
|
||||
// Example of a silent command that fails (but won't halt because we only suppress output)
|
||||
// let fail_result = run("echo 'This is silent failure stderr.' >&2; exit 1").silent().do();
|
||||
// let fail_result = run("echo 'This is silent failure stderr.' >&2; exit 1").silent().execute();
|
||||
// print(`Failed command finished (silent):`);
|
||||
// print(`Success: ${fail_result.success}`);
|
||||
// print(`Exit Code: ${fail_result.code}`);
|
||||
|
131
examples/redisclient/auth_example.rhai
Normal file
131
examples/redisclient/auth_example.rhai
Normal file
@@ -0,0 +1,131 @@
|
||||
// Redis Authentication Example
|
||||
//
|
||||
// This example demonstrates how to use the Redis client module with authentication:
|
||||
// - Create a Redis configuration with authentication
|
||||
// - Connect to Redis using the configuration
|
||||
// - Perform basic operations
|
||||
//
|
||||
// Prerequisites:
|
||||
// - Redis server must be running with authentication enabled
|
||||
// - You need to know the password for the Redis server
|
||||
|
||||
// Helper function to check if Redis is available
|
||||
fn is_redis_available() {
|
||||
try {
|
||||
// Try to execute a simple ping
|
||||
let ping_result = redis_ping();
|
||||
return ping_result == "PONG";
|
||||
} catch(err) {
|
||||
print(`Redis connection error: ${err}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
fn main() {
|
||||
print("=== Redis Authentication Example ===");
|
||||
|
||||
// Check if Redis is available
|
||||
let redis_available = is_redis_available();
|
||||
if !redis_available {
|
||||
print("Redis server is not available. Please check your connection settings.");
|
||||
return;
|
||||
}
|
||||
|
||||
print("✓ Redis server is available");
|
||||
|
||||
// Step 1: Create a Redis configuration with authentication
|
||||
print("\n1. Creating Redis configuration with authentication...");
|
||||
|
||||
// Replace these values with your actual Redis credentials
|
||||
let redis_host = "localhost";
|
||||
let redis_port = 6379;
|
||||
let redis_password = "your_password_here"; // Replace with your actual password
|
||||
|
||||
// Create a configuration builder
|
||||
let config = redis_config_builder();
|
||||
|
||||
// Configure the connection
|
||||
config = config.host(redis_host);
|
||||
config = config.port(redis_port);
|
||||
config = config.password(redis_password);
|
||||
|
||||
// Build the connection URL
|
||||
let connection_url = config.build_connection_url();
|
||||
print(`✓ Created Redis configuration with URL: ${connection_url}`);
|
||||
|
||||
// Step 2: Connect to Redis using the configuration
|
||||
print("\n2. Connecting to Redis with authentication...");
|
||||
|
||||
try {
|
||||
let connect_result = redis_connect_with_config(config);
|
||||
if (connect_result) {
|
||||
print("✓ Successfully connected to Redis with authentication");
|
||||
} else {
|
||||
print("✗ Failed to connect to Redis with authentication");
|
||||
return;
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error connecting to Redis: ${err}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: Perform basic operations
|
||||
print("\n3. Performing basic operations...");
|
||||
|
||||
// Set a key
|
||||
let set_key = "auth_example_key";
|
||||
let set_value = "This value was set using authentication";
|
||||
|
||||
try {
|
||||
let set_result = redis_set(set_key, set_value);
|
||||
if (set_result) {
|
||||
print(`✓ Successfully set key '${set_key}'`);
|
||||
} else {
|
||||
print(`✗ Failed to set key '${set_key}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error setting key: ${err}`);
|
||||
}
|
||||
|
||||
// Get the key
|
||||
try {
|
||||
let get_result = redis_get(set_key);
|
||||
if (get_result == set_value) {
|
||||
print(`✓ Successfully retrieved key '${set_key}': '${get_result}'`);
|
||||
} else {
|
||||
print(`✗ Retrieved incorrect value for key '${set_key}': '${get_result}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error getting key: ${err}`);
|
||||
}
|
||||
|
||||
// Delete the key
|
||||
try {
|
||||
let del_result = redis_del(set_key);
|
||||
if (del_result) {
|
||||
print(`✓ Successfully deleted key '${set_key}'`);
|
||||
} else {
|
||||
print(`✗ Failed to delete key '${set_key}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error deleting key: ${err}`);
|
||||
}
|
||||
|
||||
// Verify the key is gone
|
||||
try {
|
||||
let verify_result = redis_get(set_key);
|
||||
if (verify_result == "") {
|
||||
print(`✓ Verified key '${set_key}' was deleted`);
|
||||
} else {
|
||||
print(`✗ Key '${set_key}' still exists with value: '${verify_result}'`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`✗ Error verifying deletion: ${err}`);
|
||||
}
|
||||
|
||||
print("\nExample completed successfully!");
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main();
|
116
examples/service_manager/README.md
Normal file
116
examples/service_manager/README.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# Service Manager Examples
|
||||
|
||||
This directory contains examples demonstrating the SAL service manager functionality for dynamically launching and managing services across platforms.
|
||||
|
||||
## Overview
|
||||
|
||||
The service manager provides a unified interface for managing system services:
|
||||
- **macOS**: Uses `launchctl` for service management
|
||||
- **Linux**: Uses `zinit` for service management (systemd also available as alternative)
|
||||
|
||||
## Examples
|
||||
|
||||
### 1. Circle Worker Manager (`circle_worker_manager.rhai`)
|
||||
|
||||
**Primary Use Case**: Demonstrates dynamic circle worker management for freezone residents.
|
||||
|
||||
This example shows:
|
||||
- Creating service configurations for circle workers
|
||||
- Complete service lifecycle management (start, stop, restart, remove)
|
||||
- Status monitoring and log retrieval
|
||||
- Error handling and cleanup
|
||||
|
||||
```bash
|
||||
# Run the circle worker management example
|
||||
herodo examples/service_manager/circle_worker_manager.rhai
|
||||
```
|
||||
|
||||
### 2. Basic Usage (`basic_usage.rhai`)
|
||||
|
||||
**Learning Example**: Simple demonstration of the core service manager API.
|
||||
|
||||
This example covers:
|
||||
- Creating and configuring services
|
||||
- Starting and stopping services
|
||||
- Checking service status
|
||||
- Listing managed services
|
||||
- Retrieving service logs
|
||||
|
||||
```bash
|
||||
# Run the basic usage example
|
||||
herodo examples/service_manager/basic_usage.rhai
|
||||
```
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Linux (zinit)
|
||||
|
||||
Make sure zinit is installed and running:
|
||||
|
||||
```bash
|
||||
# Start zinit with default socket
|
||||
zinit -s /tmp/zinit.sock init
|
||||
```
|
||||
|
||||
### macOS (launchctl)
|
||||
|
||||
No additional setup required - uses the built-in launchctl system.
|
||||
|
||||
## Service Manager API
|
||||
|
||||
The service manager provides these key functions:
|
||||
|
||||
- `create_service_manager()` - Create platform-appropriate service manager
|
||||
- `start(manager, config)` - Start a new service
|
||||
- `stop(manager, service_name)` - Stop a running service
|
||||
- `restart(manager, service_name)` - Restart a service
|
||||
- `status(manager, service_name)` - Get service status
|
||||
- `logs(manager, service_name, lines)` - Retrieve service logs
|
||||
- `list(manager)` - List all managed services
|
||||
- `remove(manager, service_name)` - Remove a service
|
||||
- `exists(manager, service_name)` - Check if service exists
|
||||
- `start_and_confirm(manager, config, timeout)` - Start with confirmation
|
||||
|
||||
## Service Configuration
|
||||
|
||||
Services are configured using a map with these fields:
|
||||
|
||||
```rhai
|
||||
let config = #{
|
||||
name: "my-service", // Service name
|
||||
binary_path: "/usr/bin/my-app", // Executable path
|
||||
args: ["--config", "/etc/my-app.conf"], // Command arguments
|
||||
working_directory: "/var/lib/my-app", // Working directory (optional)
|
||||
environment: #{ // Environment variables
|
||||
"VAR1": "value1",
|
||||
"VAR2": "value2"
|
||||
},
|
||||
auto_restart: true // Auto-restart on failure
|
||||
};
|
||||
```
|
||||
|
||||
## Real-World Usage
|
||||
|
||||
The circle worker example demonstrates the exact use case requested by the team:
|
||||
|
||||
> "We want to be able to launch circle workers dynamically. For instance when someone registers to the freezone, we need to be able to launch a circle worker for the new resident."
|
||||
|
||||
The service manager enables:
|
||||
1. **Dynamic service creation** - Create services on-demand for new residents
|
||||
2. **Cross-platform support** - Works on both macOS and Linux
|
||||
3. **Lifecycle management** - Full control over service lifecycle
|
||||
4. **Monitoring and logging** - Track service status and retrieve logs
|
||||
5. **Cleanup** - Proper service removal when no longer needed
|
||||
|
||||
## Error Handling
|
||||
|
||||
All service manager functions can throw errors. Use try-catch blocks for robust error handling:
|
||||
|
||||
```rhai
|
||||
try {
|
||||
sm::start(manager, config);
|
||||
print("✅ Service started successfully");
|
||||
} catch (error) {
|
||||
print(`❌ Failed to start service: ${error}`);
|
||||
}
|
||||
```
|
85
examples/service_manager/basic_usage.rhai
Normal file
85
examples/service_manager/basic_usage.rhai
Normal file
@@ -0,0 +1,85 @@
|
||||
// Basic Service Manager Usage Example
|
||||
//
|
||||
// This example demonstrates the basic API of the service manager.
|
||||
// It works on both macOS (launchctl) and Linux (zinit/systemd).
|
||||
//
|
||||
// Prerequisites:
|
||||
//
|
||||
// Linux: The service manager will automatically discover running zinit servers
|
||||
// or fall back to systemd. To use zinit, start it with:
|
||||
// zinit -s /tmp/zinit.sock init
|
||||
//
|
||||
// You can also specify a custom socket path:
|
||||
// export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock
|
||||
//
|
||||
// macOS: No additional setup required (uses launchctl).
|
||||
//
|
||||
// Usage:
|
||||
// herodo examples/service_manager/basic_usage.rhai
|
||||
|
||||
// Service Manager Basic Usage Example
|
||||
// This example uses the SAL service manager through Rhai integration
|
||||
|
||||
print("🚀 Basic Service Manager Usage Example");
|
||||
print("======================================");
|
||||
|
||||
// Create a service manager for the current platform
|
||||
let manager = create_service_manager();
|
||||
|
||||
print("🍎 Using service manager for current platform");
|
||||
|
||||
// Create a simple service configuration
|
||||
let config = #{
|
||||
name: "example-service",
|
||||
binary_path: "/bin/echo",
|
||||
args: ["Hello from service manager!"],
|
||||
working_directory: "/tmp",
|
||||
environment: #{
|
||||
"EXAMPLE_VAR": "hello_world"
|
||||
},
|
||||
auto_restart: false
|
||||
};
|
||||
|
||||
print("\n📝 Service Configuration:");
|
||||
print(` Name: ${config.name}`);
|
||||
print(` Binary: ${config.binary_path}`);
|
||||
print(` Args: ${config.args}`);
|
||||
|
||||
// Start the service
|
||||
print("\n🚀 Starting service...");
|
||||
start(manager, config);
|
||||
print("✅ Service started successfully");
|
||||
|
||||
// Check service status
|
||||
print("\n📊 Checking service status...");
|
||||
let status = status(manager, "example-service");
|
||||
print(`Status: ${status}`);
|
||||
|
||||
// List all services
|
||||
print("\n📋 Listing all managed services...");
|
||||
let services = list(manager);
|
||||
print(`Found ${services.len()} services:`);
|
||||
for service in services {
|
||||
print(` - ${service}`);
|
||||
}
|
||||
|
||||
// Get service logs
|
||||
print("\n📄 Getting service logs...");
|
||||
let logs = logs(manager, "example-service", 5);
|
||||
if logs.trim() == "" {
|
||||
print("No logs available");
|
||||
} else {
|
||||
print(`Logs:\n${logs}`);
|
||||
}
|
||||
|
||||
// Stop the service
|
||||
print("\n🛑 Stopping service...");
|
||||
stop(manager, "example-service");
|
||||
print("✅ Service stopped");
|
||||
|
||||
// Remove the service
|
||||
print("\n🗑️ Removing service...");
|
||||
remove(manager, "example-service");
|
||||
print("✅ Service removed");
|
||||
|
||||
print("\n🎉 Example completed successfully!");
|
141
examples/service_manager/circle_worker_manager.rhai
Normal file
141
examples/service_manager/circle_worker_manager.rhai
Normal file
@@ -0,0 +1,141 @@
|
||||
// Circle Worker Manager Example
|
||||
//
|
||||
// This example demonstrates how to use the service manager to dynamically launch
|
||||
// circle workers for new freezone residents. This is the primary use case requested
|
||||
// by the team.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// On macOS (uses launchctl):
|
||||
// herodo examples/service_manager/circle_worker_manager.rhai
|
||||
//
|
||||
// On Linux (uses zinit - requires zinit to be running):
|
||||
// First start zinit: zinit -s /tmp/zinit.sock init
|
||||
// herodo examples/service_manager/circle_worker_manager.rhai
|
||||
|
||||
// Circle Worker Manager Example
|
||||
// This example uses the SAL service manager through Rhai integration
|
||||
|
||||
print("🚀 Circle Worker Manager Example");
|
||||
print("=================================");
|
||||
|
||||
// Create the appropriate service manager for the current platform
|
||||
let service_manager = create_service_manager();
|
||||
print("✅ Created service manager for current platform");
|
||||
|
||||
// Simulate a new freezone resident registration
|
||||
let resident_id = "resident_12345";
|
||||
let worker_name = `circle-worker-${resident_id}`;
|
||||
|
||||
print(`\n📝 New freezone resident registered: ${resident_id}`);
|
||||
print(`🔧 Creating circle worker service: ${worker_name}`);
|
||||
|
||||
// Create service configuration for the circle worker
|
||||
let config = #{
|
||||
name: worker_name,
|
||||
binary_path: "/bin/sh",
|
||||
args: [
|
||||
"-c",
|
||||
`echo 'Circle worker for ${resident_id} starting...'; sleep 30; echo 'Circle worker for ${resident_id} completed'`
|
||||
],
|
||||
working_directory: "/tmp",
|
||||
environment: #{
|
||||
"RESIDENT_ID": resident_id,
|
||||
"WORKER_TYPE": "circle",
|
||||
"LOG_LEVEL": "info"
|
||||
},
|
||||
auto_restart: true
|
||||
};
|
||||
|
||||
print("📋 Service configuration created:");
|
||||
print(` Name: ${config.name}`);
|
||||
print(` Binary: ${config.binary_path}`);
|
||||
print(` Args: ${config.args}`);
|
||||
print(` Auto-restart: ${config.auto_restart}`);
|
||||
|
||||
print(`\n🔄 Demonstrating service lifecycle for: ${worker_name}`);
|
||||
|
||||
// 1. Check if service already exists
|
||||
print("\n1️⃣ Checking if service exists...");
|
||||
if exists(service_manager, worker_name) {
|
||||
print("⚠️ Service already exists, removing it first...");
|
||||
remove(service_manager, worker_name);
|
||||
print("🗑️ Existing service removed");
|
||||
} else {
|
||||
print("✅ Service doesn't exist, ready to create");
|
||||
}
|
||||
|
||||
// 2. Start the service
|
||||
print("\n2️⃣ Starting the circle worker service...");
|
||||
start(service_manager, config);
|
||||
print("✅ Service started successfully");
|
||||
|
||||
// 3. Check service status
|
||||
print("\n3️⃣ Checking service status...");
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status: ${status}`);
|
||||
|
||||
// 4. List all services to show our service is there
|
||||
print("\n4️⃣ Listing all managed services...");
|
||||
let services = list(service_manager);
|
||||
print(`📋 Managed services (${services.len()}):`);
|
||||
for service in services {
|
||||
let marker = if service == worker_name { "👉" } else { " " };
|
||||
print(` ${marker} ${service}`);
|
||||
}
|
||||
|
||||
// 5. Wait a moment and check status again
|
||||
print("\n5️⃣ Waiting 3 seconds and checking status again...");
|
||||
sleep(3000); // 3 seconds in milliseconds
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status after 3s: ${status}`);
|
||||
|
||||
// 6. Get service logs
|
||||
print("\n6️⃣ Retrieving service logs...");
|
||||
let logs = logs(service_manager, worker_name, 10);
|
||||
if logs.trim() == "" {
|
||||
print("📄 No logs available yet (this is normal for new services)");
|
||||
} else {
|
||||
print("📄 Recent logs:");
|
||||
let log_lines = logs.split('\n');
|
||||
for i in 0..5 {
|
||||
if i < log_lines.len() {
|
||||
print(` ${log_lines[i]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Demonstrate start_and_confirm with timeout
|
||||
print("\n7️⃣ Testing start_and_confirm (should succeed quickly since already running)...");
|
||||
start_and_confirm(service_manager, config, 5);
|
||||
print("✅ Service confirmed running within timeout");
|
||||
|
||||
// 8. Stop the service
|
||||
print("\n8️⃣ Stopping the service...");
|
||||
stop(service_manager, worker_name);
|
||||
print("🛑 Service stopped");
|
||||
|
||||
// 9. Check status after stopping
|
||||
print("\n9️⃣ Checking status after stop...");
|
||||
let status = status(service_manager, worker_name);
|
||||
print(`📊 Service status after stop: ${status}`);
|
||||
|
||||
// 10. Restart the service
|
||||
print("\n🔟 Restarting the service...");
|
||||
restart(service_manager, worker_name);
|
||||
print("🔄 Service restarted successfully");
|
||||
|
||||
// 11. Final cleanup
|
||||
print("\n🧹 Cleaning up - removing the service...");
|
||||
remove(service_manager, worker_name);
|
||||
print("🗑️ Service removed successfully");
|
||||
|
||||
// 12. Verify removal
|
||||
print("\n✅ Verifying service removal...");
|
||||
if !exists(service_manager, worker_name) {
|
||||
print("✅ Service successfully removed");
|
||||
} else {
|
||||
print("⚠️ Service still exists after removal");
|
||||
}
|
||||
|
||||
print("\n🎉 Circle worker management demonstration complete!");
|
78
examples/zinit/zinit_basic.rhai
Normal file
78
examples/zinit/zinit_basic.rhai
Normal file
@@ -0,0 +1,78 @@
|
||||
// Basic example of using the Zinit client in Rhai
|
||||
|
||||
// Socket path for Zinit
|
||||
let socket_path = "/tmp/zinit.sock";
|
||||
|
||||
// List all services
|
||||
print("Listing all services:");
|
||||
let services = zinit_list(socket_path);
|
||||
|
||||
if services.is_empty() {
|
||||
print("No services found.");
|
||||
} else {
|
||||
// Iterate over the keys of the map
|
||||
for name in services.keys() {
|
||||
let state = services[name];
|
||||
print(`${name}: ${state}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get status of a specific service
|
||||
let service_name = "test";
|
||||
print(`Getting status for ${service_name}:`);
|
||||
|
||||
try {
|
||||
let status = zinit_status(socket_path, service_name);
|
||||
print(`Service: ${status.name}`);
|
||||
print(`PID: ${status.pid}`);
|
||||
print(`State: ${status.state}`);
|
||||
print(`Target: ${status.target}`);
|
||||
print("Dependencies:");
|
||||
|
||||
for (dep, state) in status.after.keys() {
|
||||
print(` ${dep}: ${state}`);
|
||||
}
|
||||
} catch(err) {
|
||||
print(`Error getting status: ${err}`);
|
||||
}
|
||||
|
||||
// Create a new service
|
||||
print("\nCreating a new service:");
|
||||
let new_service = "rhai-test-service";
|
||||
let exec_command = "echo 'Hello from Rhai'";
|
||||
let oneshot = true;
|
||||
|
||||
try {
|
||||
let result = zinit_create_service(socket_path, new_service, exec_command, oneshot);
|
||||
print(`Service created: ${result}`);
|
||||
|
||||
// Monitor the service
|
||||
print("\nMonitoring the service:");
|
||||
let monitor_result = zinit_monitor(socket_path, new_service);
|
||||
print(`Service monitored: ${monitor_result}`);
|
||||
|
||||
// Start the service
|
||||
print("\nStarting the service:");
|
||||
let start_result = zinit_start(socket_path, new_service);
|
||||
print(`Service started: ${start_result}`);
|
||||
|
||||
// Get logs for a specific service
|
||||
print("\nGetting logs:");
|
||||
let logs = zinit_logs(socket_path, new_service);
|
||||
|
||||
for log in logs {
|
||||
print(log);
|
||||
}
|
||||
// Clean up
|
||||
print("\nCleaning up:");
|
||||
let stop_result = zinit_stop(socket_path, new_service);
|
||||
print(`Service stopped: ${stop_result}`);
|
||||
|
||||
let forget_result = zinit_forget(socket_path, new_service);
|
||||
print(`Service forgotten: ${forget_result}`);
|
||||
|
||||
let delete_result = zinit_delete_service(socket_path, new_service);
|
||||
print(`Service deleted: ${delete_result}`);
|
||||
} catch(err) {
|
||||
print(`Error: ${err}`);
|
||||
}
|
41
examples/zinit/zinit_basic2.rhai
Normal file
41
examples/zinit/zinit_basic2.rhai
Normal file
@@ -0,0 +1,41 @@
|
||||
// Basic example of using the Zinit client in Rhai
|
||||
|
||||
// Socket path for Zinit
|
||||
let socket_path = "/tmp/zinit.sock";
|
||||
|
||||
// Create a new service
|
||||
print("\nCreating a new service:");
|
||||
let new_service = "rhai-test-service";
|
||||
let exec_command = "echo 'Hello from Rhai'";
|
||||
let oneshot = true;
|
||||
|
||||
let result = zinit_create_service(socket_path, new_service, exec_command, oneshot);
|
||||
print(`Service created: ${result}`);
|
||||
|
||||
// Monitor the service
|
||||
print("\nMonitoring the service:");
|
||||
let monitor_result = zinit_monitor(socket_path, new_service);
|
||||
print(`Service monitored: ${monitor_result}`);
|
||||
|
||||
// Start the service
|
||||
print("\nStarting the service:");
|
||||
let start_result = zinit_start(socket_path, new_service);
|
||||
print(`Service started: ${start_result}`);
|
||||
|
||||
// Get logs for a specific service
|
||||
print("\nGetting logs:");
|
||||
let logs = zinit_logs(socket_path, new_service);
|
||||
|
||||
for log in logs {
|
||||
print(log);
|
||||
}
|
||||
// Clean up
|
||||
print("\nCleaning up:");
|
||||
let stop_result = zinit_stop(socket_path, new_service);
|
||||
print(`Service stopped: ${stop_result}`);
|
||||
|
||||
let forget_result = zinit_forget(socket_path, new_service);
|
||||
print(`Service forgotten: ${forget_result}`);
|
||||
|
||||
let delete_result = zinit_delete_service(socket_path, new_service);
|
||||
print(`Service deleted: ${delete_result}`);
|
25
herodo/Cargo.toml
Normal file
25
herodo/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "herodo"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["PlanetFirst <info@incubaid.com>"]
|
||||
description = "Herodo - A Rhai script executor for SAL (System Abstraction Layer)"
|
||||
repository = "https://git.threefold.info/herocode/sal"
|
||||
license = "Apache-2.0"
|
||||
keywords = ["rhai", "scripting", "automation", "sal", "system"]
|
||||
categories = ["command-line-utilities", "development-tools"]
|
||||
|
||||
[[bin]]
|
||||
name = "herodo"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Core dependencies for herodo binary
|
||||
env_logger = { workspace = true }
|
||||
rhai = { workspace = true }
|
||||
|
||||
# SAL library for Rhai module registration (with all features for herodo)
|
||||
sal = { path = "..", features = ["all"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
160
herodo/README.md
Normal file
160
herodo/README.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# Herodo - Rhai Script Executor for SAL
|
||||
|
||||
**Version: 0.1.0**
|
||||
|
||||
Herodo is a command-line utility that executes Rhai scripts with full access to the SAL (System Abstraction Layer) library. It provides a powerful scripting environment for automation and system management tasks.
|
||||
|
||||
## Features
|
||||
|
||||
- **Single Script Execution**: Execute individual `.rhai` script files
|
||||
- **Directory Execution**: Execute all `.rhai` scripts in a directory (recursively)
|
||||
- **Sorted Execution**: Scripts are executed in alphabetical order for predictable behavior
|
||||
- **SAL Integration**: Full access to all SAL modules and functions
|
||||
- **Error Handling**: Clear error messages and proper exit codes
|
||||
- **Logging Support**: Built-in logging with `env_logger`
|
||||
|
||||
## Installation
|
||||
|
||||
### Build and Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/PlanetFirst/sal.git
|
||||
cd sal
|
||||
./build_herodo.sh
|
||||
```
|
||||
|
||||
This script will:
|
||||
- Build herodo in debug mode
|
||||
- Install it to `~/hero/bin/herodo` (non-root) or `/usr/local/bin/herodo` (root)
|
||||
- Make it available in your PATH
|
||||
|
||||
**Note**: If using the non-root installation, make sure `~/hero/bin` is in your PATH:
|
||||
```bash
|
||||
export PATH="$HOME/hero/bin:$PATH"
|
||||
```
|
||||
|
||||
### Install from crates.io (Coming Soon)
|
||||
|
||||
```bash
|
||||
# This will be available once herodo is published to crates.io
|
||||
cargo install herodo
|
||||
```
|
||||
|
||||
**Note**: `herodo` is not yet published to crates.io due to publishing rate limits. It will be available soon.
|
||||
|
||||
## Usage
|
||||
|
||||
### Execute a Single Script
|
||||
|
||||
```bash
|
||||
herodo path/to/script.rhai
|
||||
```
|
||||
|
||||
### Execute All Scripts in a Directory
|
||||
|
||||
```bash
|
||||
herodo path/to/scripts/
|
||||
```
|
||||
|
||||
When given a directory, herodo will:
|
||||
1. Recursively find all `.rhai` files
|
||||
2. Sort them alphabetically
|
||||
3. Execute them in order
|
||||
4. Stop on the first error
|
||||
|
||||
## Example Scripts
|
||||
|
||||
### Basic Script
|
||||
```rhai
|
||||
// hello.rhai
|
||||
println("Hello from Herodo!");
|
||||
let result = 42 * 2;
|
||||
println("Result: " + result);
|
||||
```
|
||||
|
||||
### Using SAL Functions
|
||||
```rhai
|
||||
// system_info.rhai
|
||||
println("=== System Information ===");
|
||||
|
||||
// Check if a file exists
|
||||
let config_exists = exist("/etc/hosts");
|
||||
println("Config file exists: " + config_exists);
|
||||
|
||||
// Download a file
|
||||
download("https://example.com/data.txt", "/tmp/data.txt");
|
||||
println("File downloaded successfully");
|
||||
|
||||
// Execute a system command
|
||||
let output = run("ls -la /tmp");
|
||||
println("Directory listing:");
|
||||
println(output.stdout);
|
||||
```
|
||||
|
||||
### Redis Operations
|
||||
```rhai
|
||||
// redis_example.rhai
|
||||
println("=== Redis Operations ===");
|
||||
|
||||
// Set a value
|
||||
redis_set("app_status", "running");
|
||||
println("Status set in Redis");
|
||||
|
||||
// Get the value
|
||||
let status = redis_get("app_status");
|
||||
println("Current status: " + status);
|
||||
```
|
||||
|
||||
## Available SAL Functions
|
||||
|
||||
Herodo provides access to all SAL modules through Rhai:
|
||||
|
||||
- **File System**: `exist()`, `mkdir()`, `delete()`, `file_size()`
|
||||
- **Downloads**: `download()`, `download_install()`
|
||||
- **Process Management**: `run()`, `kill()`, `process_list()`
|
||||
- **Redis**: `redis_set()`, `redis_get()`, `redis_del()`
|
||||
- **PostgreSQL**: Database operations and management
|
||||
- **Network**: HTTP requests, SSH operations, TCP connectivity
|
||||
- **Virtualization**: Container operations with Buildah and Nerdctl
|
||||
- **Text Processing**: String manipulation and template rendering
|
||||
- **And many more...**
|
||||
|
||||
## Error Handling
|
||||
|
||||
Herodo provides clear error messages and appropriate exit codes:
|
||||
|
||||
- **Exit Code 0**: All scripts executed successfully
|
||||
- **Exit Code 1**: Error occurred (file not found, script error, etc.)
|
||||
|
||||
## Logging
|
||||
|
||||
Enable detailed logging by setting the `RUST_LOG` environment variable:
|
||||
|
||||
```bash
|
||||
RUST_LOG=debug herodo script.rhai
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test suite:
|
||||
|
||||
```bash
|
||||
cd herodo
|
||||
cargo test
|
||||
```
|
||||
|
||||
The test suite includes:
|
||||
- Unit tests for core functionality
|
||||
- Integration tests with real script execution
|
||||
- Error handling scenarios
|
||||
- SAL module integration tests
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **rhai**: Embedded scripting language
|
||||
- **env_logger**: Logging implementation
|
||||
- **sal**: System Abstraction Layer library
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
143
herodo/src/lib.rs
Normal file
143
herodo/src/lib.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
//! Herodo - A Rhai script executor for SAL
|
||||
//!
|
||||
//! This library loads the Rhai engine, registers all SAL modules,
|
||||
//! and executes Rhai scripts from a specified directory in sorted order.
|
||||
|
||||
use rhai::{Engine, Scope};
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
/// Run the herodo script executor with the given script path
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `script_path` - Path to a Rhai script file or directory containing Rhai scripts
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
pub fn run(script_path: &str) -> Result<(), Box<dyn Error>> {
|
||||
let path = Path::new(script_path);
|
||||
|
||||
// Check if the path exists
|
||||
if !path.exists() {
|
||||
eprintln!("Error: '{}' does not exist", script_path);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// Create a new Rhai engine
|
||||
let mut engine = Engine::new();
|
||||
|
||||
// TODO: if we create a scope here we could clean up all the different functionsand types regsitered wit the engine
|
||||
// We should generalize the way we add things to the scope for each module sepeartely
|
||||
let mut scope = Scope::new();
|
||||
// Conditionally add Hetzner client only when env config is present
|
||||
if let Ok(cfg) = sal::hetzner::config::Config::from_env() {
|
||||
let hetzner_client = sal::hetzner::api::Client::new(cfg);
|
||||
scope.push("hetzner", hetzner_client);
|
||||
}
|
||||
// This makes it easy to call e.g. `hetzner.get_server()` or `mycelium.get_connected_peers()`
|
||||
// --> without the need of manually created a client for each one first
|
||||
// --> could be conditionally compiled to only use those who we need (we only push the things to the scope that we actually need to run the script)
|
||||
|
||||
|
||||
// Register println function for output
|
||||
engine.register_fn("println", |s: &str| println!("{}", s));
|
||||
|
||||
// Register all SAL modules with the engine
|
||||
sal::rhai::register(&mut engine)?;
|
||||
|
||||
// Collect script files to execute
|
||||
let script_files: Vec<PathBuf> = if path.is_file() {
|
||||
// Single file
|
||||
if let Some(extension) = path.extension() {
|
||||
if extension != "rhai" {
|
||||
eprintln!("Warning: '{}' does not have a .rhai extension", script_path);
|
||||
}
|
||||
}
|
||||
vec![path.to_path_buf()]
|
||||
} else if path.is_dir() {
|
||||
// Directory - collect all .rhai files recursively and sort them
|
||||
let mut files = Vec::new();
|
||||
collect_rhai_files(path, &mut files)?;
|
||||
|
||||
if files.is_empty() {
|
||||
eprintln!("No .rhai files found in directory: {}", script_path);
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
// Sort files for consistent execution order
|
||||
files.sort();
|
||||
|
||||
files
|
||||
} else {
|
||||
eprintln!("Error: '{}' is neither a file nor a directory", script_path);
|
||||
process::exit(1);
|
||||
};
|
||||
|
||||
println!(
|
||||
"Found {} Rhai script{} to execute:",
|
||||
script_files.len(),
|
||||
if script_files.len() == 1 { "" } else { "s" }
|
||||
);
|
||||
|
||||
// Execute each script in sorted order
|
||||
for script_file in script_files {
|
||||
println!("\nExecuting: {}", script_file.display());
|
||||
|
||||
// Read the script content
|
||||
let script = fs::read_to_string(&script_file)?;
|
||||
|
||||
// Execute the script
|
||||
// match engine.eval::<rhai::Dynamic>(&script) {
|
||||
// Ok(result) => {
|
||||
// println!("Script executed successfully");
|
||||
// if !result.is_unit() {
|
||||
// println!("Result: {}", result);
|
||||
// }
|
||||
// }
|
||||
// Err(err) => {
|
||||
// eprintln!("Error executing script: {}", err);
|
||||
// // Exit with error code when a script fails
|
||||
// process::exit(1);
|
||||
// }
|
||||
// }
|
||||
engine.run_with_scope(&mut scope, &script)?;
|
||||
}
|
||||
|
||||
println!("\nAll scripts executed successfully!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Recursively collect all .rhai files from a directory
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `dir` - Directory to search
|
||||
/// * `files` - Vector to collect files into
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Result indicating success or failure
|
||||
fn collect_rhai_files(dir: &Path, files: &mut Vec<PathBuf>) -> Result<(), Box<dyn Error>> {
|
||||
for entry in fs::read_dir(dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
// Recursively search subdirectories
|
||||
collect_rhai_files(&path, files)?;
|
||||
} else if path.is_file() {
|
||||
// Check if it's a .rhai file
|
||||
if let Some(extension) = path.extension() {
|
||||
if extension == "rhai" {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user