|
4 | 4 | push: |
5 | 5 | branches: [main] |
6 | 6 | paths: |
7 | | - - 'arcane/**' |
| 7 | + - 'arcane/hosts/**' |
8 | 8 | - '.github/workflows/arcane-deploy.yaml' |
9 | 9 | workflow_dispatch: |
| 10 | + inputs: |
| 11 | + force: |
| 12 | + description: 'Deploy all stacks regardless of changes (ignore floating tags)' |
| 13 | + type: boolean |
| 14 | + default: false |
10 | 15 |
|
11 | 16 | concurrency: |
12 | 17 | group: arcane-deploy |
13 | 18 | cancel-in-progress: false |
14 | 19 |
|
15 | 20 | jobs: |
16 | | - deploy: |
17 | | - name: Deploy to Arcane |
| 21 | + discover: |
| 22 | + name: Discover Changed Deployments |
18 | 23 | runs-on: ubuntu-latest |
19 | 24 | permissions: |
20 | 25 | contents: read |
| 26 | + outputs: |
| 27 | + matrix: ${{ steps.discover.outputs.matrix }} |
| 28 | + has-deployments: ${{ steps.discover.outputs.has-deployments }} |
21 | 29 | steps: |
22 | | - - uses: actions/checkout@v6 |
| 30 | + - name: Checkout |
| 31 | + uses: actions/checkout@v4 |
| 32 | + with: |
| 33 | + fetch-depth: 0 |
| 34 | + fetch-tags: true |
| 35 | + |
| 36 | + - name: Discover changed deployments |
| 37 | + id: discover |
| 38 | + env: |
| 39 | + FORCE: ${{ inputs.force || 'false' }} |
| 40 | + run: | |
| 41 | + set -euo pipefail |
| 42 | +
|
| 43 | + matrix_items=() |
| 44 | +
|
| 45 | + # Find all yaml/yml files at arcane/hosts/{host}/{project}/{file} |
| 46 | + # (depth 3 relative to arcane/hosts/) |
| 47 | + while IFS= read -r -d '' compose_file; do |
| 48 | + rel_path="${compose_file#./}" |
| 49 | +
|
| 50 | + # Parse host and project from path: arcane/hosts/{host}/{project}/{file} |
| 51 | + host=$(cut -d'/' -f3 <<< "$rel_path") |
| 52 | + project=$(cut -d'/' -f4 <<< "$rel_path") |
| 53 | + project_dir="arcane/hosts/${host}/${project}" |
| 54 | +
|
| 55 | + # Require per-host config file with environment_id |
| 56 | + host_config="arcane/hosts/${host}/arcane.json" |
| 57 | + if [[ ! -f "$host_config" ]]; then |
| 58 | + echo "::warning::No arcane.json found for host '${host}' — skipping '${rel_path}'" |
| 59 | + continue |
| 60 | + fi |
| 61 | +
|
| 62 | + env_id=$(jq -r '.environment_id // empty' "$host_config") |
| 63 | + if [[ -z "$env_id" ]]; then |
| 64 | + echo "::warning::No environment_id in ${host_config} — skipping '${rel_path}'" |
| 65 | + continue |
| 66 | + fi |
| 67 | +
|
| 68 | + # Floating tag: updated to HEAD after each successful deploy |
| 69 | + tag="deployed/arcane/${host}/${project}" |
| 70 | +
|
| 71 | + # Skip if nothing in the project directory changed since last deploy |
| 72 | + if [[ "${FORCE}" != "true" ]] && git tag -l "$tag" | grep -q .; then |
| 73 | + if git diff --quiet "${tag}" HEAD -- "${project_dir}/"; then |
| 74 | + echo "No changes in ${project_dir} since ${tag} — skipping" |
| 75 | + continue |
| 76 | + fi |
| 77 | + echo "Changes detected in ${project_dir} since ${tag}" |
| 78 | + fi |
| 79 | +
|
| 80 | + matrix_items+=("$(jq -cn \ |
| 81 | + --arg host "$host" \ |
| 82 | + --arg project "$project" \ |
| 83 | + --arg compose_file "$rel_path" \ |
| 84 | + --arg tag "$tag" \ |
| 85 | + --arg env_id "$env_id" \ |
| 86 | + '{host: $host, project: $project, "compose-file": $compose_file, tag: $tag, "environment-id": $env_id}')") |
| 87 | + done < <(find arcane/hosts -mindepth 3 -maxdepth 3 -type f \ |
| 88 | + \( -name "*.yaml" -o -name "*.yml" \) -print0 | sort -z) |
| 89 | +
|
| 90 | + if [[ ${#matrix_items[@]} -eq 0 ]]; then |
| 91 | + echo "No deployments needed" |
| 92 | + echo "has-deployments=false" >> "$GITHUB_OUTPUT" |
| 93 | + echo 'matrix={"include":[]}' >> "$GITHUB_OUTPUT" |
| 94 | + else |
| 95 | + matrix_json=$(printf '%s\n' "${matrix_items[@]}" | jq -sc '{include: .}') |
| 96 | + echo "has-deployments=true" >> "$GITHUB_OUTPUT" |
| 97 | + echo "matrix=${matrix_json}" >> "$GITHUB_OUTPUT" |
| 98 | + fi |
23 | 99 |
|
| 100 | + set-global-vars: |
| 101 | + name: Set Arcane Global Variables |
| 102 | + runs-on: ubuntu-latest |
| 103 | + needs: discover |
| 104 | + steps: |
24 | 105 | - name: Set Arcane global variables |
25 | 106 | env: |
26 | 107 | ARCANE_URL: ${{ secrets.ARCANE_URL }} |
@@ -50,16 +131,53 @@ jobs: |
50 | 131 |
|
51 | 132 | echo "Global variables updated" |
52 | 133 |
|
53 | | - - name: Deploy heapsnas stacks |
| 134 | + deploy: |
| 135 | + name: Deploy ${{ matrix.host }}/${{ matrix.project }} |
| 136 | + runs-on: ubuntu-latest |
| 137 | + needs: [discover, set-global-vars] |
| 138 | + if: needs.discover.outputs.has-deployments == 'true' |
| 139 | + strategy: |
| 140 | + matrix: ${{ fromJSON(needs.discover.outputs.matrix) }} |
| 141 | + fail-fast: false |
| 142 | + permissions: |
| 143 | + contents: read |
| 144 | + steps: |
| 145 | + - name: Checkout |
| 146 | + uses: actions/checkout@v4 |
| 147 | + |
| 148 | + - name: Deploy to Arcane |
54 | 149 | # TODO: Pin to commit SHA once nsheaps/github-actions has releases. |
55 | 150 | # See: https://github.com/nsheaps/iac/issues/3 |
56 | 151 | uses: nsheaps/github-actions/.github/actions/arcane-deploy@main |
57 | 152 | with: |
58 | 153 | arcane-url: ${{ secrets.ARCANE_URL }} |
59 | 154 | arcane-api-key: ${{ secrets.ARCANE_API_KEY }} |
60 | | - environment-id: ${{ secrets.ARCANE_ENVIRONMENT_ID }} |
61 | | - compose-dir: arcane/hosts/heapsnas |
62 | | - sync-name-prefix: heapsnas |
| 155 | + environment-id: ${{ matrix['environment-id'] }} |
| 156 | + compose-files: ${{ matrix['compose-file'] }} |
| 157 | + sync-name-prefix: ${{ matrix.host }} |
63 | 158 | auth-type: http |
64 | 159 | git-token: ${{ secrets.GIT_TOKEN }} |
65 | 160 | branch: main |
| 161 | + |
| 162 | + tag: |
| 163 | + name: Tag ${{ matrix.host }}/${{ matrix.project }} |
| 164 | + runs-on: ubuntu-latest |
| 165 | + needs: [discover, deploy] |
| 166 | + if: needs.discover.outputs.has-deployments == 'true' |
| 167 | + strategy: |
| 168 | + matrix: ${{ fromJSON(needs.discover.outputs.matrix) }} |
| 169 | + fail-fast: false |
| 170 | + permissions: |
| 171 | + contents: write |
| 172 | + steps: |
| 173 | + - name: Checkout as app |
| 174 | + uses: nsheaps/github-actions/.github/actions/checkout-as-app@main |
| 175 | + with: |
| 176 | + app-id: ${{ secrets.AUTOMATION_GITHUB_APP_ID }} |
| 177 | + private-key: ${{ secrets.AUTOMATION_GITHUB_APP_PRIVATE_KEY }} |
| 178 | + |
| 179 | + - name: Push deployment tag |
| 180 | + shell: bash |
| 181 | + run: | |
| 182 | + git tag -f "${{ matrix.tag }}" HEAD |
| 183 | + git push origin "refs/tags/${{ matrix.tag }}" --force |
0 commit comments