Skip to content

Commit 3fc90e5

Browse files
Merge pull request #162 from scality/improvement/PTFE-2835-Improve-smart-workflow-triggers-features
Improvement/ptfe 2835 improve smart workflow triggers features
2 parents 3585808 + 2585d4d commit 3fc90e5

File tree

4 files changed

+270
-24
lines changed

4 files changed

+270
-24
lines changed

actions-nightly-trigger/README.md

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ jobs:
5252
| `workflow` | Workflow file to trigger | Yes | `nightly.yaml` |
5353
| `access_token` | GitHub token with workflow permissions | Yes | - |
5454
| `run-workflow` | Whether to actually run the workflow or only make decision | No | `true` |
55+
| `max-start-if-last-failed` | Maximum number of times to restart if last workflow run on last commit failed | No | `0` (disabled) |
56+
| `commit-check-period` | Time window in days to check for recent commits | No | `1` |
5557

5658
## Outputs
5759

@@ -64,9 +66,52 @@ jobs:
6466

6567
## Triggering Logic
6668

67-
1. If last commit in last 24 hours → Trigger
68-
2. If last run was 7, 14, 21... days ago → Trigger (weekly check)
69-
3. Otherwise → Skip
69+
The action applies these rules in priority order:
70+
71+
1. **Restart on failure**: If `max-start-if-last-failed` is set and the last workflow run on the last commit failed/timed out/cancelled, and we haven't exceeded the max restart count → Trigger
72+
2. **Recent commit**: If last commit is within `commit-check-period` days and no successful run exists on that commit → Trigger
73+
3. **Weekly health check**: If last run was 7, 14, 21... days ago → Trigger (weekly check)
74+
4. Otherwise → Skip
75+
76+
### Failure Restart Feature
77+
78+
The `max-start-if-last-failed` parameter allows automatic restarts when a workflow fails:
79+
80+
- Counts all workflow runs on the **last commit** of the branch
81+
- If the most recent run failed/timed out/cancelled, triggers a new run
82+
- Stops restarting once `max-start-if-last-failed` runs have been attempted
83+
- If a successful run exists on the last commit, no restart is triggered
84+
85+
Example with failure restart:
86+
87+
```yaml
88+
- uses: scality/actions/actions-nightly-trigger@main
89+
with:
90+
branch: "development/4"
91+
workflow: "nightly.yaml"
92+
access_token: ${{ secrets.GIT_ACCESS_TOKEN }}
93+
max-start-if-last-failed: 3 # Restart up to 3 times on failure
94+
```
95+
96+
### Configurable Commit Check Period
97+
98+
The `commit-check-period` parameter allows you to adjust the time window for checking recent commits:
99+
100+
- Set to `1` (default): Triggers if commit is within last 24 hours
101+
- Set to `2`: Triggers if commit is within last 48 hours (2 days)
102+
- Set to `N`: Triggers if commit is within last N days
103+
104+
This is useful when your workflow runs less frequently (e.g., every 2-3 days):
105+
106+
```yaml
107+
# For a workflow that runs every 2 days
108+
- uses: scality/actions/actions-nightly-trigger@main
109+
with:
110+
branch: "development/4"
111+
workflow: "nightly.yaml"
112+
access_token: ${{ secrets.GIT_ACCESS_TOKEN }}
113+
commit-check-period: 2 # Check commits in last 2 days
114+
```
70115

71116
## Testing Mode
72117

actions-nightly-trigger/action.yaml

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ inputs:
2424
description: 'Whether to actually run the workflow or only make decision (default: true)'
2525
required: false
2626
default: 'true'
27+
max-start-if-last-failed:
28+
description: 'Maximum number of times to retry if last workflow run on last commit failed (default: 0 - disabled)'
29+
required: false
30+
default: '0'
31+
commit-check-period:
32+
description: 'Time window in days to check for recent commits (default: 1 day)'
33+
required: false
34+
default: '1'
2735

2836
outputs:
2937
trigger:
@@ -56,14 +64,17 @@ runs:
5664
ref: ${{ steps.set-branch.outputs.branch }}
5765
fetch-depth: 1
5866

