Skip to content

Commit 8574d8e

Browse files
nsheapsclaudeautomation-nsheaps[bot]
authored
feat: add arcane-deploy GitHub Action for Docker Compose GitOps sync
* feat: add arcane-deploy action for Docker Compose GitOps sync Adds a reusable GitHub Action that automatically deploys Docker Compose stacks to Arcane's container management platform via its GitOps sync API. Supports directory-based compose file discovery, explicit file lists, shared environment variables, and create-or-update semantics (no deletes). https://claude.ai/code/session_01PrmzWtCEcxJTMjxXje3ndd * Move arcane-deploy detailed docs to action README The root README now has a brief entry matching the style of other actions, with a link to the full docs in .github/actions/arcane-deploy/README.md. https://claude.ai/code/session_01PrmzWtCEcxJTMjxXje3ndd * chore: `mise format` Triggered by: 420104a Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22290862379 * Add PR review reports (in progress) Review agents for simplicity, flexibility, security, and usability have completed. Remaining categories (documentation, patterns, best-practices, quality-assurance) are still running and will be added in a follow-up commit. https://claude.ai/code/session_01PrmzWtCEcxJTMjxXje3ndd * Add remaining review reports (documentation, best-practices, patterns pending) https://claude.ai/code/session_01PrmzWtCEcxJTMjxXje3ndd * chore: `mise format` Triggered by: 166213a Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22321183132 * chore: `mise format` Triggered by: 877bd47 Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22321202828 * docs: add complete PR review with overall report All 8 category reviews complete plus overall summary. https://claude.ai/code/session_01PrmzWtCEcxJTMjxXje3ndd * chore: `mise format` Triggered by: 2afd682 Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22321236572 * fix: address all review findings — security, reliability, usability, docs Security (Critical): - C1: Move ::add-mask:: for API key to top of script, before any logging - C2: Add ::add-mask:: for git token immediately after assignment Security/Reliability (High): - H1: Mask env-var values, validate key format, reject newline injection - H2: Replace curl || true with proper transport error handling + timeouts - H3: Validate sync-interval is a positive integer before jq --argjson - H7: Add jq_extract_id() helper to guard against null/malformed API IDs Usability (High): - H4: Reject unsupported auth-type values (remove ssh until implemented) - H5: Validate git-token is required when auth-type is http (the default) - H6: Correct env-vars description — these are runner env vars, not container env Simplicity: - Deduplicate trigger-sync logic (was in both if/else branches) - Remove unnecessary auto_sync_val intermediate variable - Extract trim() and require_input() helper functions - Add comment explaining sync name dedup logic Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: `mise format` Triggered by: d556cfd Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22332835231 * fix: validate auto-sync boolean, enforce HTTPS, document scan depth - Validate auto-sync is 'true' or 'false' before passing to jq --argjson - Reject arcane-url values that don't use HTTPS - Document maxdepth 2 scan limit in compose-dir descriptions (action.yml, README) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: `mise format` Triggered by: 11bdea1 Workflow run: https://github.com/nsheaps/github-actions/actions/runs/22333108862 * fix: omit token for auth-type=none, deduplicate compose files, log trigger failures - M7: Omit token field from create-repository payload when auth-type=none - M5: Deduplicate compose files when compose-dir scan overlaps explicit list - N2: Log a warning when trigger-sync API call fails instead of silently continuing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: automation-nsheaps[bot] <251779498+automation-nsheaps[bot]@users.noreply.github.com>
1 parent 81a7845 commit 8574d8e

13 files changed

Lines changed: 1582 additions & 0 deletions

