Skip to content

Commit ce2a532

Browse files
Decouple sync and release actions (#84)
1 parent 7830b31 commit ce2a532

File tree

2 files changed

+167
-93
lines changed

2 files changed

+167
-93
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,24 @@
1-
# LocalStack specific workflow to implement a fully-integrated continuous integration pipeline for our fork
1+
# LocalStack specific workflow to implement a fully-integrated continuous integration pipeline for Moto-Ext
22
# - Rebase this fork based on the latest commit on `main` of upstream
3-
# - Build a Python source and wheel distribution of moto-ext with deterministic versioning
4-
# - Publish the distributions to PyPi
5-
# - Tag the commit in this fork with the new version
6-
# - Create a GitHub release for the new version
73

8-
name: Sync / Release moto-ext
4+
name: Sync moto-ext with upstream
95

106
on:
11-
schedule:
12-
- cron: 0 5 * * MON
137
workflow_dispatch:
148
inputs:
159
dry_run:
16-
description: 'Dry Run?'
10+
description: 'Dry run'
1711
default: true
1812
required: true
1913
type: boolean
2014

21-
# limit concurrency to 1
15+
# Limit concurrency to 1
2216
concurrency:
2317
group: ${{ github.workflow }}
2418

2519
jobs:
26-
sync-build-release-moto-ext:
20+
sync-moto-ext:
2721
runs-on: ubuntu-latest
28-
environment:
29-
name: pypi
30-
url: https://pypi.org/project/moto-ext/
3122
permissions:
3223
contents: write
3324
id-token: write
@@ -50,35 +41,35 @@ jobs:
5041
git config --global user.name 'LocalStack Bot'
5142
git config --global user.email 'localstack-bot@users.noreply.github.com'
5243
git remote set-url origin https://git:${{ secrets.PRO_ACCESS_TOKEN }}@github.com/${{ github.repository }}
53-
44+
5445
# make sure to switch to the `localstack` branch (default / main branch of this fork)
5546
git switch localstack
5647
# add moto upstream as remote
5748
git remote add upstream https://github.com/getmoto/moto.git
5849
# rebase with latest changes
5950
git pull
60-
51+
6152
# Create a custom merge driver which prefers everything from upstream _BUT_ the name and the URL
6253
mkdir -p $HOME/.local/bin
6354
cat > $HOME/.local/bin/git-prefer-theirs-name-url << EOF
6455
#!/bin/bash
6556
set -e
66-
57+
6758
base="\$1"
6859
local="\$2"
6960
remote="\$3"
70-
61+
7162
echo "Executing custom merge driver for base \$base, local \$local, remote \$remote."
72-
63+
7364
# Define keys to keep
7465
KEYS=("name" "url")
75-
66+
7667
# Read files into arrays
7768
mapfile -t REMOTE_LINES < "\$remote"
7869
mapfile -t LOCAL_LINES < "\$local"
79-
70+
8071
echo "merging \$local + \$local + \$remote ..."
81-
72+
8273
# Function to check if a line should be kept (matches any key)
8374
keep_line() {
8475
local line="\$1"
@@ -87,7 +78,7 @@ jobs:
8778
done
8879
return 1
8980
}
90-
81+
9182
# keep key-matched lines from local, others from remote
9283
for i in "\${!LOCAL_LINES[@]}"; do
9384
if keep_line "\${REMOTE_LINES[i]}"; then
@@ -96,22 +87,22 @@ jobs:
9687
echo "\${LOCAL_LINES[i]}"
9788
fi
9889
done > "\$local"
99-
90+
10091
exit 0
10192
EOF
102-
93+
10394
# make the script executable and add it to the PATH
10495
chmod +x $HOME/.local/bin/git-prefer-theirs-name-url
10596
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
106-
97+
10798
# add the merge driver to the git config
10899
cat >> .git/config << EOF
109-
100+
110101
[merge "git-prefer-theirs-name-url"]
111102
name = A driver which resolves merge conflicts on a setup.cfg such that it always takes the local name and url, and everything else from upstream
112103
driver = git-prefer-theirs-name-url %O %A %B
113104
EOF
114-
105+
115106
# define to use the custom merge driver for the setup.cfg
116107
cat > .gitattributes << EOF
117108
setup.cfg merge=git-prefer-theirs-name-url
@@ -121,75 +112,12 @@ jobs:
121112
run: |
122113
git fetch upstream
123114
git rebase -f upstream/master
124-
125-
- name: Determine new version
126-
run: |
127-
echo "Determining new version..."
128-
cat > setuptools.cfg << EOF
129-
[tool.setuptools_scm]
130-
local_scheme = "no-local-version"
131-
version_scheme = "post-release"
132-
EOF
133-
python3 -m venv .venv
134-
source .venv/bin/activate
135-
python3 -m pip install setuptools_scm
136-
NEW_VERSION=$(python3 -m setuptools_scm -c setuptools.cfg)
137-
NEW_VERSION="${NEW_VERSION//dev/post}"
138-
echo "New version is: $NEW_VERSION"
139-
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_ENV
140-
141-
- name: Build Python distributions
142-
# FYI: Checks in this script only work because the -e flag is enabled by default in GitHub actions
143-
run: |
144-
python3 -m pip install build
145-
146-
echo "Setting new version in setup.cfg":
147-
# make sure setup.cfg is not dirty yet
148-
git diff --exit-code setup.cfg
149-
sed -i -E 's/^(version\s*=\s*)("?)[^"]+("?)/\1\2'"$NEW_VERSION"'\3/' setup.cfg
150-
# make sure setup.cfg is dirty now
151-
! git diff --exit-code setup.cfg
152-
153-
echo "Building new version and tagging commit..."
154-
python3 -m build
155-
156-
- name: Tag successful build
157-
run: |
158-
git tag -a $NEW_VERSION -m $NEW_VERSION
159-
160-
- name: Clean up
161-
run: |
162-
git reset --hard
163-
git clean -df
164-
165-
- name: Store built distributions
166-
uses: actions/upload-artifact@v4
167-
with:
168-
name: moto-ext-dists
169-
path: dist/*.*
170-
171-
# publish the package before pushing the tag (this might fail if the version already exists on PyPI)
172-
- name: Publish package distributions to PyPI
173-
if: ${{ github.event.inputs.dry_run != 'true' }}
174-
uses: pypa/gh-action-pypi-publish@release/v1
115+
git log --oneline --graph --abbrev-commit --max-count 20
175116
176117
- name: Push
177118
if: ${{ github.event.inputs.dry_run != 'true' }}
178119
run: |
179120
git push --force-with-lease
180-
git push --atomic origin localstack $NEW_VERSION
121+
git push --atomic origin localstack
181122
env:
182123
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
183-
184-
# Add a retry to avoid issues where the GH CLI fails
185-
# because it does not yet detect the pushed tag.
186-
- name: Create Release
187-
uses: nick-fields/retry@v3
188-
if: ${{ github.event.inputs.dry_run != 'true' }}
189-
env:
190-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
191-
with:
192-
max_attempts: 5
193-
retry_wait_seconds: 120
194-
timeout_minutes: 5
195-
command: gh release create $NEW_VERSION --repo localstack/moto --notes "automatic rebase sync and release"

.github/workflows/ci_release.yml

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# LocalStack specific workflow to implement a fully-integrated continuous integration pipeline for Moto-Ext
2+
# - Guess next patch semantic version
3+
# - Build source and wheel distributions
4+
# - Publish the distributions to PyPi
5+
# - Tag the commit with the new version
6+
# - Create a GitHub release for the new version
7+
8+
name: Release moto-ext
9+
10+
on:
11+
schedule:
12+
- cron: 0 5 * * MON
13+
workflow_dispatch:
14+
inputs:
15+
dry_run:
16+
description: 'Dry run'
17+
default: true
18+
required: true
19+
type: boolean
20+
21+
# Limit concurrency to 1
22+
concurrency:
23+
group: ${{ github.workflow }}
24+
25+
jobs:
26+
analyse-commit-log:
27+
runs-on: ubuntu-latest
28+
outputs:
29+
is-release-required: ${{ steps.check.outputs.new_commits_made }}
30+
last-tag: ${{ steps.last_tag.outputs.tag }}
31+
steps:
32+
- name: Checkout
33+
uses: actions/checkout@v4
34+
with:
35+
fetch-depth: 0
36+
ref: localstack
37+
persist-credentials: false
38+
39+
- name: Get last tag
40+
id: last_tag
41+
uses: "WyriHaximus/github-action-get-previous-tag@v1"
42+
env:
43+
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
44+
45+
- name: Check for new commits since the last tag
46+
id: check
47+
env:
48+
LAST_TAG: ${{ steps.last_tag.outputs.tag }}
49+
run: |
50+
if [ $(git log --oneline $LAST_TAG..HEAD | wc -l) -eq "0" ]; then
51+
echo "No commits since the last tag."
52+
echo "new_commits_made=false" >> $GITHUB_OUTPUT
53+
else
54+
echo "new_commits_made=true" >> $GITHUB_OUTPUT
55+
fi
56+
57+
build-release-moto-ext:
58+
runs-on: ubuntu-latest
59+
needs: analyse-commit-log
60+
if: ${{ needs.analyse-commit-log.outputs.is-release-required == 'true' }}
61+
environment:
62+
name: pypi
63+
url: https://pypi.org/project/moto-ext/
64+
permissions:
65+
contents: write
66+
id-token: write
67+
steps:
68+
- name: Checkout
69+
uses: actions/checkout@v4
70+
with:
71+
fetch-depth: 0
72+
ref: localstack
73+
persist-credentials: false
74+
75+
- name: Setup Python
76+
uses: actions/setup-python@v6
77+
with:
78+
python-version: '3.13'
79+
80+
- name: Guess next version
81+
id: semver
82+
uses: "WyriHaximus/github-action-next-semvers@v1"
83+
with:
84+
version: ${{ needs.analyse-commit-log.outputs.last-tag }}
85+
86+
- name: Ensure guessed version does not exist
87+
env:
88+
NEW_VERSION: ${{ steps.semver.outputs.patch }}
89+
run: |
90+
! git rev-parse $NEW_VERSION || { echo "A tag for $NEW_VERSION already exists" ; exit 1; }
91+
92+
- name: Build Python distributions
93+
env:
94+
NEW_VERSION: ${{ steps.semver.outputs.patch }}
95+
# FYI: Checks in this script only work because the -e flag is enabled by default in GitHub actions
96+
run: |
97+
python3 -m pip install build
98+
99+
# make sure setup.cfg is not dirty yet
100+
git diff --exit-code setup.cfg
101+
102+
echo "Setting new version in setup.cfg":
103+
sed -i -E 's/^(version\s*=\s*)("?)[^"]+("?)/\1\2'"$NEW_VERSION"'\3/' setup.cfg
104+
105+
# make sure setup.cfg is dirty now
106+
! git diff --exit-code setup.cfg
107+
108+
echo "Building distributions"
109+
python3 -m build
110+
111+
- name: Create and push tag
112+
env:
113+
NEW_VERSION: ${{ steps.semver.outputs.patch }}
114+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
115+
if: ${{ github.event.inputs.dry_run != 'true' }}
116+
run: |
117+
git tag -a $NEW_VERSION -m $NEW_VERSION
118+
git push --tags origin localstack
119+
120+
- name: Clean up
121+
run: |
122+
git reset --hard
123+
git clean -df
124+
125+
- name: Upload distributions
126+
uses: actions/upload-artifact@v4
127+
with:
128+
name: moto-ext-dists
129+
path: dist/*.*
130+
131+
# publish the package before pushing the tag (this might fail if the version already exists on PyPI)
132+
- name: Publish package distributions to PyPI
133+
if: ${{ github.event.inputs.dry_run != 'true' }}
134+
uses: pypa/gh-action-pypi-publish@release/v1
135+
136+
# Add a retry to avoid issues where the GH CLI fails because it does not yet detect the pushed tag.
137+
- name: Create Release
138+
uses: nick-fields/retry@v3
139+
if: ${{ github.event.inputs.dry_run != 'true' }}
140+
env:
141+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142+
with:
143+
max_attempts: 5
144+
retry_wait_seconds: 120
145+
timeout_minutes: 5
146+
command: gh release create $NEW_VERSION --repo localstack/moto --notes "automatic release"

0 commit comments

Comments
 (0)