Skip to content

Commit 46256a1

Browse files
committed
APP-9140 | Testing chainguard optimized image
1 parent 5208083 commit 46256a1

3 files changed

Lines changed: 134 additions & 79 deletions

File tree

.github/workflows/pyatlan-chainguard.yaml

Lines changed: 23 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,7 @@ jobs:
7373
BUILD_TYPE="release"
7474
PYTHON_VERSION="3.13"
7575
PYATLAN_VERSION=$(cat pyatlan/version.txt)
76-
PYATLAN_BRANCH=""
77-
INSTALL_FROM_GIT="false"
78-
echo "Release trigger detected - using latest versions"
76+
echo "Release trigger detected - building from git tag v${PYATLAN_VERSION}"
7977
else
8078
# Manual dispatch: use provided inputs with defaults
8179
BUILD_TYPE="${{ github.event.inputs.build_type }}"
@@ -86,33 +84,28 @@ jobs:
8684
else
8785
PYTHON_VERSION="${{ github.event.inputs.python_version }}"
8886
fi
89-
90-
# Check if branch is provided (overrides version)
91-
if [ -n "${{ github.event.inputs.pyatlan_branch }}" ]; then
92-
PYATLAN_BRANCH="${{ github.event.inputs.pyatlan_branch }}"
93-
PYATLAN_VERSION="branch-${PYATLAN_BRANCH}"
94-
INSTALL_FROM_GIT="true"
95-
echo "Using pyatlan branch: $PYATLAN_BRANCH"
87+
88+
# Set Pyatlan version (reads from pyatlan/version.txt by default)
89+
# This matches the version in the repository
90+
if [ -z "${{ github.event.inputs.pyatlan_version }}" ]; then
91+
PYATLAN_VERSION=$(cat pyatlan/version.txt)
92+
echo "Using pyatlan version from version.txt: $PYATLAN_VERSION"
9693
else
97-
# Set Pyatlan version (default to version.txt if empty)
98-
if [ -z "${{ github.event.inputs.pyatlan_version }}" ]; then
99-
PYATLAN_VERSION=$(cat pyatlan/version.txt)
100-
echo "Using pyatlan version from version.txt: $PYATLAN_VERSION"
101-
else
102-
PYATLAN_VERSION="${{ github.event.inputs.pyatlan_version }}"
103-
echo "Using specified pyatlan version: $PYATLAN_VERSION"
104-
fi
105-
PYATLAN_BRANCH=""
106-
INSTALL_FROM_GIT="false"
94+
PYATLAN_VERSION="${{ github.event.inputs.pyatlan_version }}"
95+
echo "Using specified pyatlan version: $PYATLAN_VERSION"
96+
fi
97+
98+
# Handle branch input (if provided, will be used as commit SHA or branch name)
99+
if [ -n "${{ github.event.inputs.pyatlan_branch }}" ]; then
100+
echo "Note: Building from branch/commit: ${{ github.event.inputs.pyatlan_branch }}"
101+
echo "This will use PYATLAN_COMMIT_SHA build arg"
107102
fi
108103
fi
109104
110105
echo "BUILD_TYPE=$BUILD_TYPE" >> $GITHUB_ENV
111106
echo "PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV
112107
echo "PYATLAN_VERSION=$PYATLAN_VERSION" >> $GITHUB_ENV
113-
echo "PYATLAN_BRANCH=$PYATLAN_BRANCH" >> $GITHUB_ENV
114-
echo "INSTALL_FROM_GIT=$INSTALL_FROM_GIT" >> $GITHUB_ENV
115-
108+
116109
# Set platforms based on build type
117110
if [ "$BUILD_TYPE" = "dev" ]; then
118111
PLATFORMS="linux/amd64"
@@ -129,13 +122,8 @@ jobs:
129122
echo " - Trigger: ${{ github.event_name }}"
130123
echo " - Build Type: $BUILD_TYPE"
131124
echo " - Python Version: $PYTHON_VERSION"
132-
echo " - Pyatlan Version: $PYATLAN_VERSION"
133-
if [ "$INSTALL_FROM_GIT" = "true" ]; then
134-
echo " - Pyatlan Branch: $PYATLAN_BRANCH"
135-
echo " - Install Method: Git (development build)"
136-
else
137-
echo " - Install Method: PyPI (stable release)"
138-
fi
125+
echo " - Pyatlan Version: $PYATLAN_VERSION (from git tag v${PYATLAN_VERSION})"
126+
echo " - Build Method: Multi-stage build from GitHub source"
139127
echo " - Platforms: $PLATFORMS"
140128
echo " - Commit Hash: $COMMIT_HASH"
141129
@@ -159,22 +147,6 @@ jobs:
159147
160148
echo "Generated image tag: ghcr.io/atlanhq/pyatlan-chainguard-base:$IMAGE_TAG"
161149
162-
- name: Wait for PyPI availability
163-
if: env.INSTALL_FROM_GIT == 'false' && (github.event.inputs.pyatlan_version != '' || github.event_name == 'release')
164-
uses: nick-fields/retry@v3
165-
with:
166-
max_attempts: 5
167-
timeout_minutes: 3
168-
command: |
169-
echo "Checking if pyatlan==${{ env.PYATLAN_VERSION }} is available on PyPI..."
170-
if pip index versions pyatlan | grep -q "${{ env.PYATLAN_VERSION }}"; then
171-
echo "Package is available on PyPI!"
172-
exit 0
173-
else
174-
echo "Package not available yet. Retrying..."
175-
exit 1
176-
fi
177-
178150
- name: Build and push Docker image
179151
uses: docker/build-push-action@v6
180152
with:
@@ -187,8 +159,7 @@ jobs:
187159
build-args: |
188160
PYTHON_VERSION=${{ env.PYTHON_VERSION }}
189161
PYATLAN_VERSION=${{ env.PYATLAN_VERSION }}
190-
PYATLAN_BRANCH=${{ env.PYATLAN_BRANCH }}
191-
INSTALL_FROM_GIT=${{ env.INSTALL_FROM_GIT }}
162+
PYATLAN_COMMIT_SHA=${{ env.COMMIT_HASH }}
192163
cache-from: type=gha
193164
cache-to: type=gha,mode=max
194165