File tree

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Overall PR Review: arcane-deploy GitHub Action
2+
3+
## Score Summary
4+
5+
| Category | Score | Status |
6+
| ----------------- | ---------- | ------------------ |
7+
| Simplicity | 42/100 | :rotating_light: |
8+
| Flexibility | 62/100 | :rotating_light: |
9+
| Usability | 72/100 | :warning: |
10+
| Documentation | 82/100 | :warning: |
11+
| Security | 52/100 | :rotating_light: |
12+
| Patterns | 62/100 | :rotating_light: |
13+
| Best Practices | 86/100 | :white_check_mark: |
14+
| Quality Assurance | 52/100 | :rotating_light: |
15+
| **Overall** | **64/100** | :rotating_light: |
16+
17+
## Executive Summary
18+
19+
This PR introduces a well-intentioned `arcane-deploy` GitHub Action that automates Docker Compose stack deployment to Arcane via its GitOps sync API. The action follows the repo's structural conventions (composite action, `action.yml` + `action.sh`, `INPUT_*` env mapping) and demonstrates strong engineering in several areas: idempotent upsert logic, safe JSON construction via `jq --arg`, proper use of `::group::` logging, and comprehensive README documentation.
20+
21+
However, the action has significant issues across multiple dimensions that warrant a round of revisions before merge:
22+
23+
### Critical Issues (Must Fix)
24+
25+
1. **Security: `git-token` is never masked** (`action.sh:14`) -- The git token secret is never passed through `::add-mask::`, creating a real risk of secret leakage in workflow logs. The API key is masked (line 307) but too late in the script.
26+
27+
2. **Security: `GITHUB_ENV` injection via `env-vars`** (`action.sh:260-278`) -- User-supplied `KEY=VALUE` lines are written directly to `$GITHUB_ENV` with no sanitization. Multiline values or delimiter manipulation can overwrite arbitrary environment variables for subsequent workflow steps.
28+
29+
3. **Security: No URL scheme validation** (`action.sh:5`) -- `arcane-url` feeds directly into `curl` with no HTTPS enforcement, enabling SSRF risk.
30+
31+
4. **Correctness: `sync_name_from_path` produces collisions** (`action.sh:111-127`) -- `basename(dirname(path))` naming causes two compose files in sibling directories with the same leaf name (e.g., `web/frontend/compose.yml` and `api/frontend/compose.yml`) to produce identical sync names, with the second silently overwriting the first.
32+
33+
5. **Correctness: `curl || true` swallows connection errors** (`action.sh:53`) -- Network failures produce empty `http_code`, which passes the `>= 400` check as "success", sending empty data to `jq` and causing confusing downstream parse errors.
34+
35+
### Important Issues (Should Fix)
36+
37+
6. **Flexibility: `maxdepth 2` is hardcoded and undocumented** (`action.sh:91`) -- Deeper directory structures are silently missed with no way to override.
38+
39+
7. **Flexibility: SSH auth is declared but non-functional** (`action.yml:49-52`) -- `auth-type: ssh` is accepted but no SSH key handling exists.
40+
41+
8. **Usability: `env-vars` semantics are misleading** (`action.yml:81-84`) -- Description implies compose-level injection, but it only writes to `GITHUB_ENV` for subsequent workflow steps.
42+
43+
9. **Patterns: Multiple deviations from repo conventions** -- `set -euo pipefail` vs `set -e`, no colored output, no helper functions (`set_output`, `mask_value`), 2-space vs 4-space indent, second commit lacks conventional commit prefix.
44+
45+
10. **QA: No curl timeout** -- Missing `--connect-timeout`/`--max-time` means the action hangs indefinitely if Arcane is unreachable.
46+
47+
### What's Done Well
48+
49+
- Idempotent upsert pattern matched by `composePath + repositoryId`
50+
- Safe JSON construction with `jq --arg` throughout (no JSON injection)
51+
- Comprehensive action README with three usage examples, inputs table, and "How It Works" section
52+
- Clean four-phase `::group::` structure for readable logs
53+
- Smart defaults derived from GitHub context (`GITHUB_REF_NAME`, `GITHUB_REPOSITORY`)
54+
- First action in the repo with a dedicated README -- a good pattern to adopt
55+
56+
## Detailed Category Reports
57+
58+
Individual detailed reports are available in:
59+
60+
- [Simplicity](./simplicity/REPORT.md)
61+
- [Flexibility](./flexibility/REPORT.md)
62+
- [Usability](./usability/REPORT.md)
63+
- [Documentation](./documentation/REPORT.md)
64+
- [Security](./security/REPORT.md)
65+
- [Patterns](./patterns/REPORT.md)
66+
- [Best Practices](./best-practices/REPORT.md)
67+
- [Quality Assurance](./quality-assurance/REPORT.md)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Best Practices Review
2+
3+
**Score: 86/100**
4+
5+
This PR demonstrates strong adherence to best practices across shell scripting, GitHub Actions design, and API integration. The code is well-structured with clear separation of concerns, consistent error handling, idempotent API operations, and proper use of GitHub Actions workflow commands. The main areas for improvement involve a few defensive coding gaps in the shell script, a missing trap-based cleanup pattern for temp files, and a second commit that does not follow conventional commit format.
6+
7+
## Detailed Findings
8+
9+
### Excellent Practices
10+
11+
**Shell Scripting (Strong)**
12+
13+
- `set -euo pipefail` is present on line 2 of `action.sh`, ensuring fail-fast behavior for unset variables, non-zero exits, and pipeline failures.
14+
- Variables are consistently and correctly double-quoted throughout the entire script (e.g., `"${ARCANE_URL}"`, `"${compose_path}"`), preventing word splitting and globbing issues.
15+
- `local` variable declarations are used in every function (`local method`, `local path`, `local files=()`, `local http_code`, etc.), preventing variable leakage into the global scope.
16+
- `jq` is used consistently for all JSON construction and parsing (lines 138, 148, 160, 186, 198, 225, 247), avoiding fragile string interpolation for JSON payloads. The use of `--arg` and `--argjson` is correct and avoids injection risks.
17+
- The `find` command uses `-print0` with `read -r -d ''` (lines 88-96), correctly handling filenames with spaces or special characters.
18+
- Whitespace trimming uses pure bash parameter expansion (lines 73-74, 267-268), avoiding unnecessary subprocess calls.
19+
20+
**GitHub Actions Structure (Strong)**
21+
22+
- The composite action in `action.yml` is cleanly organized with descriptive input names, good descriptions, sensible defaults, and clear groupings via comments.
23+
- Branding is properly configured with `icon: 'upload-cloud'` and `color: 'purple'` (lines 5-7 of `action.yml`).
24+
- Outputs are properly wired from the step ID to the action outputs (lines 86-97 of `action.yml`).
25+
- The step has a clear `name: Deploy to Arcane` and an explicit `id: deploy` for output references.
26+
- Environment variables are passed explicitly from inputs to the shell script via the `env:` block (lines 107-121 of `action.yml`), which is the correct pattern for composite actions rather than relying on `${{ inputs.* }}` in shell code.
27+
28+
**GitHub Actions Workflow Commands (Strong)**
29+
30+
- `::error::` is used in `log_error()` (line 31), causing errors to appear as annotations in the Actions UI.
31+
- `::group::` / `::endgroup::` is used for logical sections: "Ensuring git repository" (line 132/174), "Discovering compose files" (line 313/320), "Syncing compose stacks" (line 326/337), and "Setting shared environment variables" (line 265/277).
32+
- `::add-mask::` is used for the API key (line 307), preventing it from leaking in logs.
33+
34+
**API Integration (Strong)**
35+
36+
- The `arcane_api()` helper (lines 37-64) centralizes all HTTP requests with consistent authentication, error handling, and HTTP status code checking (`http_code -ge 400`).
37+
- The upsert pattern in `upsert_sync()` (lines 179-257) matches by `composePath` + `repositoryId`, making the operation fully idempotent on re-runs.
38+
- `ensure_repository()` (lines 131-175) follows the same find-or-create pattern, and also updates credentials on existing repos to keep tokens current.
39+
- Error responses include both the HTTP status code and the response body (lines 56-58), which aids debugging.
40+
41+
**Documentation (Strong)**
42+
43+
- The `README.md` provides usage examples for all three patterns (directory scan, explicit list, environment variables), a complete inputs/outputs table, and a clear "How It Works" explanation.
44+
- The root `README.md` is updated with a concise entry and a link to the detailed docs.
45+
46+
**Input Validation (Good)**
47+
48+
- Required inputs are validated early (lines 289-304) with descriptive error messages and `exit 1`.
49+
- The mutual requirement of `compose-dir` or `compose-files` is explicitly checked (lines 301-304).
50+
51+
### Areas for Improvement
52+
53+
**1. Temp File Cleanup via Trap (Medium Impact)**
54+
In `arcane_api()` (lines 43-63), a temp file is created with `mktemp` and cleaned up manually with `rm -f`. If the function is interrupted between `mktemp` and `rm -f` (e.g., by a signal or `set -e` triggering on `cat`), the temp file will be leaked. Best practice is to use a `trap` for cleanup:
55+
56+
```bash
57+
local tmp_body
58+
tmp_body=$(mktemp)
59+
trap "rm -f '${tmp_body}'" RETURN
60+
```
61+
62+
Alternatively, a global trap at the top of the script could manage a temp directory.
63+
64+
**2. Second Commit Does Not Follow Conventional Commits (Low-Medium Impact)**
65+
The first commit (`feat: add arcane-deploy action for Docker Compose GitOps sync`) follows conventional commit format correctly. The second commit (`Move arcane-deploy detailed docs to action README`) does not use a conventional prefix. It should be something like `docs: move arcane-deploy detailed docs to action README`. While not a blocking issue, consistency with conventional commits is a stated best practice for this repository given the first commit's format.
66+
67+
**3. Missing `::add-mask::` for `GIT_TOKEN` (Medium Impact)**
68+
The API key is masked on line 307 with `::add-mask::`, but `GIT_TOKEN` (which contains a git authentication credential) is never masked. If it appears in any log output (e.g., in an error response from the API), it would be visible in plain text. A mask should be added:
69+
70+
```bash
71+
if [[ -n "${GIT_TOKEN}" ]]; then
72+
echo "::add-mask::${GIT_TOKEN}"
73+
fi
74+
```
75+
76+
**4. `curl` Failure Suppressed with `|| true` (Low-Medium Impact)**
77+
On line 53, `|| true` suppresses curl's own exit code (e.g., DNS resolution failure, connection refused, timeout). This means a network-level failure would result in an empty `http_code` rather than a caught error. The subsequent `[[ "${http_code}" -ge 400 ]]` comparison would fail unpredictably on an empty string. A more robust approach:
78+
79+
```bash
80+
http_code=$(curl -s -w "%{http_code}" ... -o "${tmp_body}" "$@" "${url}") || {
81+
log_error "API ${method} ${path} - curl failed (network error)"
82+
rm -f "${tmp_body}"
83+
return 1
84+
}
85+
```
86+
87+
**5. No Retry Logic for Transient API Failures (Low Impact)**
88+
API calls are single-shot with no retry. For a deployment action, transient 5xx errors or network blips could cause unnecessary failures. While not strictly required, a simple retry with backoff (even just 1-2 retries) would improve reliability. This is a nice-to-have rather than a requirement.
89+
90+
**6. `export_env_vars` Uses `local` Outside a Function Context Concern (Low Impact)**
91+
In `export_env_vars()` on line 272, `local key` and `local value` are declared inside a `while` loop that is itself inside a function, which is fine. However, the values written to `GITHUB_ENV` (line 275) are not masked. If sensitive values are passed via `env-vars`, they could appear in logs. The function logs `${key}=***` which is good, but consider also masking the values:
92+
93+
```bash
94+
echo "::add-mask::${value}"
95+
```
96+
97+
**7. Composite Action Has a Single Step (Informational)**
98+
The action runs all logic in a single composite step. While this is perfectly functional, some best practice guides suggest splitting validation, discovery, and API operations into separate steps for better observability in the Actions UI. The use of `::group::` within the script mitigates this well, but separate steps would give individual step-level timing and status indicators.
99+
100+
**8. `action.sh` Line Count (Informational)**
101+
At 345 lines, the script is at the upper bound of comfortable single-file shell script size. If more features are added, consider splitting into helper modules sourced from the main script.
102+
103+
## References
104+
105+
- `/home/user/github-actions/.github/actions/arcane-deploy/action.sh` - Main shell script (345 lines)
106+
- `/home/user/github-actions/.github/actions/arcane-deploy/action.yml` - Composite action definition (122 lines)
107+
- `/home/user/github-actions/.github/actions/arcane-deploy/README.md` - Action documentation (97 lines)
108+
- `/home/user/github-actions/README.md` - Root repository README (179 lines)
109+
- [GitHub Actions - Creating a composite action](https://docs.github.com/en/actions/sharing-automations/creating-actions/creating-a-composite-action)
110+
- [GitHub Actions - Workflow commands](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions)
111+
- [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/)
112+
- [Google Shell Style Guide](https://google.github.io/styleguide/shellguide.html)

0 commit comments

Comments
 (0)