Skip to content

Commit 13d769e

Browse files
authored
[CERT-9836] Replace active reviews (#73)
* Replace active reviews * Use argjson * Use variables * Use conf file * [CERT-9814] Certora API calls (#74) * Cancel all jobs * Add server * Fix script path * Export domain * Add README * Add error log
1 parent 4ae686f commit 13d769e

File tree

7 files changed

+221
-12
lines changed

7 files changed

+221
-12
lines changed

.github/workflows/certora_api.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Certora API
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
command:
7+
required: true
8+
description: |
9+
The Certora API command to execute. Options are:
10+
- `cancel <group_id>`: Cancel all jobs in the specified group.
11+
- `refresh <group_id>`: Refresh the status of all jobs in the specified group.
12+
server:
13+
required: true
14+
type: choice
15+
description: |
16+
The Certora server to use. Either production, staging, or vaas-dev.
17+
Default is production.
18+
default: production
19+
options:
20+
- production
21+
- staging
22+
- vaas-dev
23+
24+
permissions:
25+
contents: read
26+
id-token: write
27+
28+
jobs:
29+
certora_api_command:
30+
runs-on: ubuntu-latest
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: ./api/
34+
with:
35+
certora-command: ${{ github.event.inputs.command }}
36+
server: ${{ github.event.inputs.server }}

README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,10 +236,57 @@ You typically need:
236236
- **A Cargo lockfile.**
237237
If you do not want to commit it in your repository, you can generate it from the action
238238
by running, for example:
239+
239240
```sh
240241
cargo update -p cvlr-soroban
241242
```
242243

244+
## Certora API Integration
245+
246+
You can also use this action to call Certora API commands such as cancelling or
247+
refreshing jobs in a group. To do so, you can create a workflow like this:
248+
249+
```yml
250+
name: Certora API
251+
on:
252+
workflow_dispatch:
253+
inputs:
254+
command:
255+
required: true
256+
description: |
257+
The Certora API command to execute. Options are:
258+
- `cancel <group_id>`: Cancel all jobs in the specified group.
259+
- `refresh <group_id>`: Refresh the status of all jobs in the specified group.
260+
server:
261+
required: true
262+
type: choice
263+
description: |
264+
The Certora server to use. Either production, staging, or vaas-dev.
265+
Default is production.
266+
default: production
267+
options:
268+
- production
269+
- staging
270+
- vaas-dev
271+
272+
permissions:
273+
contents: read
274+
id-token: write
275+
276+
jobs:
277+
certora_api_command:
278+
runs-on: ubuntu-latest
279+
steps:
280+
- uses: actions/checkout@v4
281+
- uses: Certora/certora-run-action/api@v2
282+
with:
283+
certora-command: ${{ github.event.inputs.command }}
284+
server: ${{ github.event.inputs.server }}
285+
```
286+
287+
Then, you can trigger this workflow manually from the GitHub Actions UI, providing the
288+
command and the server as inputs.
289+
243290
### Comments on the Pull Request
244291
245292
First, it will add a comment with details about runs:

action.yml

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ inputs:
9999
default: "true"
100100
description: |-
101101
Add a comment to the PR only if the run fails.
102+
replace-comments:
103+
default: "true"
104+
description: |-
105+
Whether to replace previous review comments made by this action. Default is `true`.
102106
debug-level:
103107
default: "0"
104108
description: |-
@@ -145,6 +149,7 @@ inputs:
145149
description: |-
146150
The version of Vyper to install. Can be latest, or a specific version like 0.3.3.
147151
If not specified, Vyper will not be installed.
152+
148153
runs:
149154
using: "composite"
150155
steps:
@@ -232,14 +237,25 @@ runs:
232237
CERTORA_API_SUBDOMAIN="data-api-stg"
233238
fi
234239
235-
echo "solc_cache_key=$(md5sum <<< '${{ inputs.solc-versions }}' | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
240+
echo "solc_cache_key=$(md5sum <<< "'${{ inputs.solc-versions }}'" | awk '{ print $1 }')" >> "$GITHUB_OUTPUT"
241+
242+
# Remove leading spaces, trailing spaces, comments, and empty lines
243+
CERTORA_CONFIGURATIONS_FILE="/tmp/certora-logs/REPORT-${GROUP_ID}-configurations"
244+
sed -r 's/^\s+//; s/\s+$//; /^[[:blank:]]*#/d; s/^#.*//; /^\s*$/d' <<< "$CERTORA_RAW_CONFIGURATIONS" | sort -u > "$CERTORA_CONFIGURATIONS_FILE"
245+
246+
CERTORA_CONFIGURATIONS_HASH="$(md5sum < "$CERTORA_CONFIGURATIONS_FILE" | awk '{ print $1 }')"
236247
237248
echo "CERTORA_ACTION_REF=$CERTORA_ACTION_REF" >> "$GITHUB_ENV"
238249
echo "CERTORA_SUBDOMAIN=$CERTORA_SUBDOMAIN" >> "$GITHUB_ENV"
239250
echo "certora_subdomain=$CERTORA_SUBDOMAIN" >> "$GITHUB_OUTPUT"
240251
echo "CERTORA_API_SUBDOMAIN=$CERTORA_API_SUBDOMAIN" >> "$GITHUB_ENV"
252+
echo "CERTORA_CONFIGURATIONS_FILE=$CERTORA_CONFIGURATIONS_FILE" >> "$GITHUB_ENV"
253+
echo "CERTORA_CONFIGURATIONS_HASH=$CERTORA_CONFIGURATIONS_HASH" >> "$GITHUB_ENV"
254+
echo "certora_configurations_file=$CERTORA_CONFIGURATIONS_FILE" >> "$GITHUB_OUTPUT"
255+
echo "certora_configurations_hash=$CERTORA_CONFIGURATIONS_HASH" >> "$GITHUB_OUTPUT"
241256
env:
242257
CERTORA_ACTION_REF: ${{ github.action_ref }}
258+
CERTORA_RAW_CONFIGURATIONS: "${{ inputs.configurations }}"
243259

244260
- name: Install uv
245261
uses: astral-sh/setup-uv@v7
@@ -337,14 +353,14 @@ runs:
337353
bash "$RUN_CERTORA"
338354
fi
339355
env:
340-
CERTORA_CONFIGURATIONS: "${{ inputs.configurations }}"
341356
CERTORA_SERVER: "${{ inputs.server }}"
342357
CERTORAKEY: "${{ inputs.certora-key }}"
343358
CERTORA_JOB_NAME: "${{ inputs.job-name }}"
344359
CERTORA_COMPILATION_STEPS_ONLY: "${{ inputs.compilation-steps-only }}"
345360
CERTORA_ECOSYSTEM: "${{ inputs.ecosystem }}"
346361
DEBUG_LEVEL: "${{ inputs.debug-level }}"
347362
CERTORA_USE_HARD_LINKS: "${{ inputs.use-hard-links }}"
363+
CERTORA_REPLACE_COMMENTS: "${{ inputs.replace-comments }}"
348364

349365
- name: Add GH Status
350366
if: ${{ steps.certora-run.outputs.total_jobs > 0}}
@@ -381,5 +397,5 @@ runs:
381397
if: ${{ (failure() || inputs.comment-fail-only == 'false') && (steps.certora-run.outputs.total_jobs > 0 || steps.certora-run.outputs.failed_jobs > 0) }}
382398
uses: mshick/add-pr-comment@v2
383399
with:
384-
message-id: ${{ steps.setup-env.outputs.group_id }}
400+
message-id: ${{ inputs.replace-comments == 'true' && steps.setup-env.outputs.certora_configurations_hash || steps.setup-env.outputs.group_id }}
385401
message-path: ${{ steps.setup-env.outputs.report_file }}

api/action.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# yaml-language-server: $schema=https://raw.githubusercontent.com/SchemaStore/schemastore/refs/heads/master/src/schemas/json/github-action.json
2+
name: Certora API
3+
4+
description: |-
5+
GitHub Action to call Certora API to cancel or refresh a job group.
6+
This action requires that the Certora GitHub App is installed on the repository.
7+
8+
branding:
9+
color: blue
10+
icon: cloud-lightning
11+
12+
inputs:
13+
certora-command:
14+
required: true
15+
description: |-
16+
Command to send to the Certora API. It should be in the format
17+
"<command> <group_id>", where <command> is either "cancel" or "refresh",
18+
and <group_id> is the identifier of a job group to which the command applies.
19+
server:
20+
required: false
21+
description: |-
22+
The Certora server to use. Can be "production" (default). "vaas-dev" or "vaas-stg".
23+
default: "production"
24+
debug-level:
25+
default: "0"
26+
description: |-
27+
The debug level to use for the action command. Default is `0`.
28+
Options: `0`, `1`, `2`, or `3`.
29+
30+
runs:
31+
using: "composite"
32+
steps:
33+
- name: Call Certora API
34+
shell: bash
35+
run: |
36+
RUN_ID="$(cat /proc/sys/kernel/random/uuid)"
37+
CERTORA_LOG_DIR="/tmp/certora-logs/$RUN_ID"
38+
CERTORA_API_SUBDOMAIN="data-api"
39+
if [[ "${{ inputs.server }}" == "vaas-dev" || "${{ inputs.server }}" == "development" ]]; then
40+
CERTORA_API_SUBDOMAIN="data-api-dev"
41+
elif [[ "${{ inputs.server }}" == "staging" || "${{ inputs.server }}" == "vaas-stg" ]]; then
42+
CERTORA_API_SUBDOMAIN="data-api-stg"
43+
fi
44+
export CERTORA_API_SUBDOMAIN
45+
export CERTORA_LOG_DIR
46+
bash "${{ github.action_path }}/../scripts/call-api.sh"
47+
env:
48+
CERTORA_COMMAND: ${{ inputs.certora-command }}
49+
DEBUG_LEVEL: "${{ inputs.debug-level }}"

scripts/call-api.sh

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/bin/bash
2+
3+
set -euo pipefail
4+
5+
if [ "${DEBUG_LEVEL:-0}" -gt 0 ]; then
6+
set -x
7+
fi
8+
9+
# Validate required variables
10+
required_vars=(
11+
CERTORA_COMMAND
12+
CERTORA_API_SUBDOMAIN
13+
CERTORA_LOG_DIR
14+
ACTIONS_ID_TOKEN_REQUEST_TOKEN
15+
ACTIONS_ID_TOKEN_REQUEST_URL
16+
)
17+
18+
missing_args=false
19+
20+
for var in "${required_vars[@]}"; do
21+
if [ -z "${!var:-}" ]; then
22+
echo "::error title=Missing variable::$var is required but not set"
23+
missing_args=true
24+
fi
25+
done
26+
27+
if [ "$missing_args" = true ]; then
28+
exit 1
29+
fi
30+
31+
endpoint=""
32+
group_id=""
33+
34+
read -r endpoint group_id <<< "$CERTORA_COMMAND"
35+
36+
# Fetch OIDC token
37+
TOKEN="$(curl -sSfL --retry 3 --max-time 30 -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r .value)"
38+
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
39+
echo "::error title=Token Retrieval Failed::Could not fetch GitHub OIDC token."
40+
exit 1
41+
fi
42+
43+
GHINT_LOG="$CERTORA_LOG_DIR/gh-call.json"
44+
45+
CERT_GH_APP_LINK='https://github.com/apps/certora-run'
46+
CERT_GH_ACTION_LINK='https://github.com/Certora/certora-run-action'
47+
ERROR_MSG="There was an issue executing the API call (Please have a look at $CERT_GH_APP_LINK, $CERT_GH_ACTION_LINK)."
48+
49+
# Make API request to verify GitHub App integration
50+
curl -sSL --proto '=https' --tlsv1.2 --retry 10 --max-time 60 --retry-connrefused --fail-with-body -X POST "https://$CERTORA_API_SUBDOMAIN.certora.com/v1/github-app/$endpoint?groupId=$group_id" \
51+
-H "Authorization: Bearer $TOKEN" \
52+
-H "Content-Type: application/json" >"$GHINT_LOG" || {
53+
echo "::error title=Certora GitHub Application Integration Missing::$(jq -r '"Error \(.status_code): \(.detail)"' "$GHINT_LOG") - $ERROR_MSG"
54+
cat "$GHINT_LOG"
55+
exit 1
56+
}

scripts/gh-app-integration.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ required_vars=(
1616
ACTIONS_ID_TOKEN_REQUEST_TOKEN
1717
ACTIONS_ID_TOKEN_REQUEST_URL
1818
GITHUB_EVENT_PATH
19+
CERTORA_CONFIGURATIONS_HASH
20+
CERTORA_REPLACE_COMMENTS
1921
)
2022

2123
missing_args=false
@@ -52,8 +54,10 @@ PAYLOAD=$(jq -n \
5254
--arg group_id "$GROUP_ID" \
5355
--arg commit "$COMMIT_SHA" \
5456
--arg action_ref "$CERTORA_ACTION_REF" \
57+
--arg config_hash "$CERTORA_CONFIGURATIONS_HASH" \
58+
--argjson replace_comments "$CERTORA_REPLACE_COMMENTS" \
5559
--argjson pr_number "${PR_NUMBER:-null}" \
56-
'{group_id: $group_id, commit: $commit, action_ref: $action_ref, pr_number: $pr_number}')
60+
'{group_id: $group_id, commit: $commit, action_ref: $action_ref, config_hash: $config_hash, replace_comments: $replace_comments, pr_number: $pr_number}')
5761

5862
# Make API request to verify GitHub App integration
5963
curl -sSL --proto '=https' --tlsv1.2 --retry 10 --max-time 60 --retry-connrefused --fail-with-body -X POST "https://$CERTORA_API_SUBDOMAIN.certora.com/v1/github-app/verify" \

scripts/run-certora.sh

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@ pids=()
1313
configs=()
1414
logs=()
1515

16-
# Remove leading spaces, trailing spaces, comments, and empty lines
17-
CERTORA_CONFIGURATIONS="$(sed -r 's/^\s+//; s/\s+$//; /^[[:blank:]]*#/d; s/^#.*//; /^\s*$/d' <<<"$CERTORA_CONFIGURATIONS")"
1816

19-
IFS=$'\n' read -rd '' -a confs <<<"$(echo "$CERTORA_CONFIGURATIONS" | sort -u)"
17+
# Read configurations from file specified by $CERTORA_CONFIGURATIONS_FILE
18+
if [[ -z "$CERTORA_CONFIGURATIONS_FILE" ]]; then
19+
echo "::error title=Missing Configurations File::CERTORA_CONFIGURATIONS_FILE is not set."
20+
exit 1
21+
fi
22+
23+
IFS=$'\n' read -rd '' -a confs < "$CERTORA_CONFIGURATIONS_FILE"
2024

2125
echo "Configurations: ${confs[*]}"
2226

2327
if [[ ${#confs[@]} -gt 1 ]]; then
2428
# Extract the common prefix from the configurations
25-
# Sed script to extract the common prefix
26-
# For the first line, copy pattern space to hold space and delete the pattern space
27-
# Append a newline and the hold space to the pattern space, capture the common prefix
28-
# Copy the pattern space to the hold space and delete the pattern space until the last line
29-
common_prefix="$(echo "$CERTORA_CONFIGURATIONS" | sed -e '1{h;d;}' -e 'G;s,\(.*\).*\n\1.*,\1,;s,\(.*[/ ]\).*$,\1,;h;$!d' | tr -d '\n')"
29+
# Use the contents of the file for prefix extraction
30+
common_prefix="$(sed -e '1{h;d;}' -e 'G;s,\(.*\).*\n\1.*,\1,;s,\(.*[/ ]\).*$,\1,;h;$!d' "$CERTORA_CONFIGURATIONS_FILE" | tr -d '\n')"
3031
elif [[ "${confs[0]}" == */* ]]; then
3132
# Keep the file name only
3233
common_prefix="$(echo "${confs[0]}" | sed 's/\(.*\/\)[^\/]*$/\1/')"

0 commit comments

Comments
 (0)