Skip to content

Commit 238b23f

Browse files
NO-JIRA: Add workflow to sync grafana/loki with openshift/loki
1 parent 2f8c19a commit 238b23f

2 files changed

Lines changed: 299 additions & 0 deletions

File tree

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
name: Common logging sync main flow
2+
on:
3+
workflow_call:
4+
inputs:
5+
upstream:
6+
description: Upstream repo path in owner/repo format
7+
required: true
8+
type: string
9+
downstream:
10+
description: Downstream repo path in owner/repo format
11+
required: true
12+
type: string
13+
downstream-branch:
14+
description: Downstream branch to sync into
15+
required: false
16+
default: main
17+
type: string
18+
sandbox:
19+
description: Sandbox repo path in owner/repo format. Used as a base to create PR against downstream.
20+
required: true
21+
type: string
22+
commit-filter:
23+
description: Grep pattern to filter incoming commits. When set, a PR is only created if matching commits exist. When empty, always syncs.
24+
required: false
25+
default: ''
26+
type: string
27+
restore-upstream:
28+
description: List of files to be reset using upstream content on merge conflict.
29+
required: false
30+
default: ''
31+
type: string
32+
restore-downstream:
33+
description: List of files to be reset using downstream content on merge conflict.
34+
required: false
35+
default: ''
36+
type: string
37+
secrets:
38+
cloner-app-id:
39+
description: Github ID of cloner app
40+
required: true
41+
cloner-app-private-key:
42+
description: Github private key of cloner app
43+
required: true
44+
pr-app-id:
45+
description: Github ID of PR creation app
46+
required: true
47+
pr-app-private-key:
48+
description: Github private key of PR creation app
49+
required: true
50+
slack-webhook-url:
51+
description: Slack webhook URL to send notification
52+
required: true
53+
54+
jobs:
55+
sync:
56+
runs-on: ubuntu-latest
57+
name: Sync main branch
58+
steps:
59+
- name: Find github org name from repo name
60+
id: org
61+
run: |
62+
{
63+
echo "upstream=$(dirname ${{ inputs.upstream }})"
64+
echo "downstream=$(dirname ${{ inputs.downstream }})"
65+
echo "sandbox=$(dirname ${{ inputs.sandbox }})"
66+
} >> "$GITHUB_OUTPUT"
67+
68+
- uses: actions/checkout@v6
69+
with:
70+
repository: ${{ inputs.downstream }}
71+
fetch-depth: 0
72+
ref: ${{ inputs.downstream-branch }}
73+
74+
- name: Configure git
75+
run: |
76+
git config user.name 'github-actions[bot]'
77+
git config user.email 'github-actions[bot]@users.noreply.github.com'
78+
79+
- name: Fetch upstream ${{ inputs.downstream-branch }}
80+
run: git fetch https://github.com/${{ inputs.upstream }} ${{ inputs.downstream-branch }}
81+
82+
- name: Check if behind upstream
83+
id: check
84+
run: |
85+
BEHIND=$(git rev-list --count HEAD..FETCH_HEAD)
86+
echo "behind=${BEHIND}" >> "$GITHUB_OUTPUT"
87+
if [ "${BEHIND}" -eq 0 ]; then
88+
echo "::notice::Already up-to-date with upstream"
89+
else
90+
echo "::notice::${BEHIND} commits behind upstream"
91+
fi
92+
93+
- name: Filter incoming commits
94+
if: steps.check.outputs.behind != '0'
95+
id: filter
96+
run: |
97+
if [ -n "${{ inputs.commit-filter }}" ]; then
98+
COMMITS=$(git log --oneline HEAD..FETCH_HEAD --grep='${{ inputs.commit-filter }}' --extended-regexp)
99+
if [ -z "$COMMITS" ]; then
100+
echo "has_matches=false" >> "$GITHUB_OUTPUT"
101+
echo "::notice::No commits matching filter '${{ inputs.commit-filter }}' — skipping PR"
102+
exit 0
103+
fi
104+
echo "has_matches=true" >> "$GITHUB_OUTPUT"
105+
{
106+
echo 'commits<<EOF'
107+
echo "$COMMITS"
108+
echo 'EOF'
109+
} >> "$GITHUB_OUTPUT"
110+
else
111+
echo "has_matches=true" >> "$GITHUB_OUTPUT"
112+
fi
113+
114+
- name: Merge upstream ${{ inputs.downstream-branch }}
115+
if: steps.check.outputs.behind != '0' && steps.filter.outputs.has_matches == 'true'
116+
id: merge
117+
run: |
118+
git merge FETCH_HEAD --no-edit || echo 'MERGE_CONFLICT=true' >> "$GITHUB_OUTPUT"
119+
120+
- name: Resolve conflict using upstream contents
121+
if: steps.merge.outputs.MERGE_CONFLICT == 'true' && inputs.restore-upstream != ''
122+
run: |
123+
echo "reset ${{ inputs.restore-upstream }}"
124+
git checkout --theirs ${{ inputs.restore-upstream }} || true
125+
git add ${{ inputs.restore-upstream }} || true
126+
127+
- name: Remove deleted files
128+
if: steps.merge.outputs.MERGE_CONFLICT == 'true'
129+
run: |
130+
git diff --name-only --diff-filter=D | xargs -r git rm
131+
132+
- name: Resolve conflict using downstream contents
133+
if: steps.merge.outputs.MERGE_CONFLICT == 'true' && inputs.restore-downstream != ''
134+
run: |
135+
echo "reset ${{ inputs.restore-downstream }}"
136+
git checkout --ours ${{ inputs.restore-downstream }}
137+
git add ${{ inputs.restore-downstream }}
138+
139+
- name: Resolve conflict due to deleted downstream files
140+
if: steps.merge.outputs.MERGE_CONFLICT == 'true'
141+
run: |
142+
git status --porcelain | awk '{ if ($1=="UD" || $1=="DU") print $2 }' | xargs -I {} git rm {}
143+
144+
- name: Continue after merge conflict
145+
if: steps.merge.outputs.MERGE_CONFLICT == 'true'
146+
run: |
147+
git add -A
148+
git merge --continue
149+
150+
- name: Resolve caller workflow file
151+
id: caller
152+
run: |
153+
WORKFLOW_PATH=$(echo "${{ github.workflow_ref }}" | sed 's|${{ github.repository }}/||' | sed 's|@.*||')
154+
echo "url=${{ github.server_url }}/${{ github.repository }}/blob/main/${WORKFLOW_PATH}" >> "$GITHUB_OUTPUT"
155+
156+
- name: Get auth token to create pull request for ${{ inputs.downstream }}
157+
if: github.event_name != 'pull_request' && steps.check.outputs.behind != '0' && steps.filter.outputs.has_matches == 'true'
158+
id: pr
159+
uses: getsentry/action-github-app-token@v3
160+
with:
161+
app_id: ${{ secrets.pr-app-id }}
162+
private_key: ${{ secrets.pr-app-private-key }}
163+
scope: ${{ steps.org.outputs.downstream }}
164+
165+
- name: Get auth token to push to ${{ inputs.sandbox }}
166+
if: github.event_name != 'pull_request' && steps.check.outputs.behind != '0' && steps.filter.outputs.has_matches == 'true'
167+
id: cloner
168+
uses: getsentry/action-github-app-token@v3
169+
with:
170+
app_id: ${{ secrets.cloner-app-id }}
171+
private_key: ${{ secrets.cloner-app-private-key }}
172+
scope: ${{ steps.org.outputs.sandbox }}
173+
174+
- name: Create Pull Request
175+
if: github.event_name != 'pull_request' && steps.check.outputs.behind != '0' && steps.filter.outputs.has_matches == 'true'
176+
uses: rhobs/create-pull-request@v4
177+
id: create-pr
178+
with:
179+
title: "[Logging Sync Bot] Sync ${{ inputs.downstream }} with ${{ inputs.upstream }} main"
180+
body: |
181+
## Description
182+
Automated sync of `${{ inputs.upstream }}` main into `${{ inputs.downstream }}` ${{ inputs.downstream-branch }}.
183+
184+
Created by [syncbot](${{ steps.caller.outputs.url }}) — [run logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
185+
186+
${{ inputs.commit-filter != '' && format('## Commits matching `{0}`', inputs.commit-filter) || '' }}
187+
${{ steps.filter.outputs.commits || '' }}
188+
author: 'github-actions[bot]<github-actions[bot]@users.noreply.github.com>'
189+
committer: 'github-actions[bot]<github-actions[bot]@users.noreply.github.com>'
190+
signoff: true
191+
branch: automated-sync-main-${{ inputs.downstream-branch }}
192+
delete-branch: true
193+
token: ${{ steps.pr.outputs.token }}
194+
push-to-fork: ${{ inputs.sandbox }}
195+
push-to-fork-token: ${{ steps.cloner.outputs.token }}
196+
197+
- name: Check if PR exists using gh cli
198+
if: github.event_name != 'pull_request' && failure()
199+
id: pr-exists
200+
env:
201+
GH_TOKEN: ${{ steps.pr.outputs.token }}
202+
run: |
203+
if [ "${{ steps.create-pr.outcome }}" != "success" ]; then
204+
PR_URL=$(gh pr list --json url --jq '.[0].url' --repo ${{ inputs.downstream }} --state open --head automated-sync-main-${{ inputs.downstream-branch }})
205+
if [ ! -z "$PR_URL" ]; then
206+
echo "pr_exists=1" >> "$GITHUB_OUTPUT"
207+
echo "pr_url=$PR_URL" >> "$GITHUB_OUTPUT"
208+
echo "PR exists >> $PR_URL"
209+
else
210+
echo "pr_exists=0" >> "$GITHUB_OUTPUT"
211+
fi
212+
fi
213+
214+
- name: Compose slack message body
215+
if: github.event_name != 'pull_request' && (success() || steps.check.outputs.behind == '0' || steps.filter.outputs.has_matches == 'false' || steps.pr-exists.outputs.pr_exists == '1')
216+
continue-on-error: true
217+
id: slack-message
218+
run: |
219+
if [ "${{ steps.pr-exists.outputs.pr_exists }}" == "1" ]; then
220+
PR_URL="${{ steps.pr-exists.outputs.pr_url }}"
221+
else
222+
PR_URL="${{ steps.create-pr.outputs.pull-request-url }}"
223+
fi
224+
if [ "${{ steps.check.outputs.behind }}" == "0" ]; then
225+
echo "message=${{ inputs.downstream }} is already up-to-date with ${{ inputs.upstream }} main." >> "$GITHUB_OUTPUT"
226+
elif [ "${{ steps.filter.outputs.has_matches }}" == "false" ]; then
227+
echo "message=${{ inputs.downstream }} has no commits matching filter '${{ inputs.commit-filter }}' — skipped." >> "$GITHUB_OUTPUT"
228+
else
229+
echo "message=PR $PR_URL has been ${{ steps.create-pr.outputs.pull-request-operation || 'updated' }}." >> "$GITHUB_OUTPUT"
230+
fi
231+
232+
- uses: 8398a7/action-slack@v3
233+
if: github.event_name != 'pull_request' && (success() || steps.check.outputs.behind == '0' || steps.filter.outputs.has_matches == 'false' || steps.pr-exists.outputs.pr_exists == '1')
234+
continue-on-error: true
235+
with:
236+
status: custom
237+
fields: workflow
238+
custom_payload: |
239+
{
240+
attachments: [{
241+
color: 'good',
242+
text: `${process.env.AS_WORKFLOW}\n ${{ steps.slack-message.outputs.message }}`,
243+
}]
244+
}
245+
env:
246+
SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook-url }}
247+
248+
- uses: 8398a7/action-slack@v3
249+
if: github.event_name != 'pull_request' && (failure() && steps.check.outputs.behind != '0' && steps.filter.outputs.has_matches != 'false' && !(steps.pr-exists.outputs.pr_exists == '1'))
250+
continue-on-error: true
251+
with:
252+
status: custom
253+
fields: workflow
254+
custom_payload: |
255+
{
256+
attachments: [{
257+
color: 'danger',
258+
text: `${process.env.AS_WORKFLOW} has failed.`,
259+
}]
260+
}
261+
env:
262+
SLACK_WEBHOOK_URL: ${{ secrets.slack-webhook-url }}