@@ -197,17 +168,13 @@ jobs:
197168
echo "## 🐳 Chainguard Docker Image Built Successfully!" >> $GITHUB_STEP_SUMMARY
198169
echo "" >> $GITHUB_STEP_SUMMARY
199170
echo "### Image Details:" >> $GITHUB_STEP_SUMMARY
200-
echo "- **Base Image:** Chainguard" >> $GITHUB_STEP_SUMMARY
171+
echo "- **Base Image:** Chainguard pyatlan-golden:3.11" >> $GITHUB_STEP_SUMMARY
201172
echo "- **Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
202173
echo "- **Build Type:** ${{ env.BUILD_TYPE }}" >> $GITHUB_STEP_SUMMARY
174+
echo "- **Build Method:** Multi-stage from GitHub source (no PyPI)" >> $GITHUB_STEP_SUMMARY
203175
echo "- **Python Version:** ${{ env.PYTHON_VERSION }}" >> $GITHUB_STEP_SUMMARY
204-
echo "- **Pyatlan Version:** ${{ env.PYATLAN_VERSION }}" >> $GITHUB_STEP_SUMMARY
205-
if [ "${{ env.INSTALL_FROM_GIT }}" = "true" ]; then
206-
echo "- **Pyatlan Branch:** ${{ env.PYATLAN_BRANCH }}" >> $GITHUB_STEP_SUMMARY
207-
echo "- **Install Method:** Git (development build)" >> $GITHUB_STEP_SUMMARY
208-
else
209-
echo "- **Install Method:** PyPI (stable release)" >> $GITHUB_STEP_SUMMARY
210-
fi
176+
echo "- **Pyatlan Version:** ${{ env.PYATLAN_VERSION }} (git tag v${{ env.PYATLAN_VERSION }})" >> $GITHUB_STEP_SUMMARY
177+
echo "- **Dependencies:** Hardened from Chainguard APK + minimal pip" >> $GITHUB_STEP_SUMMARY
211178
echo "- **Platforms:** ${{ env.PLATFORMS }}" >> $GITHUB_STEP_SUMMARY
212179
if [ "${{ env.BUILD_TYPE }}" = "dev" ]; then
213180
echo "- **Commit Hash:** ${{ env.COMMIT_HASH }}" >> $GITHUB_STEP_SUMMARY