59-
- name: Get last commit timestamp
67+
- name: Get last commit info
6068
id: check-commits
6169
shell: bash
6270
run: |
63-
# Get the timestamp of the last commit
71+
# Get the timestamp and SHA of the last commit
6472
last_commit_time=$(git log -1 --format=%ct)
73+
last_commit_sha=$(git log -1 --format=%H)
6574
echo "last_commit_time=$last_commit_time" >> $GITHUB_OUTPUT
75+
echo "last_commit_sha=$last_commit_sha" >> $GITHUB_OUTPUT
6676
echo "📊 Last commit timestamp: $last_commit_time"
77+
echo "📊 Last commit SHA: $last_commit_sha"
6778
6879
- name: Check latest workflow run status
6980
id: check-status
@@ -109,13 +120,50 @@ runs:
109120
110121
echo "🔍 Latest workflow run #$run_id: status=$status, conclusion=$conclusion"
111122
123+
- name: Count workflow runs on last commit
124+
id: check-runs-on-commit
125+
shell: bash
126+
env:
127+
GITHUB_TOKEN: ${{ github.token }}
128+
COMMIT_SHA: ${{ steps.check-commits.outputs.last_commit_sha }}
129+
run: |
130+
# Get all workflow runs for this specific commit SHA
131+
runs=$(gh api \
132+
-H "Accept: application/vnd.github+json" \
133+
-H "X-GitHub-Api-Version: 2022-11-28" \
134+
"/repos/${{ github.repository }}/actions/workflows/${{ inputs.workflow }}/runs?head_sha=${COMMIT_SHA}" \
135+
--jq '.workflow_runs')
136+
137+
# Count total runs
138+
run_count=$(echo "$runs" | jq 'length')
139+
echo "run_count=$run_count" >> $GITHUB_OUTPUT
140+
echo "🔢 Total runs on commit ${COMMIT_SHA:0:8}: $run_count"
141+
142+
# Get the most recent run's conclusion
143+
if [ "$run_count" -gt 0 ]; then
144+
last_run_conclusion=$(echo "$runs" | jq -r '.[0].conclusion')
145+
last_run_status=$(echo "$runs" | jq -r '.[0].status')
146+
echo "last_run_on_commit_conclusion=$last_run_conclusion" >> $GITHUB_OUTPUT
147+
echo "last_run_on_commit_status=$last_run_status" >> $GITHUB_OUTPUT
148+
echo "📊 Last run on this commit: status=$last_run_status, conclusion=$last_run_conclusion"
149+
else
150+
echo "last_run_on_commit_conclusion=not_found" >> $GITHUB_OUTPUT
151+
echo "last_run_on_commit_status=not_found" >> $GITHUB_OUTPUT
152+
echo "📊 No runs found on this commit"
153+
fi
154+
112155
- name: Decide whether to trigger workflow
113156
id: decide
114157
shell: bash
115158
env:
116159
LAST_COMMIT_TIME: ${{ steps.check-commits.outputs.last_commit_time }}
117160
STATUS: ${{ steps.check-status.outputs.status }}
118161
DAYS_SINCE: ${{ steps.check-status.outputs.days_since_last_run }}
162+
RUN_COUNT: ${{ steps.check-runs-on-commit.outputs.run_count }}
163+
LAST_RUN_CONCLUSION: ${{ steps.check-runs-on-commit.outputs.last_run_on_commit_conclusion }}
164+
LAST_RUN_STATUS: ${{ steps.check-runs-on-commit.outputs.last_run_on_commit_status }}
165+
MAX_RETRIES: ${{ inputs.max-start-if-last-failed }}
166+
COMMIT_CHECK_PERIOD: ${{ inputs.commit-check-period }}
119167
run: |
120168
# Run the decision script
121169
${{ github.action_path }}/decide-trigger.sh
@@ -129,6 +177,7 @@ runs:
129177
130178
- name: Trigger workflow
131179
if: steps.decide.outputs.trigger == 'true' && inputs.run-workflow == 'true'
180+
id: trigger
132181
shell: bash
133182
env:
134183
GITHUB_TOKEN: ${{ inputs.access_token }}
@@ -138,18 +187,51 @@ runs:
138187
echo "📋 Reason: ${{ steps.decide.outputs.reason }}"
139188
gh workflow run "${{ inputs.workflow }}" --ref="${BRANCH}"
140189
190+
# Wait for the workflow run to be created
191+
echo "⏳ Waiting for workflow run to be created..."
192+
sleep 3
193+
194+
# Get the latest workflow run ID
195+
new_run_id=$(gh run list --workflow="${{ inputs.workflow }}" --branch="${BRANCH}" --limit=1 --json databaseId --jq '.[0].databaseId')
196+
197+
if [ -n "${new_run_id}" ]; then
198+
echo "triggered_run_id=${new_run_id}" >> $GITHUB_OUTPUT
199+
echo "✅ Triggered run ID: ${new_run_id}"
200+
fi
201+
141202
- name: Log decision
142203
shell: bash
143204
env:
144205
BRANCH: ${{ steps.set-branch.outputs.branch }}
206+
WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/workflows/${{ inputs.workflow }}
207+
TRIGGERED_RUN_ID: ${{ steps.trigger.outputs.triggered_run_id }}
145208
run: |
146-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
147-
echo "📊 Smart Nightly Trigger Decision Summary"
148-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
149-
echo "Branch: ${BRANCH}"
150-
echo "Trigger: ${{ steps.decide.outputs.trigger }}"
151-
echo "Reason: ${{ steps.decide.outputs.reason }}"
152-
echo "Last commit time: ${{ steps.check-commits.outputs.last_commit_time }}"
153-
echo "Days since last run: ${{ steps.check-status.outputs.days_since_last_run }}"
154-
echo "Last status: ${{ steps.check-status.outputs.conclusion }}"
155-
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
209+
# Build summary using heredoc
210+
SUMMARY=$(cat <<EOF
211+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
212+
📊 Smart Nightly Trigger Decision Summary
213+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
214+
Workflow: ${{ inputs.workflow }}
215+
Workflow URL: ${WORKFLOW_URL}
216+
Branch: ${BRANCH}
217+
Trigger: ${{ steps.decide.outputs.trigger }}
218+
Reason: ${{ steps.decide.outputs.reason }}
219+
EOF
220+
)
221+
222+
if [ -n "${TRIGGERED_RUN_ID}" ]; then
223+
SUMMARY="${SUMMARY}
224+
Triggered run: https://github.com/${{ github.repository }}/actions/runs/${TRIGGERED_RUN_ID}"
225+
fi
226+
227+
SUMMARY="${SUMMARY}
228+
Last commit time: ${{ steps.check-commits.outputs.last_commit_time }}
229+
Days since last run: ${{ steps.check-status.outputs.days_since_last_run }}
230+
Last status: ${{ steps.check-status.outputs.conclusion }}
231+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
232+
233+
# Display to console
234+
echo "${SUMMARY}"
235+
236+
# Write to GITHUB_STEP_SUMMARY
237+
echo "${SUMMARY}" >> $GITHUB_STEP_SUMMARY

