Skip to content

Commit 09995c3

Browse files
authored
Eric/cus 10 add automation for deploying to test pypi and prod (#32)
* github actions for test and release automation
1 parent cf17c92 commit 09995c3

File tree

8 files changed

+390
-5
lines changed

8 files changed

+390
-5
lines changed

.github/workflows/docker-stable.yml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Mark Release as Stable
2+
on:
3+
workflow_dispatch:
4+
inputs:
5+
version:
6+
description: 'Version to mark as stable (e.g., 1.2.3)'
7+
required: true
8+
9+
jobs:
10+
stable:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Check if version exists in PyPI
16+
id: version_check
17+
run: |
18+
if ! curl -s -f https://pypi.org/pypi/socketsecurity/${{ inputs.version }}/json > /dev/null; then
19+
echo "Error: Version ${{ inputs.version }} not found on PyPI"
20+
exit 1
21+
fi
22+
echo "Version ${{ inputs.version }} found on PyPI - proceeding with release"
23+
24+
- name: Login to Docker Hub
25+
uses: docker/login-action@v3
26+
with:
27+
username: ${{ secrets.DOCKERHUB_USERNAME }}
28+
password: ${{ secrets.DOCKERHUB_TOKEN }}
29+
30+
- name: Build & Push Stable Docker
31+
uses: docker/build-push-action@v5
32+
with:
33+
push: true
34+
platforms: linux/amd64,linux/arm64
35+
tags: socketdev/cli:stable
36+
build-args: |
37+
CLI_VERSION=${{ inputs.version }}

.github/workflows/pr-preview.yml

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
name: PR Preview
2+
on:
3+
pull_request:
4+
types: [opened, synchronize]
5+
6+
jobs:
7+
preview:
8+
runs-on: ubuntu-latest
9+
steps:
10+
- uses: actions/checkout@v4
11+
- uses: actions/setup-python@v5
12+
with:
13+
python-version: '3.x'
14+
15+
- name: Set preview version
16+
run: |
17+
BASE_VERSION=$(grep -o "__version__.*" socketsecurity/__init__.py | awk '{print $3}' | tr -d "'")
18+
PREVIEW_VERSION="${BASE_VERSION}.dev${{ github.event.pull_request.number }}${{ github.event.pull_request.commits }}"
19+
echo "VERSION=${PREVIEW_VERSION}" >> $GITHUB_ENV
20+
21+
# Update version in __init__.py
22+
echo "__version__ = \"${PREVIEW_VERSION}\"" > socketsecurity/__init__.py.tmp
23+
cat socketsecurity/__init__.py | grep -v "__version__" >> socketsecurity/__init__.py.tmp
24+
mv socketsecurity/__init__.py.tmp socketsecurity/__init__.py
25+
26+
# Verify the change
27+
echo "Updated version in __init__.py:"
28+
cat socketsecurity/__init__.py | grep "__version__"
29+
30+
- name: Check if version exists on Test PyPI
31+
id: version_check
32+
env:
33+
VERSION: ${{ env.VERSION }}
34+
run: |
35+
if curl -s -f https://test.pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then
36+
echo "Version ${VERSION} already exists on Test PyPI"
37+
echo "exists=true" >> $GITHUB_OUTPUT
38+
else
39+
echo "Version ${VERSION} not found on Test PyPI"
40+
echo "exists=false" >> $GITHUB_OUTPUT
41+
fi
42+
43+
- name: Build package
44+
if: steps.version_check.outputs.exists != 'true'
45+
run: |
46+
pip install build
47+
python -m build
48+
49+
- name: Restore original version
50+
if: always()
51+
run: |
52+
BASE_VERSION=$(echo $VERSION | cut -d'.' -f1-3)
53+
echo "__version__ = \"${BASE_VERSION}\"" > socketsecurity/__init__.py.tmp
54+
cat socketsecurity/__init__.py | grep -v "__version__" >> socketsecurity/__init__.py.tmp
55+
mv socketsecurity/__init__.py.tmp socketsecurity/__init__.py
56+
57+
- name: Publish to Test PyPI
58+
if: steps.version_check.outputs.exists != 'true'
59+
uses: pypa/[email protected]
60+
with:
61+
repository-url: https://test.pypi.org/legacy/
62+
password: ${{ secrets.TEST_PYPI_TOKEN }}
63+
verbose: true
64+
65+
- name: Comment on PR
66+
if: steps.version_check.outputs.exists != 'true'
67+
uses: actions/github-script@v7
68+
env:
69+
VERSION: ${{ env.VERSION }}
70+
with:
71+
script: |
72+
const version = process.env.VERSION;
73+
const prNumber = context.payload.pull_request.number;
74+
const owner = context.repo.owner;
75+
const repo = context.repo.repo;
76+
// Find existing bot comments
77+
const comments = await github.rest.issues.listComments({
78+
owner: context.repo.owner,
79+
repo: context.repo.repo,
80+
issue_number: prNumber,
81+
});
82+
83+
const botComment = comments.data.find(comment =>
84+
comment.user.type === 'Bot' &&
85+
comment.body.includes('🚀 Preview package published!')
86+
);
87+
88+
const comment = `
89+
🚀 Preview package published!
90+
91+
Install with:
92+
\`\`\`bash
93+
pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple socketsecurity==${version}
94+
\`\`\`
95+
96+
Docker image: \`socketdev/cli:pr-${prNumber}\`
97+
`;
98+
99+
if (botComment) {
100+
// Update existing comment
101+
await github.rest.issues.updateComment({
102+
owner: owner,
103+
repo: repo,
104+
comment_id: botComment.id,
105+
body: comment
106+
});
107+
} else {
108+
// Create new comment
109+
await github.rest.issues.createComment({
110+
owner: owner,
111+
repo: repo,
112+
issue_number: prNumber,
113+
body: comment
114+
});
115+
}
116+
117+
- name: Verify package is available
118+
if: steps.version_check.outputs.exists != 'true'
119+
id: verify_package
120+
env:
121+
VERSION: ${{ env.VERSION }}
122+
run: |
123+
for i in {1..30}; do
124+
if pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple socketsecurity==${VERSION}; then
125+
echo "Package ${VERSION} is now available and installable on Test PyPI"
126+
pip uninstall -y socketsecurity
127+
echo "success=true" >> $GITHUB_OUTPUT
128+
exit 0
129+
fi
130+
echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/30)"
131+
sleep 20
132+
done
133+
echo "success=false" >> $GITHUB_OUTPUT
134+
exit 1
135+
136+
- name: Login to Docker Hub
137+
if: steps.verify_package.outputs.success == 'true'
138+
uses: docker/login-action@v3
139+
with:
140+
username: ${{ secrets.DOCKERHUB_USERNAME }}
141+
password: ${{ secrets.DOCKERHUB_TOKEN }}
142+
143+
- name: Build & Push Docker Preview
144+
if: steps.verify_package.outputs.success == 'true'
145+
uses: docker/build-push-action@v5
146+
env:
147+
VERSION: ${{ env.VERSION }}
148+
with:
149+
push: true
150+
tags: socketdev/cli:pr-${{ github.event.pull_request.number }}
151+
build-args: |
152+
CLI_VERSION=${{ env.VERSION }}
153+
PIP_INDEX_URL=https://test.pypi.org/simple
154+
PIP_EXTRA_INDEX_URL=https://pypi.org/simple

