-
Notifications
You must be signed in to change notification settings - Fork 61
Expand file tree
/
Copy pathaction.yml
More file actions
377 lines (346 loc) · 14.4 KB
/
Copy pathaction.yml
File metadata and controls
377 lines (346 loc) · 14.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
name: Fullsend
description: >-
Install fullsend-cli, create ${GITHUB_WORKSPACE}/output, invoke fullsend run with
--output-dir pointing at that directory, then upload it with actions/upload-artifact. Does not
read agent configuration from the repository; configure the job environment or CLI as needed.
branding:
icon: zap
color: purple
inputs:
agent:
description: Agent name passed to fullsend run (e.g. triage, code, review).
required: true
version:
description: >-
Release tag or version: use latest, v0.0.1, or 0.0.1. Download URLs use a leading v; asset
names use the version without v (fullsend_0.0.1_linux_amd64.tar.gz).
default: latest
fullsend-dir:
description: Path to fullsend config directory (default GITHUB_WORKSPACE).
default: ""
target-repo:
description: Path to target repo checkout (default GITHUB_WORKSPACE/target-repo).
default: ""
github_token:
description: >-
GitHub token for authenticated API calls (avoids 60 req/hour unauthenticated rate limit).
default: ${{ github.token }}
run-url:
description: URL of the CI/CD run for status comments (optional).
default: ""
status-repo:
description: Repository (owner/repo) for status comments (optional).
default: ""
status-number:
description: Issue/PR number for status comments (optional).
default: ""
mint-url:
description: >-
Mint service URL for on-demand GitHub App tokens. Used for both
status comment tokens and agent runtime tokens (GH_TOKEN,
PUSH_TOKEN, REVIEW_TOKEN) minted by mintAgentToken().
default: ""
runs:
using: composite
steps:
- name: Detect install method
id: detect
shell: bash
env:
VERSION: ${{ inputs.version }}
GH_TOKEN: ${{ inputs.github_token }}
run: |
set -euo pipefail
# retry_curl: retry a curl command up to MAX_ATTEMPTS times with exponential backoff.
# Usage: retry_curl [curl args...]
retry_curl() {
local max_attempts=3
local attempt=1
local delay=5
while true; do
if curl "$@"; then
return 0
fi
if (( attempt >= max_attempts )); then
echo "::error::API request failed after ${max_attempts} attempts"
return 1
fi
echo "::warning::API attempt ${attempt}/${max_attempts} failed, retrying in ${delay}s..."
sleep "${delay}"
(( attempt++ ))
(( delay *= 3 ))
done
}
# Use vendored binary if present (placed by fullsend admin install --vendor).
# Per-org mode stores it at bin/fullsend (in .fullsend config repo);
# per-repo mode stores it at .fullsend/bin/fullsend (in the target repo).
# GitHub Contents API does not preserve the executable bit, so check -f not -x.
VENDORED=""
if [[ -f "${GITHUB_WORKSPACE}/.fullsend/bin/fullsend" ]]; then
VENDORED="${GITHUB_WORKSPACE}/.fullsend/bin/fullsend"
elif [[ -f "${GITHUB_WORKSPACE}/bin/fullsend" ]]; then
VENDORED="${GITHUB_WORKSPACE}/bin/fullsend"
fi
if [[ -n "${VENDORED}" ]]; then
echo "Using vendored binary: ${VENDORED#"${GITHUB_WORKSPACE}/"}"
echo "install-method=vendored" >> "${GITHUB_OUTPUT}"
echo "vendored-path=${VENDORED}" >> "${GITHUB_OUTPUT}"
exit 0
fi
VERSION="$(printf '%s' "${VERSION:-latest}" | tr -d '[:space:]')"
VERSION="${VERSION:-latest}"
# Resolve 'latest' to the actual tag before checking the release API.
if [[ "${VERSION}" == "latest" ]]; then
TAG="$(retry_curl -fsSL \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
"https://api.github.com/repos/fullsend-ai/fullsend/releases/latest" | jq -r '.tag_name')"
if [[ -z "${TAG}" || "${TAG}" == "null" ]]; then
echo "::error::Could not resolve latest release tag"
exit 1
fi
VERSION="${TAG}"
fi
if [[ "${VERSION}" == v* ]]; then
VERSION_URL="${VERSION}"
VERSION_ASSET="${VERSION#v}"
else
VERSION_URL="v${VERSION}"
VERSION_ASSET="${VERSION}"
fi
HTTP_STATUS="$(retry_curl -sL -o /dev/null -w "%{http_code}" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GH_TOKEN}" \
"https://api.github.com/repos/fullsend-ai/fullsend/releases/tags/${VERSION_URL}")"
if [[ "${HTTP_STATUS}" == "200" ]]; then
echo "Found release ${VERSION_URL}; downloading pre-built binary"
echo "install-method=release" >> "${GITHUB_OUTPUT}"
echo "version-url=${VERSION_URL}" >> "${GITHUB_OUTPUT}"
echo "version-asset=${VERSION_ASSET}" >> "${GITHUB_OUTPUT}"
elif [[ "${HTTP_STATUS}" == "404" ]]; then
echo "No release found for ${VERSION_URL}; building from source at ref: ${VERSION}"
echo "install-method=source" >> "${GITHUB_OUTPUT}"
echo "source-ref=${VERSION}" >> "${GITHUB_OUTPUT}"
else
echo "::error::Unexpected HTTP ${HTTP_STATUS} checking release ${VERSION_URL}; cannot proceed"
exit 1
fi
- name: Install vendored binary
if: steps.detect.outputs.install-method == 'vendored'
shell: bash
env:
VENDORED: ${{ steps.detect.outputs.vendored-path }}
run: |
set -euo pipefail
mkdir -p "${RUNNER_TEMP}/fullsend"
cp "${VENDORED}" "${RUNNER_TEMP}/fullsend/fullsend"
chmod +x "${RUNNER_TEMP}/fullsend/fullsend"
echo "${RUNNER_TEMP}/fullsend" >> "${GITHUB_PATH}"
- name: Download release binary
if: steps.detect.outputs.install-method == 'release'
shell: bash
env:
VERSION_URL: ${{ steps.detect.outputs.version-url }}
VERSION_ASSET: ${{ steps.detect.outputs.version-asset }}
RUNNER_OS: ${{ runner.os }}
RUNNER_ARCH: ${{ runner.arch }}
GH_TOKEN: ${{ inputs.github_token }}
run: |
set -euo pipefail
# retry_curl: retry a curl command up to MAX_ATTEMPTS times with exponential backoff.
# Usage: retry_curl [curl args...]
retry_curl() {
local max_attempts=3
local attempt=1
local delay=5
while true; do
if curl "$@"; then
return 0
fi
if (( attempt >= max_attempts )); then
echo "::error::Download failed after ${max_attempts} attempts"
return 1
fi
echo "::warning::Download attempt ${attempt}/${max_attempts} failed, retrying in ${delay}s..."
sleep "${delay}"
(( attempt++ ))
(( delay *= 3 ))
done
}
os="$(echo "${RUNNER_OS}" | tr '[:upper:]' '[:lower:]')"
case "${os}" in
macos) os=darwin ;;
esac
arch="$(echo "${RUNNER_ARCH}" | tr '[:upper:]' '[:lower:]')"
case "${arch}" in
x64) arch=amd64 ;;
x86) arch=386 ;;
arm64|aarch64) arch=arm64 ;;
esac
ASSET_NAME="fullsend_${VERSION_ASSET}_${os}_${arch}.tar.gz"
URL="https://github.com/fullsend-ai/fullsend/releases/download/${VERSION_URL}/${ASSET_NAME}"
echo "Downloading from: ${URL}"
retry_curl -fsSL "${URL}" -o "/tmp/${ASSET_NAME}"
mkdir -p "${RUNNER_TEMP}/fullsend"
tar -xzf "/tmp/${ASSET_NAME}" -C "${RUNNER_TEMP}/fullsend"
echo "${RUNNER_TEMP}/fullsend" >> "${GITHUB_PATH}"
- name: Clone fullsend at ref for source build
if: steps.detect.outputs.install-method == 'source'
shell: bash
env:
SOURCE_REF: ${{ steps.detect.outputs.source-ref }}
GH_TOKEN: ${{ inputs.github_token }}
run: |
set -euo pipefail
echo "::add-mask::${GH_TOKEN}"
SRC="${RUNNER_TEMP}/fullsend-src"
REMOTE="https://x-access-token:${GH_TOKEN}@github.com/fullsend-ai/fullsend.git"
echo "Cloning fullsend at ref: ${SOURCE_REF}"
git init "${SRC}"
git -C "${SRC}" remote add origin "${REMOTE}"
# Shallow fetch works for branch/tag refs but not bare commit SHAs (GitHub disallows
# unadvertised SHA fetches). Fall back to a full clone when shallow fetch fails.
if ! git -C "${SRC}" fetch --depth 1 origin "${SOURCE_REF}"; then
echo "Shallow fetch failed (ref may be a commit SHA); falling back to full clone"
rm -rf "${SRC}"
git clone "${REMOTE}" "${SRC}"
git -C "${SRC}" checkout "${SOURCE_REF}"
else
git -C "${SRC}" checkout FETCH_HEAD
fi
- name: Set up Go for source build
if: steps.detect.outputs.install-method == 'source'
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: ${{ runner.temp }}/fullsend-src/go.mod
cache-dependency-path: ${{ runner.temp }}/fullsend-src/go.sum
- name: Build fullsend from source
if: steps.detect.outputs.install-method == 'source'
shell: bash
run: |
set -euo pipefail
mkdir -p "${RUNNER_TEMP}/fullsend"
cd "${RUNNER_TEMP}/fullsend-src"
make go-build
cp bin/fullsend "${RUNNER_TEMP}/fullsend/fullsend"
echo "${RUNNER_TEMP}/fullsend" >> "${GITHUB_PATH}"
- name: Print fullsend version
shell: bash
run: fullsend --version
- name: Install Podman
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y podman
podman --version
- name: Configure rootless Podman
shell: bash
run: |
test -f /sys/fs/cgroup/cgroup.controllers || { echo "::error::cgroups v2 required"; exit 1; }
whoami_user="$(whoami)"
grep -q "^${whoami_user}:" /etc/subuid || sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "${whoami_user}"
podman system migrate
podman info
systemctl --user start podman.socket
- name: Configure OpenShell gateway
shell: bash
run: |
source "$GITHUB_ACTION_PATH/.github/scripts/openshell-version.sh"
mkdir -p $HOME/.config/openshell/
cat > $HOME/.config/openshell/gateway.env << EOF
OPENSHELL_BIND_ADDRESS=0.0.0.0
EOF
cat > $HOME/.config/openshell/gateway.toml << EOF
[openshell]
version = 1
[openshell.gateway]
supervisor_image = "ghcr.io/nvidia/openshell/supervisor:${OPENSHELL_VERSION}"
EOF
- name: Install OpenShell CLI
shell: bash
run: "$GITHUB_ACTION_PATH/.github/scripts/install-openshell.sh"
- name: Install validation dependencies
shell: bash
run: pip install --quiet "jsonschema>=4.18.0"
- name: Set up Go for target repo pre-commit hooks
if: hashFiles(format('{0}/go.mod', inputs.target-repo)) != ''
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: ${{ inputs.target-repo }}/go.mod
cache: false
- name: Run fullsend
if: inputs.agent != '__install_only__'
shell: bash
env:
GH_TOKEN: ${{ inputs.github_token }}
AGENT: ${{ inputs.agent }}
FULLSEND_DIR: ${{ inputs.fullsend-dir }}
TARGET_REPO: ${{ inputs.target-repo }}
STATUS_RUN_URL: ${{ inputs.run-url }}
STATUS_REPO: ${{ inputs.status-repo }}
STATUS_NUMBER: ${{ inputs.status-number }}
MINT_URL: ${{ inputs.mint-url }}
run: |
set -euo pipefail
FULLSEND_DIR="${FULLSEND_DIR:-${GITHUB_WORKSPACE}}"
TARGET_REPO="${TARGET_REPO:-${GITHUB_WORKSPACE}/target-repo}"
mkdir -p "${GITHUB_WORKSPACE}/output"
# SECURITY: Never expose --no-post-script as a workflow input.
# Post-scripts enforce secret scanning, protected-path blocks,
# and review-downgrade controls. Skipping them in CI bypasses
# all post-push security gates.
STATUS_FLAGS=()
if [[ -n "${STATUS_REPO}" && -n "${STATUS_NUMBER}" ]]; then
STATUS_FLAGS+=(--status-repo "${STATUS_REPO}" --status-number "${STATUS_NUMBER}")
if [[ -n "${STATUS_RUN_URL}" ]]; then
STATUS_FLAGS+=(--run-url "${STATUS_RUN_URL}")
fi
fi
MINT_FLAGS=()
if [[ -n "${MINT_URL}" ]]; then
MINT_FLAGS+=(--mint-url "${MINT_URL}")
fi
fullsend run "${AGENT}" \
--fullsend-dir "${FULLSEND_DIR}" \
--output-dir "${GITHUB_WORKSPACE}/output" \
--target-repo "${TARGET_REPO}" \
"${STATUS_FLAGS[@]+"${STATUS_FLAGS[@]}"}" \
"${MINT_FLAGS[@]+"${MINT_FLAGS[@]}"}"
- name: Finalize orphaned status comment
if: always() && inputs.agent != '__install_only__' && inputs.status-repo != '' && inputs.status-number != '' && inputs.mint-url != ''
shell: bash
env:
MINT_URL: ${{ inputs.mint-url }}
AGENT: ${{ inputs.agent }}
STATUS_REPO: ${{ inputs.status-repo }}
STATUS_NUMBER: ${{ inputs.status-number }}
RUN_ID: ${{ github.run_id }}
RUN_URL: ${{ inputs.run-url }}
COMMIT_SHA: ${{ github.sha }}
JOB_STATUS: ${{ job.status }}
run: |
set -euo pipefail
# When the fullsend process is hard-killed (SIGKILL, OOM, segfault),
# the deferred PostCompletion call never runs and the status comment
# remains in "Started" state. This step runs unconditionally (if:
# always()) to detect and finalize orphaned comments. See #2149.
RECONCILE_FLAGS=(--repo "${STATUS_REPO}" --number "${STATUS_NUMBER}" --run-id "${RUN_ID}")
RECONCILE_FLAGS+=(--mint-url "${MINT_URL}" --role "${AGENT}")
if [[ -n "${RUN_URL}" ]]; then
RECONCILE_FLAGS+=(--run-url "${RUN_URL}")
fi
if [[ -n "${COMMIT_SHA}" ]]; then
RECONCILE_FLAGS+=(--sha "${COMMIT_SHA}")
fi
if [[ "${JOB_STATUS}" == "cancelled" ]]; then
RECONCILE_FLAGS+=(--reason cancelled)
fi
fullsend reconcile-status "${RECONCILE_FLAGS[@]+"${RECONCILE_FLAGS[@]}"}" || {
echo "::warning::Could not reconcile orphaned status comment"
}
- name: Upload fullsend artifacts
if: always() && inputs.agent != '__install_only__'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: fullsend-${{ inputs.agent }}
path: ${{ github.workspace }}/output