-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaction.yml
423 lines (351 loc) · 13.7 KB
/
action.yml
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
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
name: 'PyPSA Validator Bot'
description: 'PyPSA Validator Bot'
autor: 'lkstrp'
inputs:
step:
description: 'Step to run'
required: true
type: choice
options:
- 'run-self-hosted-validation'
- 'create-comment'
# Needed for 'run-self-hosted-validation' step
env_file:
description: 'File for conda/mamba environment'
type: string
snakemake_config:
description: 'Snakemake config file'
type: string
pre_command:
description: 'Pre-command to run before validation'
type: string
main_command:
description: 'Main command to run for validation'
type: string
# Needed for 'create-comment' step
plots:
description: 'Plots to be shown in comment'
validator_key:
description: 'Private ssh key to access the validator repo'
type: string
dev:
description: 'Run in development mode'
type: boolean
default: false
# Reminder
# - ${{ github.repository }} -> 'user/repo'
# - ${{ github.event.repository.name }} -> 'repo'
runs:
using: "composite"
steps:
# ----------------------------------------
# For all steps
# ----------------------------------------
- name: General setup and checks
run: |
# Check if from fork
if [ "${{ github.repository }}" != "${{ github.event.pull_request.head.repo.full_name }}" ]; then
echo "Running from fork."
fork=true
exit 1 # Not supported yet
else
echo "Running from main repository."
fork=false
fi
# Assert Github Token is set
if [ -z "${{ github.token }}" ]; then
echo "Error: Github Token Secret not found"
exit 1
fi
# Bot config
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "github-actions[bot]"
shell: bash
# ----------------------------------------
# 'run-self-hosted-validation' step
# ----------------------------------------
- name: Run self-hosted validation
if: ${{ inputs.step == 'run-self-hosted-validation' }}
run: |
echo "Running self-hosted validation..."
# Check for dev mode
USERNAME=$(whoami)
if [ "${{ inputs.dev }}" = true ]; then
echo "Development mode activated."
command_prefix="/home/$USERNAME/runner-scripts/run-dev.sh"
else
command_prefix="/home/$USERNAME/runner-scripts/run.sh"
fi
# Fetch hash of main branch
_hash_main=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/${{ github.repository }}/commits/${{ github.base_ref }}" | jq -r '.sha')
# Fetch hash of feature branch (via PR number, also works for branches from forks)
pr_details=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}")
# Extract the repository and ref (branch name)
source_repo=$(echo "$pr_details" | jq -r '.head.repo.full_name')
branch_name=$(echo "$pr_details" | jq -r '.head.ref')
# Get the latest commit hash from the source repository
_hash_feature=$(curl -s -H "Authorization: token ${{ github.token }}" \
"https://api.github.com/repos/$source_repo/commits/$branch_name" | jq -r '.sha')
# TODO Needs checks for empty or null hashes
echo "Main branch (${{ github.base_ref }}) hash: $_hash_main"
echo "Feature branch (${{ github.head_ref }}) hash: $_hash_feature"
echo "${{ github.event.pull_request.head.repo.full_name }}"
# Execute the command with the common arguments
$command_prefix \
--repo ${{ github.repository }} \
--branch_main ${{ github.base_ref }} \
--branch_feature "pr/${{ github.event.number }}" \
--hash_main $_hash_main \
--hash_feature $_hash_feature \
--env_file ${{ inputs.env_file }} \
--config_file ${{ inputs.snakemake_config }} \
--main_command "${{ inputs.main_command }}" \
--pre_command "${{ inputs.pre_command }}"
shell: bash
- name: Upload artifacts (logs)
if: ${{ inputs.step == 'run-self-hosted-validation' }}
uses: actions/upload-artifact@v4
with:
name: logs
path: |
~/${{ github.repository }}/validator-metadata.yml
~/${{ github.repository }}/main/logs/
~/${{ github.repository }}/main/.snakemake/
~/${{ github.repository }}/feature/logs/
~/${{ github.repository }}/feature/.snakemake/
if-no-files-found: error
retention-days: 90
include-hidden-files: true
- name: Upload artifacts (benchmarks)
if: ${{ inputs.step == 'run-self-hosted-validation' }}
uses: actions/upload-artifact@v4
with:
name: benchmarks
path: |
~/${{ github.repository }}/main/benchmarks/
~/${{ github.repository }}/feature/benchmarks/
retention-days: 90
include-hidden-files: true
- name: Upload artifacts (results)
if: ${{ inputs.step == 'run-self-hosted-validation' }}
uses: actions/upload-artifact@v4
with:
name: results
path: |
~/${{ github.repository }}/main/results
~/${{ github.repository }}/feature/results
retention-days: 90
include-hidden-files: true
# ----------------------------------------
# 'create-comment' step
# ----------------------------------------
- name: Create comment specific setup and checks
if: ${{ inputs.step == 'create-comment' }}
run: |
# Assert validator key is set
if [ -z ${{ inputs.validator_key }} ]; then
echo "Error: input 'validator_key' not set"
exit 1
fi
shell: bash
- name: Download artifacts
if: ${{ inputs.step == 'create-comment' }}
uses: actions/download-artifact@v4
- name: Move artifacts to home directory
if: ${{ inputs.step == 'create-comment' }}
run: |
# Move artifacts to home directory
mkdir $HOME/artifacts
mv ./* $HOME/artifacts
shell: bash
- name: Get variables from artifacts
if: ${{ inputs.step == 'create-comment' }}
run: |
# Get compared hashes
hash_main=$(yq e '.parameters.hash_main' $HOME/artifacts/logs/validator-metadata.yml)
echo "HASH_MAIN=$hash_main" >> $GITHUB_ENV
hash_feature=$(yq e '.parameters.hash_feature' $HOME/artifacts/logs/validator-metadata.yml)
echo "HASH_FEATURE=$hash_feature" >> $GITHUB_ENV
echo "Main commit hash: ${hash_main}"
echo "Feature commit hash: ${hash_feature}"
# Assert variables found
if [[ -z $hash_main || -z $hash_feature ]]; then
echo "Error: Commit hashes not found"
exit 1
fi
# Get config prefix
prefix_main=$(yq e '.metadata.prefix_main' $HOME/artifacts/logs/validator-metadata.yml)
echo "PREFIX_MAIN=$prefix_main" >> $GITHUB_ENV
prefix_feature=$(yq e '.metadata.prefix_feature' $HOME/artifacts/logs/validator-metadata.yml)
echo "PREFIX_FEATURE=$prefix_feature" >> $GITHUB_ENV
echo "Main config prefix: ${prefix_main}"
echo "Feature config prefix: ${prefix_feature}"
# Assert variables found
if [[ -z $prefix_main || -z $prefix_feature ]]; then
echo "Error: Config prefixes not found"
exit 1
fi
shell: bash
- name: Checkout model repo
if: ${{ inputs.step == 'create-comment' }}
uses: actions/checkout@v4
with:
fetch-depth: 0
path: model-repo
- name: Get variables from repo
if: ${{ inputs.step == 'create-comment' }}
id: env-variables-repo
run: |
cd $GITHUB_WORKSPACE/model-repo
# Get git diff of used snakemake config
git_diff_config=$(git diff $HASH_MAIN $HASH_FEATURE -- ${{ inputs.snakemake_config }})
# Write to env: git diff config
echo "GIT_DIFF_CONFIG<<EOF" >> $GITHUB_ENV
echo "${git_diff_config}" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo "Git diff config:\n${git_diff_config}"
# Get the number of commits ahead of main
behind_count=$(git rev-list --right-only --count $HASH_FEATURE...$HASH_MAIN)
echo "BEHIND_COUNT=$behind_count" >> $GITHUB_ENV
ahead_count=$(git rev-list --left-only --count $HASH_FEATURE...$HASH_MAIN)
echo "AHEAD_COUNT=$ahead_count" >> $GITHUB_ENV
echo "Behind count: $behind_count"
echo "Ahead count: $ahead_count"
shell: bash
- name: Checkout validator repo
if: ${{ inputs.step == 'create-comment' }}
uses: actions/checkout@v4
with:
repository: lkstrp/pypsa-validator
ref: ${{ github.repository }}
ssh-key: ${{ inputs.validator_key }}
path: validator-repo
- name: Setup environment and retrieve scripts
if: ${{ inputs.step == 'create-comment' }}
run: |
cd $GITHUB_WORKSPACE/validator-repo
# Get potential changes from main branch
git fetch origin
if [ "${{ inputs.dev }}" = true ]; then
echo "Merging from dev instead of main."
git merge origin/dev --allow-unrelated-histories -X theirs
else
git merge origin/main --allow-unrelated-histories -X theirs
fi
# Install requirements
pip install -r requirements.txt
shell: bash
- name: Upload relevant plots
id: upload-plots
if: ${{ inputs.step == 'create-comment' }}
run: |
cd $GITHUB_WORKSPACE/validator-repo
# Get static plot list (from input)
read -a plots_array_input <<< ${{ inputs.plots }}
# Get dynamic plot list (from comment script)
read -a plots_array_dynamic <<< "$(python src/draft_comment.py plots)"
plots_array=("${plots_array_input[@]}" "${plots_array_dynamic[@]}")
# Empty directory
rm -rf _validation-images
mkdir -p _validation-images/feature
mkdir -p _validation-images/main
# Copy plots
for plotpath in "${plots_array[@]}"
do
subpath="${plotpath%/*}"
plot="${plotpath##*/}"
echo "Copying plot: ${plot} from path: ${subpath}"
# Create directories
mkdir -p "_validation-images/main/${subpath}"
mkdir -p "_validation-images/feature/${subpath}"
cp "$HOME/artifacts/results/main/results/${PREFIX_MAIN}/${subpath}/${plot}" "_validation-images/main/${subpath}/" || true # ignore if run failed
cp "$HOME/artifacts/results/feature/results/${PREFIX_FEATURE}/${subpath}/${plot}" "_validation-images/feature/${subpath}/" || true # ignore if run failed
done
# Get benchmark plot list (from benchmark script)
read -a plots_array_benchmark <<< "$(python src/plot_benchmarks.py)"
mkdir -p _validation-images/benchmarks
# Copy benchmark plots
for plot in "${plots_array_benchmark[@]}"
do
echo "Copying benchmark plot: ${plot}
# Create directories
mkdir -p "_validation-images/benchmarks
cp "${plot}" "_validation-images/benchmarks" || true # ignore if run failed
cp "${plot}" "_validation-images/benchmarks" || true # ignore if run failed
done
# Add plots to repo branch
echo "Adding plots to repo branch"
git add _validation-images
git commit -m "[github-actions.ci] upload validation images to show in comment" || true # ignore if no changes
git push origin ${{ github.repository }}
echo "COMMIT_ID=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
shell: bash
- name: Setup env variables for comment
if: ${{ inputs.step == 'create-comment' }}
run: |
# This needs to be done in a separate step to ensure the output is available
echo "PLOTS_HASH=${{ steps.upload-plots.outputs.COMMIT_ID }}" >> $GITHUB_ENV
echo "PLOTS=${{ inputs.plots }}" >> $GITHUB_ENV
shell: bash
- name: Create Validator Report
if: ${{ inputs.step == 'create-comment' }}
run: |
cd $GITHUB_WORKSPACE/validator-repo
# Create comment
# Note: The script uses many env variables. See the script for more details.
python src/draft_comment.py > $GITHUB_WORKSPACE/comment.txt
cat $GITHUB_WORKSPACE/comment.txt >> $GITHUB_STEP_SUMMARY
shell: bash
- name: Retrieve or create comment
if: ${{ inputs.step == 'create-comment' }}
id: comment
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue_number = context.issue.number;
// Fetching all comments of pr
const comments = await github.rest.issues.listComments({
owner,
repo,
issue_number
});
// Distinctive keyword present in bot's comments
const distinctiveKeyword = '_val-bot-id-keyword_';
// Searching for existing comment with the distinctive keyword
let botsComment;
for (let { user, id, body } of comments.data.reverse()) {
if (body.includes(distinctiveKeyword)) {
botsComment = { id, body };
break;
}
}
// If found return its id
if (botsComment) {
return botsComment.id;
} else {
// Creating a new comment if it doesn't exist yet and returning its ID.
const { data: newComment } = await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: `<!-- ${distinctiveKeyword} --> Initializing comment...`
});
return newComment.id;
}
- name: Update comment
if: ${{ inputs.step == 'create-comment' }}
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const comment = fs.readFileSync(`${process.env.GITHUB_WORKSPACE}/comment.txt`, 'utf8');
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{ steps.comment.outputs.result }},
body: comment
});