.github/workflows/release.yml

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
name: Release
2+
on:
3+
push:
4+
tags:
5+
- 'v*'
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
- uses: actions/setup-python@v5
13+
with:
14+
python-version: '3.x'
15+
16+
- name: Get Version
17+
id: version
18+
run: |
19+
RAW_VERSION=$(python -c "from socketsecurity import __version__; print(__version__)")
20+
echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV
21+
if [ "v$RAW_VERSION" != "${{ github.ref_name }}" ]; then
22+
echo "Error: Git tag (${{ github.ref_name }}) does not match package version (v$RAW_VERSION)"
23+
exit 1
24+
fi
25+
26+
- name: Check if version exists on PyPI
27+
id: version_check
28+
env:
29+
VERSION: ${{ env.VERSION }}
30+
run: |
31+
if curl -s -f https://pypi.org/pypi/socketsecurity/$VERSION/json > /dev/null; then
32+
echo "Version ${VERSION} already exists on PyPI"
33+
echo "pypi_exists=true" >> $GITHUB_OUTPUT
34+
else
35+
echo "Version ${VERSION} not found on PyPI - proceeding with PyPI deployment"
36+
echo "pypi_exists=false" >> $GITHUB_OUTPUT
37+
fi
38+
39+
- name: Check Docker image existence
40+
id: docker_check
41+
env:
42+
VERSION: ${{ env.VERSION }}
43+
run: |
44+
if curl -s -f "https://hub.docker.com/v2/repositories/socketdev/cli/tags/${{ env.VERSION }}" > /dev/null; then
45+
echo "Docker image socketdev/cli:${VERSION} already exists"
46+
echo "docker_exists=true" >> $GITHUB_OUTPUT
47+
else
48+
echo "docker_exists=false" >> $GITHUB_OUTPUT
49+
fi
50+
51+
- name: Build package
52+
if: steps.version_check.outputs.pypi_exists != 'true'
53+
run: |
54+
pip install build
55+
python -m build
56+
57+
- name: Publish to PyPI
58+
if: steps.version_check.outputs.pypi_exists != 'true'
59+
uses: pypa/[email protected]
60+
with:
61+
password: ${{ secrets.PYPI_TOKEN }}
62+
63+
- name: Login to Docker Hub
64+
uses: docker/login-action@v3
65+
with:
66+
username: ${{ secrets.DOCKERHUB_USERNAME }}
67+
password: ${{ secrets.DOCKERHUB_TOKEN }}
68+
69+
- name: Verify package is installable
70+
id: verify_package
71+
env:
72+
VERSION: ${{ env.VERSION }}
73+
run: |
74+
for i in {1..30}; do
75+
if pip install socketsecurity==${VERSION}; then
76+
echo "Package ${VERSION} is now available and installable on PyPI"
77+
pip uninstall -y socketsecurity
78+
echo "success=true" >> $GITHUB_OUTPUT
79+
exit 0
80+
fi
81+
echo "Attempt $i: Package not yet installable, waiting 20s... (${i}/30)"
82+
sleep 20
83+
done
84+
echo "success=false" >> $GITHUB_OUTPUT
85+
exit 1
86+
87+
- name: Build & Push Docker
88+
if: |
89+
steps.verify_package.outputs.success == 'true' &&
90+
steps.docker_check.outputs.docker_exists != 'true'
91+
uses: docker/build-push-action@v5
92+
env:
93+
VERSION: ${{ env.VERSION }}
94+
with:
95+
push: true
96+
platforms: linux/amd64,linux/arm64
97+
tags: |
98+
socketdev/cli:latest
99+
socketdev/cli:${{ env.VERSION }}

