Skip to content

Commit 982acd7

Browse files
evgeniy-antonyukagolybev
authored andcommitted
Create actions to automate release
1 parent b829e15 commit 982acd7

File tree

2 files changed

+361
-0
lines changed

2 files changed

+361
-0
lines changed

.github/workflows/create_branch.yml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Create Branch
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
branch:
7+
description: 'Branch name to create'
8+
required: true
9+
default: 'release/vX.X.X'
10+
source_branch:
11+
description: 'Source branch'
12+
required: false
13+
default: 'develop'
14+
15+
env:
16+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
17+
18+
jobs:
19+
create_branch:
20+
name: Create branch in ${{ matrix.repo }}
21+
runs-on: ubuntu-latest
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
repo: [ 'DocSpace', 'DocSpace-buildtools', 'DocSpace-client', 'DocSpace-server' ]
26+
steps:
27+
- name: Create branch in ${{ matrix.repo }}
28+
run: |
29+
SOURCE_HASH=$(gh api /repos/${{ github.repository_owner }}/${{ matrix.repo }}/git/ref/heads/${{ github.event.inputs.source_branch }} --jq '.object.sha' 2>/dev/null || true)
30+
[ -z "$SOURCE_HASH" ] && { echo "::error:: Source branch '${{ github.event.inputs.source_branch }}' not found"; exit 1; }
31+
32+
if ! gh api /repos/${{ github.repository_owner }}/${{ matrix.repo }}/git/ref/heads/${{ github.event.inputs.branch }} >/dev/null 2>&1; then
33+
if gh api --method POST /repos/${{ github.repository_owner }}/${{ matrix.repo }}/git/refs -f ref="refs/heads/${{ github.event.inputs.branch }}" -f sha="${SOURCE_HASH}"; then
34+
echo "Branch '${{ github.event.inputs.branch }}' created successfully."
35+
else
36+
echo "::error:: Error occurred while creating branch '${{ github.event.inputs.branch }}'." && exit 1
37+
fi
38+
else
39+
echo "::warning:: Target branch '${{ github.event.inputs.branch }}' already exists, skipping creation."
40+
fi
41+
42+
notify:
43+
name: Notify Create branch
44+
runs-on: ubuntu-latest
45+
needs: [create_branch ]
46+
if: ${{ always() }}
47+
steps:
48+
- name: Notify Create branch
49+
run: |
50+
JOB_OUTPUT=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs")
51+
TOTAL=$(jq '[.jobs[] | select(.name | test("^Create branch in "))] | length' <<< "${JOB_OUTPUT}")
52+
SUCCESS=$(jq '[.jobs[] | select((.name | test("^Create branch in ")) and (.conclusion=="success"))] | length' <<< "${JOB_OUTPUT}")
53+
54+
MESSAGE="\[${SUCCESS}/${TOTAL}] Created ${{ github.event.inputs.branch }} (base: ${{ github.event.inputs.source_branch }})"$'\n'
55+
MESSAGE+=$(jq -r --arg OWNER "${{ github.repository_owner }}" --arg GIT_URL "${{ github.server_url }}" '
56+
.jobs[]
57+
| select(.name | test("^Create branch in "))
58+
| (.name | sub("^Create branch in ";"")) as $REPO
59+
| (.conclusion | if .=="success" then "🟢" else "🔴" end)
60+
+ " [" + $REPO + "](" + $GIT_URL + "/" + $OWNER + "/" + $REPO + ")"
61+
' <<< "${JOB_OUTPUT}")
62+
63+
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
64+
-d chat_id="${{ secrets.TELEGRAM_TEAM_CHAT_ID }}" -d text="${MESSAGE}" -d parse_mode="Markdown" -d disable_web_page_preview=true

.github/workflows/release.yml

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
name: Release Action
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Release version'
8+
required: true
9+
default: 'vX.X.X'
10+
branch:
11+
description: 'Release branch'
12+
required: true
13+
default: 'release/vX.X.X'
14+
target_branch:
15+
description: 'Target branch'
16+
required: true
17+
default: 'master'
18+
trigger_release:
19+
type: boolean
20+
description: 'GitHub Release'
21+
required: true
22+
default: true
23+
trigger_docker_release:
24+
type: boolean
25+
description: 'Docker images release'
26+
required: true
27+
default: true
28+
docker_version:
29+
description: 'Source Docker version'
30+
required: true
31+
default: 'X.X.X.XXXX'
32+
trigger_packages_release:
33+
type: boolean
34+
description: 'Packages release'
35+
required: true
36+
default: true
37+
deb_build_number:
38+
description: 'deb package build number'
39+
required: true
40+
default: 'XXXX'
41+
rpm_build_number:
42+
description: 'rpm package build number'
43+
required: true
44+
default: 'XXX'
45+
exe_build_number:
46+
description: 'exe package build number'
47+
required: true
48+
default: 'XXXX'
49+
50+
env:
51+
GH_TOKEN: ${{ secrets.GH_TOKEN }}
52+
53+
jobs:
54+
trigger_docker_release:
55+
if: ${{ github.event.inputs.trigger_docker_release == 'true' }}
56+
name: Trigger Docker release
57+
runs-on: ubuntu-latest
58+
outputs:
59+
docker_run_id: ${{ steps.docker_release.outputs.docker_run_id }}
60+
steps:
61+
- name: Trigger Docker release
62+
id: docker_release
63+
run: |
64+
DOCKER_VERSION="${{ github.event.inputs.version }}"
65+
DOCKER_VERSION="${DOCKER_VERSION#v}.1"
66+
67+
WORKFLOW_ID=$(gh api repos/${{ github.repository }}-buildtools/actions/workflows \
68+
--jq '.workflows[] | select(.path == ".github/workflows/release-docspace.yaml") | .id')
69+
70+
gh api /repos/${{ github.repository }}-buildtools/actions/workflows/${WORKFLOW_ID}/dispatches -X POST \
71+
-f "ref=${{ github.ref_name }}" -f "inputs[release_version]=${DOCKER_VERSION}" -f "inputs[source_version]=${{ github.event.inputs.docker_version }}"
72+
73+
until [ -n "${DOCKER_RUN_ID}" ]; do
74+
DOCKER_RUN_ID=$(gh api repos/${{ github.repository }}-buildtools/actions/runs \
75+
--jq '.workflow_runs[] | select(.workflow_id=='"${WORKFLOW_ID}"' and (.status=="in_progress" or .status=="queued")) | .id' | head -n 1)
76+
sleep 5
77+
done
78+
79+
echo "docker_run_id=${DOCKER_RUN_ID}" >> $GITHUB_OUTPUT
80+
81+
trigger_packages_release:
82+
if: ${{ github.event.inputs.trigger_packages_release == 'true' }}
83+
name: Trigger Packages release
84+
runs-on: ubuntu-latest
85+
outputs:
86+
deb_build_number: ${{ steps.package_release.outputs.deb_build_number }}
87+
rpm_build_number: ${{ steps.package_release.outputs.rpm_build_number }}
88+
exe_build_number: ${{ steps.package_release.outputs.exe_build_number }}
89+
steps:
90+
- name: Trigger Packages release
91+
id: package_release
92+
run: |
93+
JENKINS_URL="${{ secrets.JENKINS_URL }}"; TOKEN="${{ secrets.JENKINS_TOKEN }}"; VERSION="${{ github.event.inputs.version }}"
94+
declare -A NUM=([deb]="${{ github.event.inputs.deb_build_number }}" [rpm]="${{ github.event.inputs.rpm_build_number }} [exe]="${{ github.event.inputs.exe_build_number }}")
95+
96+
for TYPE in deb rpm exe; do
97+
BUILD_NUM=${NUM[${TYPE}]}
98+
curl -s -S -X POST -H "Content-Length: 0" -u "${TOKEN}" "${JENKINS_URL}/job/appserver.${TYPE}/${BUILD_NUM}/toggleLogKeep"
99+
curl -s -S -X POST -u "${TOKEN}" -d "description=${VERSION}.${BUILD_NUM}" "${JENKINS_URL}/job/appserver.${TYPE}/${BUILD_NUM}/submitDescription"
100+
curl -s -S -X POST -H "Content-Length: 0" -u "${TOKEN}" "${JENKINS_URL}/job/production.${TYPE}.publish/buildWithParameters"
101+
sleep 10
102+
echo "${TYPE}_build_number=$(curl -s -S -L -u "${TOKEN}" "${JENKINS_URL}/job/production.${TYPE}.publish/lastBuild/api/json" | jq '.number')" >> "$GITHUB_OUTPUT"
103+
done
104+
105+
create_and_merge_pr:
106+
name: Create/merge PRs ${{ matrix.repo }}
107+
runs-on: ubuntu-latest
108+
strategy:
109+
matrix:
110+
repo: [ "DocSpace", "DocSpace-buildtools", "DocSpace-client", "DocSpace-server" ]
111+
steps:
112+
- name: Check branch existence
113+
run: |
114+
if ! gh api repos/${{ github.repository_owner }}/${{ matrix.repo }}/git/ref/heads/${{ github.event.inputs.branch }} --silent; then
115+
echo "::error::Branch '${{ github.event.inputs.branch }}' does not exist in repository ${{ matrix.repo }}" && exit 1
116+
fi
117+
118+
- name: Check commits difference
119+
run: |
120+
AHEAD_BY=$(gh api repos/${{ github.repository_owner }}/${{ matrix.repo }}/compare/${{ github.event.inputs.target_branch }}...${{ github.event.inputs.branch }} --jq '.ahead_by')
121+
if [[ "$AHEAD_BY" =~ ^[0-9]+$ ]] && [ "$AHEAD_BY" -eq 0 ]; then
122+
echo "::warning::No commits to merge in ${{ matrix.repo }}, skipping PR creation"
123+
echo "SKIP_PR=true" >> $GITHUB_ENV
124+
fi
125+
126+
- name: Create Pull Request
127+
if: ${{ env.SKIP_PR != 'true' }}
128+
run: |
129+
PR_NUMBER=$(gh pr list --repo ${{ github.repository_owner }}/${{ matrix.repo }} \
130+
--base ${{ github.event.inputs.target_branch }} \
131+
--head ${{ github.event.inputs.branch }} \
132+
--state open \
133+
--json number --jq '.[0].number')
134+
135+
if [[ -n "$PR_NUMBER" ]]; then
136+
echo "Pull request #$PR_NUMBER already exists in ${{ matrix.repo }}"
137+
else
138+
gh pr create --repo ${{ github.repository_owner }}/${{ matrix.repo }} \
139+
-t "Merge ${{ github.event.inputs.branch }} into ${{ github.event.inputs.target_branch }}" \
140+
-b "Automatically created by Release Action" \
141+
-B ${{ github.event.inputs.target_branch }} \
142+
-H ${{ github.event.inputs.branch }}
143+
fi
144+
145+
- name: Auto merge Pull Request
146+
if: ${{ env.SKIP_PR != 'true' }}
147+
run: |
148+
PR_NUMBER=$(timeout 60 bash -c 'while :; do
149+
NUM=$(gh pr list --repo ${{ github.repository_owner }}/${{ matrix.repo }} \
150+
--base ${{ github.event.inputs.target_branch }} \
151+
--head "${{ github.event.inputs.branch }}" \
152+
--state open \
153+
--json number --jq ".[0].number");
154+
[[ -n "$NUM" && "$NUM" != "UNKNOWN" ]] && echo $NUM; sleep 2; exit 0;
155+
done;')
156+
157+
if [ -z "${PR_NUMBER}" ]; then
158+
echo "::error::Failed to find open PR in ${{ matrix.repo }}" && exit 1
159+
fi
160+
161+
MERGEABLE_STATUS=$(gh pr view "${PR_NUMBER}" \
162+
--repo ${{ github.repository_owner }}/${{ matrix.repo }} \
163+
--json mergeable --jq '.mergeable')
164+
165+
if [ "${MERGEABLE_STATUS}" = "MERGEABLE" ]; then
166+
gh pr merge "${PR_NUMBER}" --repo ${{ github.repository_owner }}/${{ matrix.repo }} \
167+
-t "Merge ${{ github.event.inputs.branch }} into ${{ github.event.inputs.target_branch }}" --merge
168+
else
169+
echo "::warning::PR #$PR_NUMBER in ${{ matrix.repo }} is not mergeable or contains conflicts."
170+
fi
171+
172+
release:
173+
if: ${{ github.event.inputs.trigger_release == 'true' }}
174+
name: Release ${{ matrix.repo }}
175+
runs-on: ubuntu-latest
176+
needs: create_and_merge_pr
177+
strategy:
178+
fail-fast: false
179+
matrix:
180+
repo: [ "DocSpace", "DocSpace-buildtools", "DocSpace-client", "DocSpace-server" ]
181+
steps:
182+
- name: Wait for all PR merges across repositories
183+
env:
184+
REPOS: "DocSpace DocSpace-client DocSpace-server DocSpace-buildtools"
185+
run: |
186+
echo "Waiting for PR merges in all repositories..."
187+
while :; do
188+
MERGED=0
189+
for REPO in ${REPOS}; do
190+
PR_INFO=$(gh pr list --repo ${{ github.repository_owner }}/${REPO} \
191+
--base ${{ github.event.inputs.target_branch }} --head ${{ github.event.inputs.branch }} \
192+
--state all --json state,url --jq '.[0] // {state: "NOT_FOUND", url: ""}')
193+
PR_STATE=$(echo "${PR_INFO}" | jq -r '.state')
194+
PR_URL=$(echo "${PR_INFO}" | jq -r '.url')
195+
196+
echo "PR state for $REPO: ${PR_STATE} ($PR_URL)"
197+
198+
[ "${PR_STATE}" = "MERGED" ] && MERGED=$((MERGED + 1))
199+
[ "${PR_STATE}" = "NOT_FOUND" ] && REPOS=$(xargs -n1 <<< "$REPOS" | grep -vx "$REPO" | xargs)
200+
done
201+
printf '%.0s-' {1..48}; echo
202+
[ "${MERGED}" -eq "$(echo ${REPOS} | wc -w)" ] && break
203+
sleep 5
204+
done
205+
206+
- name: Checkout repository ${{ matrix.repo }}
207+
uses: actions/checkout@v4
208+
with:
209+
repository: ${{ github.repository_owner }}/${{ matrix.repo }}
210+
token: ${{ secrets.GH_TOKEN }}
211+
submodules: recursive
212+
fetch-depth: 0
213+
214+
- name: Update submodules (DocSpace only)
215+
if: ${{ matrix.repo == 'DocSpace' }}
216+
run: |
217+
git pull --recurse-submodules
218+
git submodule update --remote --merge
219+
if [ -n "$(git status --porcelain)" ]; then
220+
git config --global user.name "github-actions[bot]"
221+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
222+
git commit -am "Update submodules"
223+
git push origin HEAD
224+
else
225+
echo "::warning::No submodule changes detected."
226+
fi
227+
228+
- name: Create Tag
229+
run: |
230+
TAG_NAME=${{ github.event.inputs.version }}$([ "${{ matrix.repo }}" != "DocSpace" ] && echo '-server' || echo '')
231+
git fetch --tags
232+
if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
233+
echo "::warning::Tag ${TAG_NAME} already exists. Skipping tag creation."
234+
else
235+
git config --global user.name "github-actions[bot]"
236+
git config --global user.email "github-actions[bot]@users.noreply.github.com"
237+
git tag ${TAG_NAME} -m "${TAG_NAME}"
238+
git push origin ${TAG_NAME}
239+
fi
240+
241+
- name: Create Release (DocSpace only)
242+
if: ${{ matrix.repo == 'DocSpace' }}
243+
uses: softprops/action-gh-release@v2
244+
with:
245+
token: ${{ secrets.GH_TOKEN }}
246+
tag_name: ${{ github.event.inputs.version }}
247+
name: ${{ github.event.inputs.version }}
248+
body: See changes at [CHANGELOG.md](${{ github.server_url }}/${{ github.repository }}/blob/master/CHANGELOG.md)
249+
draft: false
250+
prerelease: false
251+
252+
notify:
253+
name: Notify Release
254+
runs-on: ubuntu-latest
255+
needs: [release, trigger_docker_release, trigger_packages_release ]
256+
if: ${{ always() }}
257+
steps:
258+
- name: Notify Release
259+
env:
260+
REPOS: "DocSpace DocSpace-client DocSpace-server DocSpace-buildtools"
261+
DOCKER_RUN_ID: ${{ needs.trigger_docker_release.outputs.docker_run_id }}
262+
DEB_BUILD_NUMBER: ${{ needs.trigger_packages_release.outputs.deb_build_number }}
263+
RPM_BUILD_NUMBER: ${{ needs.trigger_packages_release.outputs.rpm_build_number }}
264+
EXE_BUILD_NUMBER: ${{ needs.trigger_packages_release.outputs.exe_build_number }}
265+
run: |
266+
JQ_TARGET=$([ "${{ github.event.inputs.trigger_release }}" = "true" ] && echo "Release" || echo "Create/merge PR")
267+
JQ_FILTER=".jobs[] | select(.name | startswith(\"${JQ_TARGET}\")) | \"\\(.name | sub(\"^${JQ_TARGET}s? ?\"; \"\")) \\(.conclusion)\""
268+
JOB_OUTPUT=$(gh api "repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs" --jq "${JQ_FILTER}")
269+
270+
for REPO in ${REPOS}; do
271+
[[ "${JOB_OUTPUT}" =~ "${REPO} success" ]] && SUCCESS=$((SUCCESS+1)) && EMOJI="🟢" || EMOJI="🔴"
272+
STATUS+=$'\n'"${EMOJI} [${REPO}](${{ github.server_url }}/${{ github.repository_owner }}/${REPO})"
273+
done
274+
275+
MESSAGE="\[${SUCCESS:-0}/$(echo $REPOS | wc -w)] ${{ github.event.inputs.branch }} → ${{ github.event.inputs.target_branch }} ${STATUS}"
276+
if ${{ github.event.inputs.trigger_docker_release == 'true' }} || ${{ github.event.inputs.trigger_packages_release == 'true' }}; then
277+
MESSAGE+=$'\n'$'\n''Releases: '
278+
if ${{ github.event.inputs.trigger_docker_release == 'true' }}; then
279+
MESSAGE+='[Docker](${{ github.server_url }}/${{ github.repository }}-buildtools/actions/runs/${{ env.DOCKER_RUN_ID }}) | '
280+
fi
281+
if ${{ github.event.inputs.trigger_packages_release == 'true' }}; then
282+
MESSAGE+='[DEB](${{ secrets.JENKINS_URL }}/job/production.deb.publish/${{ env.DEB_BUILD_NUMBER }}) | '
283+
MESSAGE+='[RPM](${{ secrets.JENKINS_URL }}/job/production.rpm.publish/${{ env.RPM_BUILD_NUMBER }}) | '
284+
MESSAGE+='[EXE](${{ secrets.JENKINS_URL }}/job/production.exe.publish/${{ env.EXE_BUILD_NUMBER }})'
285+
fi
286+
fi
287+
288+
JSON_PAYLOAD=$(jq -n --arg CHAT_ID "${{ secrets.TELEGRAM_TEAM_CHAT_ID }}" --arg TEXT "${MESSAGE}" \
289+
'{chat_id: $CHAT_ID, text: $TEXT, parse_mode: "Markdown", disable_web_page_preview: true}')
290+
curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
291+
-H "Content-Type: application/json" -d "${JSON_PAYLOAD}"
292+
293+
if "${{ github.event.inputs.trigger_release }}" = "true" && ${{ github.event.inputs.trigger_docker_release == 'true' }} && ${{ github.event.inputs.trigger_packages_release == 'true' }}; then
294+
curl -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage" \
295+
-d chat_id=${{ secrets.TELEGRAM_RELEASE_NOTIFICATION_CHAT_ID }} -d parse_mode=Markdown \
296+
-d "text=ONLYOFFICE DocSpace [${{ github.event.inputs.version }}](${{ github.server_url }}/${{ github.repository }}/blob/master/CHANGELOG.md) for Docker, Linux and Windows has been released"
297+
fi

0 commit comments

Comments
 (0)