Repository navigation
164 lines (160 loc) · 8.56 KB
/
Copy pathnotify-tutorials-ims.yml
File metadata and controls
164 lines (160 loc) · 8.56 KB
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
# Tutorial Repo Dispatch Workflow
#
# Copy this file to .github/workflows/notify-tutorials-ims.yml in each
# sap-tutorials tutorial *source* repo that should trigger a content rebuild.
#
# Auth (#1154): prefers a GitHub App installation token, falls back to a PAT.
# - App path (recommended): set repo variable USE_GITHUB_APP=true and add
# Actions secrets TUTORIALS_APP_ID + TUTORIALS_APP_PRIVATE_KEY. The App
# (sap-tutorials-builder) must be installed on this repo and hold
# Contents: write on sap-tutorials/tutorials-ims (repository_dispatch).
# See docs/developers/operations/github-app-setup.md.
# - PAT fallback (legacy): TUTORIALS_DISPATCH_TOKEN — GitHub PAT with `repo`
# scope on tutorials-ims. Used while USE_GITHUB_APP is unset/false.
#
# NOTE: the || fallback only engages if App-token GENERATION fails — NOT if
# the dispatch itself is unauthorized. Grant the App Contents:write on
# tutorials-ims BEFORE setting USE_GITHUB_APP=true here.
#
# Target env (#1154): this template sends client_payload.environment (default
# 'prod', override per-repo via the REBUILD_ENVIRONMENT repo variable).
# rebuild-content.yml honors that field (allowlist dev|qa|prod) as of PR #1273.
# BOTH must be merged before rolling this out to production source repos — on
# a rebuild-content.yml that predates #1273, repository_dispatch still routes
# to DEV regardless of payload, so every tutorial push would rebuild DEV.
name: Notify Tutorials Platform
on:
push:
# Source repos are a MIXTURE of default branches: most default to `main`,
# but some (e.g. sap-tutorials/Tutorials) still default to `master`. List
# BOTH so this template auto-publishes regardless of a repo's default —
# a `main`-only trigger silently never fires on a `master`-default repo
# (root cause of the cp-* PROD content-drift incidents). GitHub ignores a
# listed branch that doesn't exist, so [master, main] is safe everywhere.
branches: [master, main]
paths:
- '**.md'
- '**.png'
- '**.jpg'
- '**.gif'
jobs:
notify:
runs-on: ubuntu-latest
steps:
# Determine the changed tutorial slug(s), so the rebuild can run in fast
# slug-targeted mode (~2 min) instead of a full rebuild (~10 min). Mirrors
# notify-qa.yml.template. Needs full history (fetch-depth: 0) for the
# before..after diff.
# - exactly 1 changed tutorial → slug=<slug>
# - 2..CAP changed tutorials → slugs=<csv> (#2803)
# - 0, >CAP, or any invalid slug charset → both empty (full rebuild —
# a huge push is effectively full anyway, and an invalid slug must
# never reach the dispatch payload).
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- name: Determine changed slug(s)
id: slug
run: |
BEFORE="${{ github.event.before }}"
SHA="${{ github.sha }}"
# Slugs whose tutorials/<slug>/ directory was ENTIRELY removed in this
# push (#2349). --diff-filter=D lists deleted paths; a slug counts as
# deleted only if NO file under tutorials/<slug>/ survives in the after
# tree (guards a partial delete, e.g. removing one screenshot). These
# route to the unpublish lane in rebuild-content.yml, NOT the fetch lane
# (fetch-tutorials hard-fails on a slug missing from upstream discovery).
deleted_raw=$(git diff --name-only --diff-filter=D "$BEFORE" "$SHA" \
| awk -F/ '/^tutorials\//{print $2}' | sort -u)
surviving=$(git ls-tree -r --name-only "$SHA" \
| awk -F/ '/^tutorials\//{print $2}' | sort -u)
deleted=""
for cand in $deleted_raw; do
# Keep only candidates with no surviving file under tutorials/<cand>/.
if ! echo "$surviving" | grep -qx "$cand"; then
case "$cand" in
*[!a-z0-9-]* | -* ) : ;; # non-slug charset → skip
*) deleted="${deleted:+$deleted,}$cand" ;;
esac
fi
done
echo "deleted_slugs=$deleted" >> "$GITHUB_OUTPUT"
# Changed (added/modified) tutorial slugs, EXCLUDING deletions — a lone
# deletion must not emit slug=<deleted> and trip the fetch hard-fail.
changed=$(git diff --name-only --diff-filter=d "$BEFORE" "$SHA" \
| awk -F/ '/^tutorials\//{print $2}' | sort -u)
count=$(echo "$changed" | grep -c . || true)
# Emit a scoped payload so the rebuild stays slug-targeted (~2 min)
# instead of demoting to a full-corpus rebuild (~10 min):
# - exactly 1 changed tutorial → slug=<slug>
# - 2..CAP changed tutorials → slugs=<csv> (#2803)
# - 0, >CAP, or any slug failing the strict charset → both empty.
# Validating here (sender) too — not just the receiver — keeps a
# malformed dir name out of the dispatch payload entirely. Mirrors the
# receiver validation in rebuild-content.yml's mode step and the
# notify-qa.yml.template sender.
SLUG_CAP=20
slug=""
slugs=""
valid=1
for s in $changed; do
case "$s" in *[!a-z0-9-]* | -* ) valid=0 ;; esac
done
if [ "$valid" = "1" ] && [ "$count" -ge 1 ] && [ "$count" -le "$SLUG_CAP" ]; then
if [ "$count" = "1" ]; then
slug="$changed"
else
slugs=$(echo "$changed" | paste -sd, -)
fi
fi
echo "slug=$slug" >> "$GITHUB_OUTPUT"
echo "slugs=$slugs" >> "$GITHUB_OUTPUT"
# GitHub App token (preferred). Activates when this repo has
# USE_GITHUB_APP=true and the TUTORIALS_APP_* secrets populated. Falls
# back to the classic TUTORIALS_DISPATCH_TOKEN PAT while unset.
- name: Generate GitHub App token
id: app-token
if: ${{ vars.USE_GITHUB_APP == 'true' }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.TUTORIALS_APP_ID }}
private-key: ${{ secrets.TUTORIALS_APP_PRIVATE_KEY }}
owner: sap-tutorials
repositories: tutorials-ims
- name: Trigger content rebuild
env:
DISPATCH_TOKEN: ${{ steps.app-token.outputs.token || secrets.TUTORIALS_DISPATCH_TOKEN }}
# rebuild-content.yml routes on client_payload.environment (allowlist
# dev|qa|prod, defaults dev when absent). This is the PRODUCTION
# source-repo template, so it targets prod; override per-repo with a
# REBUILD_ENVIRONMENT repo variable (e.g. a staging source repo → qa).
TARGET_ENV: ${{ vars.REBUILD_ENVIRONMENT || 'prod' }}
# Single changed slug (empty if 0 or >1 or non-slug-charset) →
# rebuild-content.yml uses it to run slug-targeted (fast) vs full.
SLUG: ${{ steps.slug.outputs.slug }}
# Multi-slug (#2803): a push that changed 2..CAP tutorials sends a
# comma-separated list so the rebuild stays slug-targeted (O(N))
# instead of demoting to a ~10-min full rebuild. Receiver validates
# every entry against the strict slug charset before use.
SLUGS: ${{ steps.slug.outputs.slugs }}
# Tutorials whose dir was entirely deleted (#2349) → rebuild-content.yml
# routes these to the unpublish (soft-delete) lane, not the fetch lane.
DELETED_SLUGS: ${{ steps.slug.outputs.deleted_slugs }}
# Pin the github context values into env too, so the JSON body is
# assembled entirely from shell vars via jq (no template interpolation
# into the payload string).
REPO: ${{ github.repository }}
REF: ${{ github.ref }}
SHA: ${{ github.sha }}
run: |
# Build the payload with jq so every value is correctly JSON-escaped
# (a slug / ref / repo containing quotes or backslashes can't break out
# of the JSON — the string-interpolation form was a json-injection risk).
BODY=$(jq -nc \
--arg repo "$REPO" --arg ref "$REF" --arg sha "$SHA" \
--arg env "$TARGET_ENV" --arg slug "$SLUG" --arg slugs "$SLUGS" --arg deleted "$DELETED_SLUGS" \
'{event_type:"tutorial-updated", client_payload:{repository:$repo, ref:$ref, sha:$sha, environment:$env, slug:$slug, slugs:$slugs, deleted_slugs:$deleted}}')
curl -X POST \
-H "Authorization: Bearer ${DISPATCH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
--data "$BODY" \
"https://api.github.com/repos/sap-tutorials/tutorials-ims/dispatches"