Skip to content

Commit 9e1624d

Browse files
authored
feat: add shared workflows (#142)
1 parent 8729fe8 commit 9e1624d

10 files changed

+900
-5
lines changed

.github/workflows/security.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
toolchain: stable
4949

5050
- name: Restore cache
51-
uses: actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
51+
uses: actions/cache/restore@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
5252
with:
5353
path: |
5454
~/.cargo/bin/
@@ -67,7 +67,7 @@ jobs:
6767
run: cargo audit
6868

6969
- name: Save cache
70-
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
70+
uses: actions/cache/save@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
7171
if: always()
7272
with:
7373
path: |
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: check-pr-label-and-branch
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
labels:
7+
required: false
8+
type: string
9+
description: "JSON string of labels from the pull request"
10+
default: ${{ toJSON(github.event.pull_request.labels) }}
11+
target_label:
12+
required: true
13+
type: string
14+
description: "Target label to check for in the PR"
15+
branch_prefix:
16+
required: false
17+
type: string
18+
description: "Required prefix for the branch name"
19+
default: ""
20+
outputs:
21+
is_valid:
22+
value: ${{ jobs.check-pr-label-and-branch.outputs.is_valid }}
23+
has_label:
24+
value: ${{ jobs.check-pr-label-and-branch.outputs.has_label }}
25+
is_valid_branch:
26+
value: ${{ jobs.check-pr-label-and-branch.outputs.is_valid_branch }}
27+
28+
jobs:
29+
check-pr-label-and-branch:
30+
name: check-pr-label-and-branch
31+
runs-on: ubuntu-latest
32+
outputs:
33+
is_valid: ${{ steps.check.outputs.is_valid }}
34+
has_label: ${{ steps.check.outputs.has_label }}
35+
is_valid_branch: ${{ steps.check.outputs.is_valid_branch }}
36+
steps:
37+
- name: Check for label and branch prefix
38+
id: check
39+
run: |
40+
TARGET_LABEL="${{ inputs.target_label }}"
41+
BRANCH_PREFIX="${{ inputs.branch_prefix }}"
42+
CURRENT_BRANCH="${{ github.head_ref }}"
43+
44+
echo "Checking for label: $TARGET_LABEL"
45+
echo "Required branch prefix: $BRANCH_PREFIX"
46+
echo "Current branch: $CURRENT_BRANCH"
47+
echo "Input labels: ${{ inputs.labels }}"
48+
49+
# Check if branch has the required prefix
50+
if [ -n "$BRANCH_PREFIX" ]; then
51+
if [[ "$CURRENT_BRANCH" == "$BRANCH_PREFIX"* ]]; then
52+
echo "✅ Branch prefix matches"
53+
HAS_BRANCH_PREFIX=true
54+
else
55+
echo "❌ Branch prefix does not match"
56+
HAS_BRANCH_PREFIX=false
57+
fi
58+
else
59+
echo "ℹ️ No branch prefix requirement"
60+
HAS_BRANCH_PREFIX=true
61+
fi
62+
63+
# Use jq to check if the label exists in the labels array
64+
HAS_LABEL=$(echo '${{ inputs.labels }}' | \
65+
jq --arg target "$TARGET_LABEL" \
66+
'[.[] | select(.name == $target)] | length > 0')
67+
68+
echo "Has label: $HAS_LABEL"
69+
70+
# Output individual condition results
71+
echo "has_label=$HAS_LABEL" >> $GITHUB_OUTPUT
72+
echo "is_valid_branch=$HAS_BRANCH_PREFIX" >> $GITHUB_OUTPUT
73+
74+
# Both conditions must be true
75+
if [ "$HAS_LABEL" = "true" ] && [ "$HAS_BRANCH_PREFIX" = "true" ]; then
76+
echo "is_valid=true" >> $GITHUB_OUTPUT
77+
echo "✅ Both label '$TARGET_LABEL' and branch prefix '$BRANCH_PREFIX' found"
78+
else
79+
echo "is_valid=false" >> $GITHUB_OUTPUT
80+
if [ "$HAS_LABEL" != "true" ]; then
81+
echo "❌ Label '$TARGET_LABEL' not found"
82+
else
83+
echo "✅ Label '$TARGET_LABEL' found"
84+
fi
85+
if [ "$HAS_BRANCH_PREFIX" != "true" ]; then
86+
echo "❌ Branch prefix '$BRANCH_PREFIX' not found"
87+
else
88+
echo "✅ Branch prefix '$BRANCH_PREFIX' found"
89+
fi
90+
fi
91+
92+
- name: Display Validation Result
93+
run: |
94+
echo "Validation result: ${{ steps.check.outputs.is_valid }}"
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: check-pr-labels-with-prefix
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
labels:
7+
description: "JSON string of labels from the pull request"
8+
required: false
9+
type: string
10+
default: ${{ toJSON(github.event.pull_request.labels) }}
11+
prefix:
12+
description: 'Label prefix to filter (e.g., "deploy-", "test-", "run-")'
13+
required: false
14+
default: ""
15+
type: string
16+
outputs:
17+
labels:
18+
description: "JSON object with labels matching the prefix"
19+
value: ${{ jobs.check-pr-labels-with-prefix.outputs.labels }}
20+
21+
jobs:
22+
check-pr-labels-with-prefix:
23+
runs-on: ubuntu-latest
24+
# Example outputs:
25+
# - With prefix "deploy-": {"deploy-d": {full label object}, "deploy-n1": {full label object}}
26+
outputs:
27+
labels: ${{ steps.filter.outputs.filtered-labels }}
28+
steps:
29+
- name: Filter labels by prefix
30+
id: filter
31+
run: |
32+
PREFIX="${{ inputs.prefix }}"
33+
34+
# Use jq to filter labels by prefix (empty prefix returns all labels)
35+
JSON_RESULT=$(echo '${{ inputs.labels }}' | \
36+
jq --arg prefix "$PREFIX" \
37+
'map(select(.name | startswith($prefix))) |
38+
map({(.name): .}) |
39+
reduce .[] as $item ({}; . + $item)')
40+
41+
# Ensure JSON_RESULT is never empty - default to {} if empty
42+
if [ -z "$JSON_RESULT" ] || [ "$JSON_RESULT" = "null" ]; then
43+
JSON_RESULT="{}"
44+
fi
45+
46+
# Use <<< to pass the JSON as a single line to avoid file command issues
47+
{
48+
echo "filtered-labels<<EOF"
49+
echo "$JSON_RESULT"
50+
echo "EOF"
51+
} >> $GITHUB_OUTPUT
52+
echo "Labels result: $JSON_RESULT"

.github/workflows/shared-lock.yml

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
name: lock
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
key:
7+
description: "Key to lock (e.g., dev, staging, prod)"
8+
required: true
9+
type: string
10+
key_owner:
11+
description: "Key owner identifier (e.g., PR number, SHA, run ID)"
12+
required: true
13+
type: string
14+
job_name:
15+
description: "Custom name for the lock job"
16+
required: false
17+
type: string
18+
19+
permissions:
20+
contents: write
21+
22+
jobs:
23+
lock:
24+
name: ${{ inputs.job_name || 'lock' }}
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Define lock check function
28+
shell: bash
29+
run: |
30+
# Define reusable function for lock ownership checking
31+
cat << 'EOF' > lock_check_function.sh
32+
check_lock() {
33+
# Parse named parameters
34+
while [[ "$#" -gt 0 ]]; do
35+
case $1 in
36+
--key) key="$2"; shift ;;
37+
--key-owner) key_owner="$2"; shift ;;
38+
--lock-reason) lock_reason="$2"; shift ;;
39+
--locked) locked="$2"; shift ;;
40+
--require-owned) require_owned="$2"; shift ;;
41+
*) echo "Unknown parameter passed: $1"; return 1 ;;
42+
esac
43+
shift
44+
done
45+
46+
echo "Key: $key"
47+
echo "Key owner: $key_owner"
48+
echo "Lock reason: $lock_reason"
49+
echo "Locked: $locked"
50+
51+
local should_proceed=false
52+
local is_owned_by_us=false
53+
54+
if [ "$locked" = "true" ]; then
55+
echo "Lock exists, checking owner..."
56+
local lock_owner=$(echo "$lock_reason" | grep -o 'owner:[^,]*' | cut -d: -f2 || true)
57+
echo "Lock owner: $lock_owner"
58+
echo "Current owner: $key_owner"
59+
if [ "$lock_owner" = "$key_owner" ]; then
60+
echo "✓ Lock is held by this owner"
61+
should_proceed=true
62+
is_owned_by_us=true
63+
elif [ -z "$lock_owner" ]; then
64+
echo "✓ Lock is available (no owner)"
65+
should_proceed=true
66+
is_owned_by_us=false
67+
else
68+
echo "✗ Lock is held by $lock_owner"
69+
should_proceed=false
70+
is_owned_by_us=false
71+
echo "::error::Key '$key' is locked by $lock_owner"
72+
return 1
73+
fi
74+
else
75+
echo "✓ Key is not locked"
76+
should_proceed=true
77+
is_owned_by_us=false
78+
fi
79+
80+
echo "should_proceed=$should_proceed" >> $GITHUB_OUTPUT
81+
echo "is_owned_by_us=$is_owned_by_us" >> $GITHUB_OUTPUT
82+
83+
# Validate ownership requirement if specified
84+
if [ "$require_owned" = "true" ] && [ "$is_owned_by_us" != "true" ]; then
85+
echo "::error::Lock ownership is required but not achieved"
86+
return 1
87+
fi
88+
}
89+
EOF
90+
91+
# Check lock state
92+
- name: Get lock state
93+
uses: github/lock@9a5898804aedcdfb43592ed16b6457768d048183 # v3.0.1
94+
id: get-lock-state
95+
with:
96+
mode: "check"
97+
environment: ${{ inputs.key }}
98+
99+
- name: Check lock state
100+
id: check-lock
101+
shell: bash
102+
run: |
103+
source ./lock_check_function.sh
104+
check_lock \
105+
--key "${{ inputs.key }}" \
106+
--key-owner "${{ inputs.key_owner }}" \
107+
--lock-reason "${{ steps.get-lock-state.outputs.reason }}" \
108+
--locked "${{ steps.get-lock-state.outputs.locked }}"
109+
110+
# Lock
111+
- name: Prepare lock reason
112+
id: lock-reason
113+
run: |
114+
# Simple reason with owner information
115+
REASON="owner:${{ inputs.key_owner }}"
116+
echo "Lock reason: $REASON"
117+
echo "reason=$REASON" >> $GITHUB_OUTPUT
118+
if: steps.check-lock.outputs.should_proceed == 'true'
119+
120+
- name: Lock
121+
uses: github/lock@9a5898804aedcdfb43592ed16b6457768d048183 # v3.0.1
122+
id: lock
123+
with:
124+
mode: "lock"
125+
environment: ${{ inputs.key }}
126+
reason: ${{ steps.lock-reason.outputs.reason }}
127+
if: steps.check-lock.outputs.should_proceed == 'true'
128+
129+
- name: Wait for lock to propagate
130+
shell: bash
131+
run: |
132+
echo "Waiting for lock to propagate..."
133+
sleep 1
134+
135+
# Recheck lock state after lock
136+
- name: Get lock state for recheck
137+
uses: github/lock@9a5898804aedcdfb43592ed16b6457768d048183 # v3.0.1
138+
id: get-lock-state-for-recheck
139+
with:
140+
mode: "check"
141+
environment: ${{ inputs.key }}
142+
143+
- name: Recheck lock state
144+
id: recheck-lock
145+
shell: bash
146+
if: steps.lock.outcome == 'success'
147+
run: |
148+
source ./lock_check_function.sh
149+
check_lock \
150+
--key "${{ inputs.key }}" \
151+
--key-owner "${{ inputs.key_owner }}" \
152+
--lock-reason "${{ steps.get-lock-state-for-recheck.outputs.reason }}" \
153+
--locked "${{ steps.get-lock-state-for-recheck.outputs.locked }}" \
154+
--require-owned true
155+
156+
- name: Final lock status
157+
if: always()
158+
run: |
159+
echo "=== Lock Result ==="
160+
echo "Key: ${{ inputs.key }}"
161+
echo "Owner: ${{ inputs.key_owner }}"
162+
echo "Status: ${{ steps.lock.outcome }}"
163+
echo "Verified ownership: ${{ steps.recheck-lock.outputs.is_owned_by_us || 'N/A' }}"

0 commit comments

Comments
 (0)