Dockerfile.chainguard

Lines changed: 111 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,129 @@
1+
# ============================================
2+
# Stage 1: Builder - Clone pyatlan from GitHub
3+
# ============================================
4+
FROM cgr.dev/atlan.com/pyatlan-golden:3.11 AS builder
5+
6+
# Build arguments
7+
# PYATLAN_VERSION should be passed via --build-arg
8+
# Default to "latest" if not specified (workflow reads from version.txt)
9+
ARG PYATLAN_VERSION=latest
10+
ARG PYATLAN_COMMIT_SHA=""
11+
12+
USER root
13+
WORKDIR /tmp/build
14+
15+
# Clone pyatlan from GitHub
16+
RUN echo "=== Cloning pyatlan from GitHub ===" && \
17+
git clone https://github.com/atlanhq/atlan-python.git /tmp/build/atlan-python
18+
19+
# Checkout specific version or commit
20+
RUN cd /tmp/build/atlan-python && \
21+
if [ -n "$PYATLAN_COMMIT_SHA" ]; then \
22+
echo "Checking out commit: $PYATLAN_COMMIT_SHA" && \
23+
git checkout "$PYATLAN_COMMIT_SHA"; \
24+
elif [ "$PYATLAN_VERSION" = "latest" ]; then \
25+
echo "Using latest from main branch"; \
26+
else \
27+
echo "Checking out version: v$PYATLAN_VERSION" && \
28+
(git checkout "v$PYATLAN_VERSION" || git checkout "$PYATLAN_VERSION"); \
29+
fi && \
30+
echo "Commit: $(git rev-parse HEAD)" && \
31+
echo "Version: $(cat pyatlan/version.txt)"
32+
33+
# ============================================
34+
# Stage 2: Final - Runtime image
35+
# ============================================
136
FROM cgr.dev/atlan.com/pyatlan-golden:3.11
237

3-
# Build arguments for configurable versions
38+
# Build arguments for labels
439
ARG PYTHON_VERSION=3.11
5-
ARG PYATLAN_VERSION=latest
6-
ARG PYATLAN_BRANCH=""
7-
ARG INSTALL_FROM_GIT=false
40+
ARG PYATLAN_COMMIT_SHA=""
841

42+
# Copy version.txt from builder and set PYATLAN_VERSION if not provided
43+
COPY --from=builder /tmp/build/atlan-python/pyatlan/version.txt /tmp/version.txt
44+
ARG PYATLAN_VERSION
45+
RUN if [ -z "$PYATLAN_VERSION" ]; then \
46+
PYATLAN_VERSION=$(cat /tmp/version.txt | tr -d '\n\r'); \
47+
fi && \
48+
echo "Using PYATLAN_VERSION: $PYATLAN_VERSION" && \
49+
echo "$PYATLAN_VERSION" > /tmp/final_version.txt
50+
51+
USER root
952
WORKDIR /app
1053

1154
# Set UV environment variables
12-
# REMOVE THIS WHEN https://github.com/astral-sh/uv/issues/8635 IS FIXED
1355
ENV UV_NO_MANAGED_PYTHON=true \
1456
UV_SYSTEM_PYTHON=true
1557

16-
# Switch to root for installation
17-
USER root
58+
# Install pyatlan dependencies from Chainguard APK (hardened packages)
59+
RUN apk add --no-cache \
60+
py3.11-pydantic=2.12.0-r0 \
61+
py3.11-pydantic-core=2.41.1-r0 \
62+
py3.11-jinja2=3.1.6-r1 \
63+
py3.11-tenacity=9.1.2-r2 \
64+
py3.11-pytz=2025.2-r2 \
65+
py3.11-python-dateutil=2.9.0-r10 \
66+
py3.11-pyyaml=6.0.3-r0 \
67+
py3.11-httpx=0.28.1-r2
1868

