1+ name : " Deploy Preview"
2+
3+ env :
4+ TEST_USERNAME : " preview-user"
5+ TEST_PASSWORD : " P@sswo3d"
6+ TEST_SUPERADMIN_USER : " preview-admin"
7+ TEST_SUPERADMIN_PASSWORD : " P@sswo3d-admin"
8+ NEBARI_IMAGE_TAG : " main"
9+ PYTHON_VERSION : " 3.11"
10+
11+ on :
12+ pull_request :
13+ types : [labeled, unlabeled]
14+
15+ concurrency :
16+ group : deploy-preview-${{ github.event.pull_request.number }}
17+ cancel-in-progress : true
18+
19+ jobs :
20+ deploy-preview :
21+ if : github.event.action == 'labeled' && github.event.label.name == 'deploy-preview'
22+ runs-on : " cirun-runner--${{ github.run_id }}"
23+ defaults :
24+ run :
25+ shell : bash -l {0}
26+ env :
27+ APP_DNS : " nebari-pr-${{ github.event.pull_request.number }}.nebari.dev"
28+ SSH_DNS : " nebari-pr-${{ github.event.pull_request.number }}-ssh.nebari.dev"
29+ PREVIEW_DIR : " preview-pr-${{ github.event.pull_request.number }}"
30+ steps :
31+ - name : " Checkout Infrastructure"
32+ uses : actions/checkout@main
33+ with :
34+ fetch-depth : 0
35+
36+ - name : " Checkout PR"
37+ run : |
38+ git fetch origin pull/${{ github.event.pull_request.number }}/head:pr-${{ github.event.pull_request.number }}
39+ git checkout pr-${{ github.event.pull_request.number }}
40+
41+ # https://kind.sigs.k8s.io/docs/user/known-issues/#pod-errors-due-to-too-many-open-files
42+ - name : " Update inotify ulimit"
43+ run : |
44+ sudo sysctl fs.inotify.max_user_watches=524288
45+ sudo sysctl fs.inotify.max_user_instances=512
46+
47+ - name : Setup runner for local deployment
48+ uses : ./.github/actions/setup-local
49+
50+ - name : Set up Python
51+ uses : conda-incubator/setup-miniconda@v3
52+ env :
53+ CONDA : /home/runnerx/miniconda3
54+ with :
55+ auto-update-conda : true
56+ python-version : ${{ env.PYTHON_VERSION }}
57+ miniconda-version : " latest"
58+ activate-environment : nebari
59+
60+ - name : Install JQ
61+ run : |
62+ sudo apt-get update
63+ sudo apt-get install jq -y
64+
65+ - name : Install Nebari
66+ run : pip install .[dev]
67+
68+ - name : Initialize Nebari config for preview deployment
69+ id : init
70+ run : |
71+ mkdir -p ${{ env.PREVIEW_DIR }}
72+ cd ${{ env.PREVIEW_DIR }}
73+
74+ nebari init local --domain ${{ env.APP_DNS }} --namespace dev --auth-provider password
75+
76+ echo "domain=${{ env.APP_DNS }}" >> $GITHUB_OUTPUT
77+ echo "directory=${{ env.PREVIEW_DIR }}" >> $GITHUB_OUTPUT
78+ echo "config=nebari-config.yaml" >> $GITHUB_OUTPUT
79+
80+ - name : Deploy Nebari Preview
81+ working-directory : ${{ steps.init.outputs.directory }}
82+ run : nebari deploy --config ${{ steps.init.outputs.config }} --disable-prompt
83+
84+ - name : Health check
85+ uses : ./.github/actions/health-check
86+ with :
87+ domain : ${{ steps.init.outputs.domain }}
88+
89+ - name : Create preview users
90+ working-directory : ${{ steps.init.outputs.directory }}
91+ run : |
92+ nebari keycloak add-user --user "${TEST_USERNAME}" -p "${TEST_PASSWORD}" --config ${{ steps.init.outputs.config }}
93+ nebari keycloak add-user --user "${TEST_SUPERADMIN_USER}" -p "${TEST_SUPERADMIN_PASSWORD}" --config ${{ steps.init.outputs.config }} --groups superadmin
94+ nebari keycloak list-users --config ${{ steps.init.outputs.config }}
95+
96+ - name : Await Workloads
97+ uses : jupyterhub/action-k8s-await-workloads@v3
98+ with :
99+ workloads : " " # all
100+ namespace : " dev"
101+ timeout : 300
102+ max-restarts : 3
103+
104+ - name : Setup FRP Tunnel
105+ uses : cirunlabs/frp-tunnel-action@main
106+ with :
107+ timeout_minutes : 0
108+ frp_client_config : |
109+ serverAddr = "frp.nebari.dev"
110+ serverPort = 7000
111+ auth.method = "token"
112+ auth.token = "${{ secrets.FRP_TOKEN }}"
113+
114+ [[proxies]]
115+ name = "nebari-ssh--${{ github.run_id }}-${{ github.sha }}"
116+ type = "tcpmux"
117+ multiplexer = "httpconnect"
118+ localIP = "127.0.0.1"
119+ localPort = 22
120+ customDomains = ["${{ env.SSH_DNS }}"]
121+
122+ [[proxies]]
123+ name = "nebari-http--${{ github.run_id }}-${{ github.sha }}"
124+ type = "https"
125+ localIP = "127.0.0.1"
126+ localPort = 443
127+ customDomains = ["${{ env.APP_DNS }}"]
128+
129+ - name : Comment on PR with deployment info
130+ uses : actions/github-script@v7
131+ with :
132+ script : |
133+ const { data: comments } = await github.rest.issues.listComments({
134+ owner: context.repo.owner,
135+ repo: context.repo.repo,
136+ issue_number: ${{ github.event.pull_request.number }}
137+ });
138+
139+ const existingComment = comments.find(comment =>
140+ comment.body.includes('🚀 Deploy Preview') && comment.user.login === 'github-actions[bot]'
141+ );
142+
143+ const body = `## 🚀 Deploy Preview Ready!
144+
145+ Your Nebari preview deployment is now available:
146+
147+ **🌐 Preview URL:** https://${{ env.APP_DNS }}
148+
149+ **👤 Test Credentials:**
150+ - **User:** \`${{ env.TEST_USERNAME }}\` / \`${{ env.TEST_PASSWORD }}\`
151+ - **Admin:** \`${{ env.TEST_SUPERADMIN_USER }}\` / \`${{ env.TEST_SUPERADMIN_PASSWORD }}\`
152+
153+ **🔗 SSH Access:**
154+ \`\`\`bash
155+ ssh ubuntu@${{ env.SSH_DNS }}
156+ \`\`\`
157+
158+ **🔄 Auto-cleanup:** This preview will be automatically cleaned up when the \`deploy-preview\` label is removed or the runner terminates.
159+
160+ ---
161+ _This preview was generated from commit ${context.sha.slice(0, 7)}_`;
162+
163+ if (existingComment) {
164+ await github.rest.issues.updateComment({
165+ owner: context.repo.owner,
166+ repo: context.repo.repo,
167+ comment_id: existingComment.id,
168+ body: body
169+ });
170+ } else {
171+ await github.rest.issues.createComment({
172+ owner: context.repo.owner,
173+ repo: context.repo.repo,
174+ issue_number: ${{ github.event.pull_request.number }},
175+ body: body
176+ });
177+ }
178+
179+ cleanup-preview :
180+ if : github.event.action == 'unlabeled' && github.event.label.name == 'deploy-preview'
181+ runs-on : ubuntu-latest
182+ steps :
183+ - name : Comment on PR about cleanup
184+ uses : actions/github-script@v7
185+ with :
186+ script : |
187+ const { data: comments } = await github.rest.issues.listComments({
188+ owner: context.repo.owner,
189+ repo: context.repo.repo,
190+ issue_number: ${{ github.event.pull_request.number }}
191+ });
192+
193+ const existingComment = comments.find(comment =>
194+ comment.body.includes('🚀 Deploy Preview') && comment.user.login === 'github-actions[bot]'
195+ );
196+
197+ const body = `## 🧹 Deploy Preview Cleaned Up
198+
199+ The preview deployment for PR #${{ github.event.pull_request.number }} has been cleaned up.
200+
201+ To create a new preview, add the \`deploy-preview\` label again.`;
202+
203+ if (existingComment) {
204+ await github.rest.issues.updateComment({
205+ owner: context.repo.owner,
206+ repo: context.repo.repo,
207+ comment_id: existingComment.id,
208+ body: body
209+ });
210+ } else {
211+ await github.rest.issues.createComment({
212+ owner: context.repo.owner,
213+ repo: context.repo.repo,
214+ issue_number: ${{ github.event.pull_request.number }},
215+ body: body
216+ });
217+ }
0 commit comments