Skip to content

Commit 662f5b9

Browse files
authored
add CI workflow to sync, build, publish, release moto-ext (#78)
1 parent 75e0721 commit 662f5b9

File tree

1 file changed

+185
-0
lines changed

1 file changed

+185
-0
lines changed

.github/workflows/ci.yml

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# LocalStack specific workflow to implement a fully-integrated continuous integration pipeline for our fork
2+
# - 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
7+
8+
name: Sync / 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+
sync-build-release-moto-ext:
27+
runs-on: ubuntu-latest
28+
environment:
29+
name: pypi
30+
url: https://pypi.org/project/moto-ext/
31+
permissions:
32+
contents: write
33+
id-token: write
34+
steps:
35+
- name: Checkout
36+
uses: actions/checkout@v4
37+
with:
38+
fetch-depth: 0
39+
ref: localstack
40+
persist-credentials: false
41+
42+
- name: Setup Python
43+
uses: actions/setup-python@v6
44+
with:
45+
python-version: '3.13'
46+
47+
- name: Configure Git
48+
run: |
49+
# Configure git
50+
git config --global user.name 'LocalStack Bot'
51+
git config --global user.email 'localstack-bot@users.noreply.github.com'
52+
git remote set-url origin https://git:${{ secrets.PRO_ACCESS_TOKEN }}@github.com/${{ github.repository }}
53+
54+
# make sure to switch to the `localstack` branch (default / main branch of this fork)
55+
git switch localstack
56+
# add moto upstream as remote
57+
git remote add upstream https://github.com/getmoto/moto.git
58+
# rebase with latest changes
59+
git pull
60+
61+
# Create a custom merge driver which prefers everything from upstream _BUT_ the name and the URL
62+
mkdir -p $HOME/.local/bin
63+
cat > $HOME/.local/bin/git-prefer-theirs-name-url << EOF
64+
#!/bin/bash
65+
set -e
66+
67+
base="\$1"
68+
local="\$2"
69+
remote="\$3"
70+
71+
echo "Executing custom merge driver for base \$base, local \$local, remote \$remote."
72+
73+
# Define keys to keep
74+
KEYS=("name" "url")
75+
76+
# Read files into arrays
77+
mapfile -t REMOTE_LINES < "\$remote"
78+
mapfile -t LOCAL_LINES < "\$local"
79+
80+
echo "merging \$local + \$local + \$remote ..."
81+
82+
# Function to check if a line should be kept (matches any key)
83+
keep_line() {
84+
local line="\$1"
85+
for key in "\${KEYS[@]}"; do
86+
[[ "\$line" == *"\$key"* ]] && return 0
87+
done
88+
return 1
89+
}
90+
91+
# keep key-matched lines from local, others from remote
92+
for i in "\${!LOCAL_LINES[@]}"; do
93+
if keep_line "\${REMOTE_LINES[i]}"; then
94+
echo "\${REMOTE_LINES[i]}"
95+
else
96+
echo "\${LOCAL_LINES[i]}"
97+
fi
98+
done > "\$local"
99+
100+
exit 0
101+
EOF
102+
103+
# make the script executable and add it to the PATH
104+
chmod +x $HOME/.local/bin/git-prefer-theirs-name-url
105+
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
106+
107+
# add the merge driver to the git config
108+
cat >> .git/config << EOF
109+
110+
[merge "git-prefer-theirs-name-url"]
111+
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
112+
driver = git-prefer-theirs-name-url %O %A %B
113+
EOF
114+
115+
# define to use the custom merge driver for the setup.cfg
116+
cat > .gitattributes << EOF
117+
setup.cfg merge=git-prefer-theirs-name-url
118+
EOF
119+
120+
- name: Rebase localstack branch with latest master from upstream
121+
run: |
122+
git fetch upstream
123+
git rebase 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 == false }}
174+
uses: pypa/gh-action-pypi-publish@release/v1
175+
176+
- name: Push & Create Release
177+
if: ${{ github.event.inputs.dry_run == false }}
178+
run: |
179+
git push --force-with-lease
180+
git push --atomic origin localstack $NEW_VERSION
181+
# Wait a few seconds to avoid "gh release create" fail because the tag is not yet available
182+
sleep 10
183+
gh release create $NEW_VERSION --notes "automatic rebase sync and release"
184+
env:
185+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)