actions-nightly-trigger/decide-trigger.sh

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,66 @@ set -e
88
LAST_COMMIT_TIME="${LAST_COMMIT_TIME:-0}"
99
STATUS="${STATUS:-not_found}"
1010
DAYS_SINCE="${DAYS_SINCE:-999}"
11+
RUN_COUNT="${RUN_COUNT:-0}"
12+
LAST_RUN_CONCLUSION="${LAST_RUN_CONCLUSION:-not_found}"
13+
LAST_RUN_STATUS="${LAST_RUN_STATUS:-not_found}"
14+
MAX_RETRIES="${MAX_RETRIES:-0}"
15+
COMMIT_CHECK_PERIOD="${COMMIT_CHECK_PERIOD:-1}"
1116

1217
trigger="false"
1318
reason="No trigger conditions met"
1419

15-
# Calculate if last commit is within 24 hours
20+
# Calculate if last commit is within the check period
1621
current_time=$(date +%s)
1722
time_diff=$((current_time - LAST_COMMIT_TIME))
1823
hours_ago=$((time_diff / 3600))
24+
days_ago=$((time_diff / 86400))
25+
check_period_seconds=$((COMMIT_CHECK_PERIOD * 86400))
1926

20-
# Priority 1: Trigger if last commit is within 24 hours
21-
if [ "$LAST_COMMIT_TIME" -gt 0 ] && [ $time_diff -le 86400 ]; then
22-
trigger="true"
23-
reason="Last commit was ${hours_ago}h ago (within 24h)"
24-
echo "$reason"
27+
# Priority 1: Restart if last run on last commit failed and under max restarts
28+
if [ "$MAX_RETRIES" -gt 0 ]; then
29+
# Check if last run on commit failed
30+
if [ "$LAST_RUN_CONCLUSION" = "failure" ] || [ "$LAST_RUN_CONCLUSION" = "timed_out" ] || [ "$LAST_RUN_CONCLUSION" = "cancelled" ]; then
31+
# Check if we haven't exceeded max restarts
32+
if [ "$RUN_COUNT" -lt "$MAX_RETRIES" ]; then
33+
trigger="true"
34+
reason="Last run failed (${LAST_RUN_CONCLUSION}), restarting ($RUN_COUNT/$MAX_RETRIES)"
35+
echo "🔄 $reason"
36+
else
37+
echo "⏭️ Max restarts reached ($RUN_COUNT/$MAX_RETRIES), last run: ${LAST_RUN_CONCLUSION}"
38+
fi
39+
elif [ "$LAST_RUN_CONCLUSION" = "success" ]; then
40+
echo "✅ Last run on commit succeeded, no restart needed"
41+
fi
42+
fi
43+
44+
# Priority 2: Trigger if last commit is within check period (and not already succeeded)
45+
if [ "$trigger" = "false" ] && [ "$LAST_COMMIT_TIME" -gt 0 ] && [ $time_diff -le $check_period_seconds ]; then
46+
# Only trigger if no successful run exists on this commit
47+
if [ "$LAST_RUN_CONCLUSION" != "success" ]; then
48+
trigger="true"
49+
if [ "$COMMIT_CHECK_PERIOD" -eq 1 ]; then
50+
reason="Last commit was ${hours_ago}h ago (within ${COMMIT_CHECK_PERIOD} day)"
51+
else
52+
reason="Last commit was ${days_ago}d ago (within ${COMMIT_CHECK_PERIOD} days)"
53+
fi
54+
echo "$reason"
55+
else
56+
if [ "$COMMIT_CHECK_PERIOD" -eq 1 ]; then
57+
echo "✅ Last commit was ${hours_ago}h ago but already has successful run"
58+
else
59+
echo "✅ Last commit was ${days_ago}d ago but already has successful run"
60+
fi
61+
fi
2562