19-
# Install pyatlan - either from PyPI or git branch
20-
RUN echo "Installing pyatlan==$PYATLAN_VERSION from PyPI"; \
21-
uv pip install --system pyatlan==$PYATLAN_VERSION;
69+
# Install dependencies and pyatlan in ONE layer (copy + install + cleanup)
70+
# This prevents Docker from keeping source files in image layers
71+
RUN --mount=type=bind,from=builder,source=/tmp/build/atlan-python,target=/mnt/source,readonly \
72+
# Copy source to writable location
73+
cp -r /mnt/source /tmp/pyatlan-source && \
74+
# Install non-APK dependencies
75+
uv pip install --system --no-cache \
76+
lazy_loader~=0.4 \
77+
nanoid~=2.0.0 \
78+
httpx-retries~=0.4.0 && \
79+
# Install pyatlan with --no-deps
80+
cd /tmp/pyatlan-source && \
81+
uv pip install --system --no-cache --no-deps . && \
82+
# Cleanup everything in same layer (source + caches + bytecode)
83+
cd / && rm -rf /tmp/pyatlan-source /root/.cache ~/.cache && \
84+
rm -rf /tmp/version.txt /tmp/final_version.txt && \
85+
find /usr/lib/python3.11 -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true && \
86+
find /usr/lib/python3.11 -type f -name '*.pyc' -delete 2>/dev/null || true
2287

23-
# Add build information as labels
24-
RUN if [ "$INSTALL_FROM_GIT" = "true" ]; then \
25-
echo "LABEL pyatlan.source=git" >> /tmp/labels && \
26-
echo "LABEL pyatlan.branch=$PYATLAN_BRANCH" >> /tmp/labels; \
27-
else \
28-
echo "LABEL pyatlan.source=pypi" >> /tmp/labels && \
29-
echo "LABEL pyatlan.version=$PYATLAN_VERSION" >> /tmp/labels; \
30-
fi && \
31-
echo "LABEL python.version=$PYTHON_VERSION" >> /tmp/labels
88+
# Verify installation and version
89+
RUN EXPECTED_VERSION="$(cat /tmp/final_version.txt)" python3 <<'EOF'
90+
import sys
91+
import os
92+
import pyatlan
93+
from pyatlan.client.atlan import AtlanClient
94+
95+
expected_version = os.environ.get("EXPECTED_VERSION", "unknown")
96+
97+
print("=== Pyatlan Installation Verification ===")
98+
print(f"Installed version: {pyatlan.__version__}")
99+
print(f"Expected version: {expected_version}")
100+
101+
# Version validation (skip if 'latest' was requested)
102+
if expected_version != "latest":
103+
if pyatlan.__version__ != expected_version:
104+
print(f"❌ ERROR: Version mismatch!")
105+
print(f" Expected: {expected_version}")
106+
print(f" Got: {pyatlan.__version__}")
107+
sys.exit(1)
108+
109+
print("✅ Version verified")
110+
print("✅ AtlanClient imported successfully")
111+
print("=== Build verification passed ===")
112+
EOF
113+
114+
# Add build metadata as labels
115+
LABEL python.version="${PYTHON_VERSION}"
116+
LABEL pyatlan.commit_sha="${PYATLAN_COMMIT_SHA}"
117+
# Note: pyatlan.version label should be set by passing --build-arg PYATLAN_VERSION=$(cat pyatlan/version.txt) during build
118+
LABEL build.method="git-multistage"
119+
LABEL build.source="github"
120+
LABEL security.approach="apk-first-no-pypi"
32121

33-
# Switch back to nonroot user
122+
# Switch to nonroot user for runtime security
34123
USER nonroot
35124

36-
# Default working directory for applications
125+
# Default working directory
37126
WORKDIR /app
38127

39-
# Default command (can be overridden by extending images)
128+
# Default command
40129
CMD ["python"]

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ Repository = "https://github.com/atlanhq/atlan-python"
4646
Documentation = "https://github.com/atlanhq/atlan-python"
4747
Issues = "https://github.com/atlanhq/atlan-python/issues"
4848

49-
[dependency-groups]
5049
dev = [
5150
"mypy~=1.18.0",
5251
"ruff~=0.14.5",

0 commit comments

Comments
 (0)