.github/workflows/version-check.yml

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Version Check
2+
on:
3+
pull_request:
4+
types: [opened, synchronize]
5+
paths:
6+
- 'socketsecurity/**'
7+
- 'setup.py'
8+
- 'pyproject.toml'
9+
10+
jobs:
11+
check_version:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
with:
16+
fetch-depth: 0 # Fetch all history for all branches
17+
18+
- name: Check version increment
19+
id: version_check
20+
run: |
21+
# Get version from current PR
22+
PR_VERSION=$(grep -o "__version__.*" socketsecurity/__init__.py | awk '{print $3}' | tr -d "'")
23+
echo "PR_VERSION=$PR_VERSION" >> $GITHUB_ENV
24+
25+
# Get version from main branch
26+
git checkout origin/main
27+
MAIN_VERSION=$(grep -o "__version__.*" socketsecurity/__init__.py | awk '{print $3}' | tr -d "'")
28+
echo "MAIN_VERSION=$MAIN_VERSION" >> $GITHUB_ENV
29+
30+
# Compare versions using Python
31+
python3 -c "
32+
from packaging import version
33+
pr_ver = version.parse('${PR_VERSION}')
34+
main_ver = version.parse('${MAIN_VERSION}')
35+
if pr_ver <= main_ver:
36+
print(f'❌ Version must be incremented! Main: {main_ver}, PR: {pr_ver}')
37+
exit(1)
38+
print(f'✅ Version properly incremented from {main_ver} to {pr_ver}')
39+
"
40+
41+
- name: Manage PR Comment
42+
uses: actions/github-script@v7
43+
if: always()
44+
env:
45+
MAIN_VERSION: ${{ env.MAIN_VERSION }}
46+
PR_VERSION: ${{ env.PR_VERSION }}
47+
CHECK_RESULT: ${{ steps.version_check.outcome }}
48+
with:
49+
script: |
50+
const success = process.env.CHECK_RESULT === 'success';
51+
const prNumber = context.payload.pull_request.number;
52+
const owner = context.repo.owner;
53+
const repo = context.repo.repo;
54+
const comments = await github.rest.issues.listComments({
55+
owner: owner,
56+
repo: repo,
57+
issue_number: prNumber,
58+
});
59+
60+
const versionComment = comments.data.find(comment =>
61+
comment.user.type === 'Bot' &&
62+
comment.body.includes('Version Check')
63+
);
64+
65+
if (versionComment) {
66+
if (success) {
67+
// Delete the warning comment if check passes
68+
await github.rest.issues.deleteComment({
69+
owner: owner,
70+
repo: repo,
71+
comment_id: versionComment.id
72+
});
73+
} else {
74+
// Update existing warning
75+
await github.rest.issues.updateComment({
76+
owner: owner,
77+
repo: repo,
78+
comment_id: versionComment.id,
79+
body: `❌ **Version Check Failed**\n\nPlease increment...`
80+
});
81+
}
82+
} else if (!success) {
83+
// Create new warning comment only if check fails
84+
await github.rest.issues.createComment({
85+
owner: owner,
86+
repo: repo,
87+
issue_number: prNumber,
88+
body: `❌ **Version Check Failed**\n\nPlease increment...`
89+
});
90+
}

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ markdown_security_temp.md
1919
*.pyc
2020
test.py
2121
*.cpython-312.pyc`
22-
file_generator.py
22+
file_generator.py
23+
.env.local

0 commit comments

Comments
 (0)