.github/workflows/merge-loki.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Loki sync
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '0 0 * * *' #@daily
7+
pull_request:
8+
paths:
9+
- '.github/workflows/logging-sync-main-flow.yaml'
10+
- '.github/workflows/merge-loki.yaml'
11+
push:
12+
paths:
13+
- '.github/workflows/logging-sync-main-flow.yaml'
14+
- '.github/workflows/merge-loki.yaml'
15+
jobs:
16+
loki-sync:
17+
uses: ./.github/workflows/logging-sync-main-flow.yaml
18+
with:
19+
upstream: grafana/loki
20+
downstream: openshift/loki
21+
sandbox: rhobs/loki
22+
commit-filter: '(operator):'
23+
restore-upstream: >-
24+
CHANGELOG.md
25+
VERSION
26+
go.mod
27+
go.sum
28+
restore-downstream: >-
29+
OWNERS
30+
Dockerfile.ocp
31+
Dockerfile.promtail.ocp
32+
secrets:
33+
pr-app-id: ${{ secrets.APP_ID }}
34+
pr-app-private-key: ${{ secrets.APP_PRIVATE_KEY }}
35+
cloner-app-id: ${{ secrets.CLONER_APP_ID }}
36+
cloner-app-private-key: ${{ secrets.CLONER_APP_PRIVATE_KEY }}
37+
slack-webhook-url: ${{ secrets.LOGGING_SLACK_WEBHOOK_URL }}

0 commit comments

Comments
 (0)