Skip to content

Commit dd10e47

Browse files
authored
Merge pull request #121 from meta-pytorch/pr-action-for-new-envs
[CI] PR action for new Env contributions
2 parents 7793694 + 4a44eb9 commit dd10e47

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-0
lines changed

.github/workflows/pr-new-env.yml

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
name: PR New Environment
2+
3+
on:
4+
pull_request_target:
5+
types:
6+
- opened
7+
- reopened
8+
- synchronize
9+
paths:
10+
- 'src/envs/**'
11+
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
jobs:
17+
detect-new-envs:
18+
name: Detect Newly Added Environments
19+
runs-on: ubuntu-latest
20+
outputs:
21+
has_new_envs: ${{ steps.detect.outputs.has_new_envs }}
22+
new_envs: ${{ steps.detect.outputs.new_envs }}
23+
new_envs_json: ${{ steps.detect.outputs.new_envs_json }}
24+
steps:
25+
- name: Checkout base branch
26+
uses: actions/checkout@v4
27+
with:
28+
ref: ${{ github.event.pull_request.base.ref }}
29+
path: base
30+
fetch-depth: 0
31+
persist-credentials: false
32+
33+
- name: Checkout PR branch
34+
uses: actions/checkout@v4
35+
with:
36+
repository: ${{ github.event.pull_request.head.repo.full_name }}
37+
ref: ${{ github.event.pull_request.head.ref }}
38+
path: pr
39+
fetch-depth: 0
40+
persist-credentials: false
41+
42+
- name: Determine new environment directories
43+
id: detect
44+
shell: bash
45+
run: |
46+
set -euo pipefail
47+
48+
if [ ! -d base/src/envs ]; then
49+
echo "Base repository missing src/envs directory."
50+
echo "has_new_envs=false" >> "$GITHUB_OUTPUT"
51+
echo "new_envs=" >> "$GITHUB_OUTPUT"
52+
echo "new_envs_json=[]" >> "$GITHUB_OUTPUT"
53+
exit 0
54+
fi
55+
56+
if [ ! -d pr/src/envs ]; then
57+
echo "PR repository missing src/envs directory."
58+
echo "has_new_envs=false" >> "$GITHUB_OUTPUT"
59+
echo "new_envs=" >> "$GITHUB_OUTPUT"
60+
echo "new_envs_json=[]" >> "$GITHUB_OUTPUT"
61+
exit 0
62+
fi
63+
64+
mapfile -t BASE_ENVS < <(cd base/src/envs && find . -maxdepth 1 -mindepth 1 -type d | sed 's|^\./||' | sort)
65+
mapfile -t PR_ENVS < <(cd pr/src/envs && find . -maxdepth 1 -mindepth 1 -type d | sed 's|^\./||' | sort)
66+
67+
declare -A BASE_SET=()
68+
for env in "${BASE_ENVS[@]}"; do
69+
BASE_SET["$env"]=1
70+
done
71+
72+
NEW_ENV_ARRAY=()
73+
for env in "${PR_ENVS[@]}"; do
74+
if [ -z "${BASE_SET[$env]:-}" ]; then
75+
NEW_ENV_ARRAY+=("$env")
76+
fi
77+
done
78+
79+
if [ ${#NEW_ENV_ARRAY[@]} -eq 0 ]; then
80+
echo "No new environment directories detected."
81+
echo "has_new_envs=false" >> "$GITHUB_OUTPUT"
82+
echo "new_envs=" >> "$GITHUB_OUTPUT"
83+
echo "new_envs_json=[]" >> "$GITHUB_OUTPUT"
84+
exit 0
85+
fi
86+
87+
printf 'Detected new environments: %s\n' "$(printf '%s ' "${NEW_ENV_ARRAY[@]}")"
88+
89+
NEW_ENVS_COMMA=$(printf '%s\n' "${NEW_ENV_ARRAY[@]}" | paste -sd, -)
90+
NEW_ENVS_JSON=$(printf '%s\n' "${NEW_ENV_ARRAY[@]}" | python -c 'import json,sys; print(json.dumps([line.strip() for line in sys.stdin if line.strip()]))')
91+
92+
echo "has_new_envs=true" >> "$GITHUB_OUTPUT"
93+
echo "new_envs=${NEW_ENVS_COMMA}" >> "$GITHUB_OUTPUT"
94+
echo "new_envs_json=${NEW_ENVS_JSON}" >> "$GITHUB_OUTPUT"
95+
96+
deploy-and-health-check:
97+
name: Deploy and Validate New Environments
98+
needs: detect-new-envs
99+
if: needs.detect-new-envs.outputs.has_new_envs == 'true'
100+
runs-on: ubuntu-latest
101+
strategy:
102+
matrix:
103+
environment: ${{ fromJSON(needs.detect-new-envs.outputs.new_envs_json) }}
104+
env:
105+
HF_TOKEN: ${{ secrets.HF_PR_TOKEN }}
106+
HF_NAMESPACE: ${{ vars.HF_PR_NAMESPACE }}
107+
SPACE_SUFFIX: -pr-${{ github.event.number }}
108+
steps:
109+
- name: Checkout PR code
110+
uses: actions/checkout@v4
111+
with:
112+
repository: ${{ github.event.pull_request.head.repo.full_name }}
113+
ref: ${{ github.event.pull_request.head.ref }}
114+
fetch-depth: 0
115+
persist-credentials: false
116+
117+
- name: Default Hugging Face namespace
118+
if: env.HF_NAMESPACE == ''
119+
shell: bash
120+
run: echo "HF_NAMESPACE=openenv-testing" >> "$GITHUB_ENV"
121+
122+
- name: Verify Hugging Face token
123+
shell: bash
124+
run: |
125+
if [ -z "${HF_TOKEN:-}" ]; then
126+
echo "HF_TOKEN secret is required for deployment." >&2
127+
exit 1
128+
fi
129+
130+
- name: Install Hugging Face CLI
131+
shell: bash
132+
run: |
133+
curl -LsSf https://hf.co/cli/install.sh | bash
134+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
135+
136+
- name: Deploy environment to Hugging Face
137+
shell: bash
138+
run: |
139+
set -euo pipefail
140+
chmod +x scripts/deploy_to_hf.sh
141+
./scripts/deploy_to_hf.sh --env "${{ matrix.environment }}" --space-suffix "${SPACE_SUFFIX}" --hub-tag "openenv-pr"
142+
143+
- name: Wait for deployment to stabilize
144+
shell: bash
145+
run: sleep 180
146+
147+
- name: Compute Space URLs
148+
id: urls
149+
shell: bash
150+
run: |
151+
set -euo pipefail
152+
153+
if [ -z "${HF_NAMESPACE:-}" ]; then
154+
echo "HF_NAMESPACE is not configured; unable to compute space URLs." >&2
155+
exit 1
156+
fi
157+
158+
namespace_slug=$(echo "${HF_NAMESPACE}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
159+
space_name="${{ matrix.environment }}${SPACE_SUFFIX}"
160+
space_slug=$(echo "${space_name}" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
161+
health_url="https://${namespace_slug}-${space_slug}.hf.space/health"
162+
live_url="https://${namespace_slug}-${space_slug}.hf.space"
163+
space_repo_url="https://huggingface.co/spaces/${HF_NAMESPACE}/${space_name}"
164+
165+
echo "namespace_slug=${namespace_slug}" >> "$GITHUB_OUTPUT"
166+
echo "space_name=${space_name}" >> "$GITHUB_OUTPUT"
167+
echo "space_slug=${space_slug}" >> "$GITHUB_OUTPUT"
168+
echo "health_url=${health_url}" >> "$GITHUB_OUTPUT"
169+
echo "live_url=${live_url}" >> "$GITHUB_OUTPUT"
170+
echo "space_repo_url=${space_repo_url}" >> "$GITHUB_OUTPUT"
171+
172+
- name: Perform environment health check
173+
id: health_check
174+
continue-on-error: true
175+
shell: bash
176+
env:
177+
HEALTH_URL: ${{ steps.urls.outputs.health_url }}
178+
SPACE_NAME: ${{ steps.urls.outputs.space_name }}
179+
run: |
180+
set -euo pipefail
181+
182+
if [ -z "${HEALTH_URL:-}" ]; then
183+
echo "HEALTH_URL not provided; cannot perform health check." >&2
184+
exit 1
185+
fi
186+
187+
echo "Checking health for ${SPACE_NAME} at ${HEALTH_URL}"
188+
189+
success=0
190+
for attempt in {1..5}; do
191+
status=$(curl -sS -o response.json -w "%{http_code}" "$HEALTH_URL" || echo "000")
192+
if [ "$status" = "200" ]; then
193+
echo "Health check passed for ${SPACE_NAME}"
194+
cat response.json
195+
success=1
196+
break
197+
fi
198+
echo "Attempt ${attempt} returned status ${status}. Retrying in 30 seconds..."
199+
sleep 30
200+
done
201+
202+
if [ $success -ne 1 ]; then
203+
echo "Health check failed for ${SPACE_NAME}" >&2
204+
if [ -f response.json ]; then
205+
echo "Last response payload:"
206+
cat response.json
207+
fi
208+
exit 1
209+
fi
210+
211+
- name: Comment on PR with deployment status
212+
if: always()
213+
uses: actions/github-script@v7
214+
env:
215+
HEALTH_CONCLUSION: ${{ steps.health_check.conclusion }}
216+
SPACE_NAME: ${{ steps.urls.outputs.space_name }}
217+
LIVE_URL: ${{ steps.urls.outputs.live_url }}
218+
SPACE_REPO_URL: ${{ steps.urls.outputs.space_repo_url }}
219+
ENV_NAME: ${{ matrix.environment }}
220+
with:
221+
github-token: ${{ secrets.GITHUB_TOKEN }}
222+
script: |
223+
const status = process.env.HEALTH_CONCLUSION || 'failure';
224+
const spaceName = process.env.SPACE_NAME;
225+
const liveUrl = process.env.LIVE_URL;
226+
const repoUrl = process.env.SPACE_REPO_URL;
227+
const envName = process.env.ENV_NAME;
228+
229+
const header = status === 'success'
230+
? `✅ Deployment succeeded for \`${envName}\``
231+
: `⚠️ Deployment failed for \`${envName}\``;
232+
233+
const summary = status === 'success'
234+
? 'Nice work! Wait for a code review and we\'re ready to go.'
235+
: 'Please resolve your environment.';
236+
237+
const body = [
238+
header,
239+
'',
240+
`- Space repo: [${repoUrl}](${repoUrl})`,
241+
`- Live URL: [${liveUrl}](${liveUrl})`,
242+
'',
243+
summary,
244+
'',
245+
'You can iterate locally or validate fixes by running `scripts/deploy_to_hf.sh --env "' + envName + '"`.'
246+
].join('\n');
247+
248+
await github.rest.issues.createComment({
249+
owner: context.repo.owner,
250+
repo: context.repo.repo,
251+
issue_number: context.payload.pull_request.number,
252+
body
253+
});
254+
255+
- name: Fail job if health check failed
256+
if: steps.health_check.conclusion == 'failure'
257+
run: exit 1

scripts/deploy_to_hf.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ while [[ $# -gt 0 ]]; do
7575
ENV_NAME="$2"
7676
shift 2
7777
;;
78+
--hub-tag)
79+
HUB_TAG="$2"
80+
shift 2
81+
;;
7882
--base-sha|--base-image-sha)
7983
BASE_IMAGE_SHA="$2"
8084
shift 2
@@ -127,6 +131,10 @@ while [[ $# -gt 0 ]]; do
127131
esac
128132
done
129133

134+
if [ -z "$HUB_TAG" ]; then
135+
HUB_TAG="openenv"
136+
fi
137+
130138
if [ -z "$ENV_NAME" ]; then
131139
echo "Error: Environment name is required" >&2
132140
usage
@@ -364,6 +372,8 @@ sdk: docker
364372
pinned: false
365373
app_port: 8000
366374
base_path: /web
375+
tags:
376+
- ${HUB_TAG}
367377
---
368378
369379
# ${env_title} Environment Server

0 commit comments

Comments
 (0)