26-
# Priority 2: Weekly health check (modulo 7 ensures only once per week)
27-
elif [ "$STATUS" != "not_found" ] && [ "$DAYS_SINCE" -gt 0 ] && [ $(($DAYS_SINCE % 7)) -eq 0 ]; then
63+
# Priority 3: Weekly health check (modulo 7 ensures only once per week)
64+
elif [ "$trigger" = "false" ] && [ "$STATUS" != "not_found" ] && [ "$DAYS_SINCE" -gt 0 ] && [ $(($DAYS_SINCE % 7)) -eq 0 ]; then
2865
trigger="true"
2966
reason="Weekly health check ($DAYS_SINCE days since last run)"
3067
echo "🏥 $reason"
3168

3269
# Default: Skip
33-
else
70+
elif [ "$trigger" = "false" ]; then
3471
echo "⏭️ Skipping nightly - $reason"
3572
echo " - Last commit: ${hours_ago}h ago"
3673
echo " - Days since last run: $DAYS_SINCE"

actions-nightly-trigger/test-decide-trigger.sh

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,88 @@ export STATUS="not_found"
113113
export DAYS_SINCE=999
114114
run_test "Status not found, old commit" 0 "false"
115115

116+
# Test 8: Restart on failure - first failure (1 run, max 3)
117+
export LAST_COMMIT_TIME=$((current_time - 172800)) # 48 hours ago (old commit)
118+
export STATUS="completed"
119+
export DAYS_SINCE=1
120+
export RUN_COUNT=1
121+
export LAST_RUN_CONCLUSION="failure"
122+
export LAST_RUN_STATUS="completed"
123+
export MAX_RETRIES=3
124+
run_test "Restart on failure - first failure (1/3)" 0 "true"
125+
126+
# Test 9: Restart on failure - max restarts reached
127+
export RUN_COUNT=3
128+
export LAST_RUN_CONCLUSION="failure"
129+
export MAX_RETRIES=3
130+
run_test "Max restarts reached (3/3)" 0 "false"
131+
132+
# Test 10: No restart - last run succeeded
133+
export RUN_COUNT=2
134+
export LAST_RUN_CONCLUSION="success"
135+
export MAX_RETRIES=3
136+
run_test "Last run succeeded, no restart needed" 0 "false"
137+
138+
# Test 11: Restart on timeout
139+
export RUN_COUNT=1
140+
export LAST_RUN_CONCLUSION="timed_out"
141+
export MAX_RETRIES=3
142+
run_test "Restart on timeout (1/3)" 0 "true"
143+
144+
# Test 12: Restart on cancelled
145+
export RUN_COUNT=2
146+
export LAST_RUN_CONCLUSION="cancelled"
147+
export MAX_RETRIES=3
148+
run_test "Restart on cancelled (2/3)" 0 "true"
149+
150+
# Test 13: Max retries disabled (0)
151+
export RUN_COUNT=1
152+
export LAST_RUN_CONCLUSION="failure"
153+
export MAX_RETRIES=0
154+
run_test "Max retries disabled (MAX_RETRIES=0)" 0 "false"
155+
156+
# Test 14: Recent commit with successful run - should not trigger
157+
export LAST_COMMIT_TIME=$((current_time - 18000)) # 5 hours ago
158+
export RUN_COUNT=1
159+
export LAST_RUN_CONCLUSION="success"
160+
export MAX_RETRIES=3
161+
run_test "Recent commit with successful run" 0 "false"
162+
163+
# Test 15: Weekly health check takes priority even when max restarts exceeded
164+
export LAST_COMMIT_TIME=$((current_time - 172800)) # 48 hours ago (old commit)
165+
export STATUS="completed"
166+
export DAYS_SINCE=7
167+
export RUN_COUNT=3
168+
export LAST_RUN_CONCLUSION="failure"
169+
export MAX_RETRIES=3
170+
export COMMIT_CHECK_PERIOD=1
171+
run_test "Weekly health check with max restarts reached (7 days)" 0 "true"
172+
173+
# Test 16: Commit within 2 day check period (30 hours ago)
174+
export LAST_COMMIT_TIME=$((current_time - 108000)) # 30 hours ago
175+
export STATUS="not_found"
176+
export DAYS_SINCE=999
177+
export RUN_COUNT=0
178+
export LAST_RUN_CONCLUSION="not_found"
179+
export MAX_RETRIES=0
180+
export COMMIT_CHECK_PERIOD=2
181+
run_test "Commit 30h ago within 2 day period" 0 "true"
182+
183+
# Test 17: Commit outside 1 day check period (30 hours ago)
184+
export LAST_COMMIT_TIME=$((current_time - 108000)) # 30 hours ago
185+
export COMMIT_CHECK_PERIOD=1
186+
run_test "Commit 30h ago outside 1 day period" 0 "false"
187+
188+
# Test 18: Commit within 3 day check period (50 hours ago)
189+
export LAST_COMMIT_TIME=$((current_time - 180000)) # 50 hours ago
190+
export COMMIT_CHECK_PERIOD=3
191+
run_test "Commit 50h ago within 3 day period" 0 "true"
192+
193+
# Test 19: Commit outside 2 day check period (50 hours ago)
194+
export LAST_COMMIT_TIME=$((current_time - 180000)) # 50 hours ago
195+
export COMMIT_CHECK_PERIOD=2
196+
run_test "Commit 50h ago outside 2 day period" 0 "false"
197+
116198
# Summary
117199
echo ""
118200
echo "═══════════════════════════════════════════"

0 commit comments

Comments
 (0)