Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cli/templates/devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
// sandcat's security boundary. Matches the Dev Containers spec
// default for dockerComposeFile scenarios.
"overrideCommand": false,
// Shared cache volumes (sandcat-cache-*) are declared external in
// compose-all.yml. `sandcat run` creates them, but an IDE's "Reopen
// in Container" calls compose directly, so create them here first.
"initializeCommand": "bash ${localWorkspaceFolder}/.devcontainer/sandcat/scripts/ensure-cache-volumes.sh",
// Remove credential sockets that VS Code forwards into the container
// (SSH agent, git credential helper). Clearing env vars alone only
// hides the paths — the socket files in /tmp can still be discovered
Expand Down
28 changes: 28 additions & 0 deletions cli/templates/devcontainer/sandcat/scripts/ensure-cache-volumes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash
#
# Creates the host-wide shared cache volumes (sandcat-cache-*) that
# compose-all.yml declares as `external: true`. Runs on the HOST as the
# devcontainer initializeCommand, so "Reopen in Container" from an IDE
# works on a machine where `sandcat run` has never created them.
#
# Idempotent: `docker volume create` on an existing name is a no-op.
# Silently does nothing when docker is missing so the IDE flow still
# gets compose's own, clearer error.
#
# Usage: ensure-cache-volumes.sh [compose-file]
# Default compose file: ../../compose-all.yml relative to this script.
set -euo pipefail

compose_file=${1:-"$(dirname "$0")/../../compose-all.yml"}

[ -f "$compose_file" ] || exit 0
command -v docker >/dev/null 2>&1 || exit 0

# Only the `name:` lines of the external volume declarations match this
# prefix, so no YAML parser is needed on the host. grep exits 1 on no
# match, which is the normal case for projects without cache volumes.
names=$(grep -E '^[[:space:]]+name:[[:space:]]+sandcat-cache-' "$compose_file" | awk '{print $2}' || true)

for name in $names; do
docker volume create --label sandcat-shared-cache=true "$name" >/dev/null
done
5 changes: 5 additions & 0 deletions cli/test/init/devcontainer.bats
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ teardown() {
assert_success
}

@test "devcontainer.json template runs ensure-cache-volumes.sh on the host before compose" {
run grep '"initializeCommand": "bash ${localWorkspaceFolder}/.devcontainer/sandcat/scripts/ensure-cache-volumes.sh"' "$DEVCONTAINER_JSON"
assert_success
}

@test "customize_devcontainer_json leaves no __PROJECT_NAME__ placeholders" {
customize_devcontainer_json "$DEVCONTAINER_JSON" "my-project"

Expand Down
63 changes: 63 additions & 0 deletions cli/test/init/ensure_cache_volumes.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bats

setup() {
load test_helper
SCRIPT="$SCT_TEMPLATEDIR/devcontainer/sandcat/scripts/ensure-cache-volumes.sh"
COMPOSE_FILE="$BATS_TEST_TMPDIR/compose-all.yml"
cat > "$COMPOSE_FILE" <<'YAML'
services:
agent:
volumes:
- sandcat-cache-maven:/home/vscode/.m2/repository
- sandcat-cache-gradle:/home/vscode/.gradle/caches
volumes:
sandcat-cache-maven:
external: true
name: sandcat-cache-maven
sandcat-cache-gradle:
external: true
name: sandcat-cache-gradle
other-shared:
external: true
name: user-added-volume
YAML
}

teardown() {
unstub_all
}

@test "creates each sandcat-cache-* external volume with the shared-cache label" {
stub docker \
"volume create --label sandcat-shared-cache=true sandcat-cache-maven : :" \
"volume create --label sandcat-shared-cache=true sandcat-cache-gradle : :"

run bash "$SCRIPT" "$COMPOSE_FILE"
assert_success
assert_output ""
}

@test "does nothing when the compose file declares no cache volumes" {
echo "services: {}" > "$COMPOSE_FILE"
stub docker

run bash "$SCRIPT" "$COMPOSE_FILE"
assert_success
}

@test "exits 0 when the compose file is missing" {
run bash "$SCRIPT" "$BATS_TEST_TMPDIR/missing.yml"
assert_success
}

@test "defaults to compose-all.yml two levels above the script" {
mkdir -p "$BATS_TEST_TMPDIR/.devcontainer/sandcat/scripts"
cp "$SCRIPT" "$BATS_TEST_TMPDIR/.devcontainer/sandcat/scripts/"
cp "$COMPOSE_FILE" "$BATS_TEST_TMPDIR/.devcontainer/compose-all.yml"
stub docker \
"volume create --label sandcat-shared-cache=true sandcat-cache-maven : :" \
"volume create --label sandcat-shared-cache=true sandcat-cache-gradle : :"

run bash "$BATS_TEST_TMPDIR/.devcontainer/sandcat/scripts/ensure-cache-volumes.sh"
assert_success
}
4 changes: 3 additions & 1 deletion docs/configuration/caches.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Each is a Docker named volume with a stable host-wide name
projects reference the same physical volume, and `sandcat compose down -v` on
one project will **not** wipe caches other projects rely on. The `sandcat run`
wrapper creates them lazily via `docker volume create` (idempotent), so no
manual setup is required.
manual setup is required. The generated `devcontainer.json` also creates them
on the host via `initializeCommand` (`sandcat/scripts/ensure-cache-volumes.sh`),
so an IDE's "Reopen in Container" works without running `sandcat` first.

Only `/home/vscode/.m2/repository/` is shared, not the whole `.m2/` — user
config like `settings.xml` stays per-project inside `agent-home`. Same pattern
Expand Down
Loading