11name : Docker Build & Push All Services
22
33on :
4+ delete :
45 push :
56 tags :
67 - " v*.*.*"
2021 - name : 🧠 Discover components and services
2122 id : set-matrix
2223 run : |
23- # Customize these lists as needed
24- EXCLUDED_COMPONENTS=(core validator)
25- EXCLUDED_SERVICES=()
26-
27- should_exclude() {
28- local item=$1
29- shift
30- local list=("$@")
31- for ex in "${list[@]}"; do
32- if [[ "$item" == "$ex" ]]; then
33- return 0
34- fi
35- done
36- return 1
37- }
38-
3924 mkdir -p .build/tmp_matrix
4025 echo '{ "include": [' > .build/tmp_matrix/matrix.json
4126 FIRST=true
@@ -44,32 +29,15 @@ jobs:
4429 [ -d "$comp" ] || continue
4530 comp_name=$(basename "$comp")
4631
47- # 🔥 Skip excluded components
48- if should_exclude "$comp_name" "${EXCLUDED_COMPONENTS[@]}"; then
49- echo "⏭️ Skipping excluded component: $comp_name"
50- continue
51- fi
52-
53- for service in "$comp"/*; do
54- [ -d "$service" ] || continue
55- service_name=$(basename "$service")
56-
57- # 🔥 Skip excluded services
58- if should_exclude "$service_name" "${EXCLUDED_SERVICES[@]}"; then
59- echo "⏭️ Skipping excluded service: $service_name"
60- continue
61- fi
62-
63- # ✅ Include only if it has a pyproject or version.py
64- if [[ -f "$service/pyproject.toml" || -f "$service/version.py" ]]; then
65- if [ "$FIRST" = true ]; then
66- FIRST=false
67- else
68- echo "," >> .build/tmp_matrix/matrix.json
69- fi
70- echo " { \"component\": \"$comp_name\", \"service\": \"$service_name\" }" >> .build/tmp_matrix/matrix.json
32+ # ✅ Include only if it has a pyproject or version.py
33+ if [[ -f "$comp/pyproject.toml" || -f "$comp/version.py" ]]; then
34+ if [ "$FIRST" = true ]; then
35+ FIRST=false
36+ else
37+ echo "," >> .build/tmp_matrix/matrix.json
7138 fi
72- done
39+ echo " { \"component\": \"$comp_name\" }" >> .build/tmp_matrix/matrix.json
40+ fi
7341 done
7442
7543 echo "] }" >> .build/tmp_matrix/matrix.json
@@ -80,22 +48,26 @@ jobs:
8048
8149 echo "🔍 Final matrix ready."
8250
83- wheel-builder :
84- if : github.event_name == 'push'
51+ build :
52+ if : github.event_name == 'push' || github.event_name == 'delete'
53+ needs : [discover]
8554 runs-on : ubuntu-latest
86- outputs :
87- tag : ${{ steps.meta.outputs.tag }}
55+ permissions :
56+ contents : read
57+ packages : write
58+ strategy :
59+ matrix :
60+ include : ${{ fromJson(needs.discover.outputs.matrix).include }}
61+
8862 steps :
89- - name : 🧾 Checkout
63+ - name : 🧾 Checkout repository
9064 uses : actions/checkout@v3
9165
9266 - name : 🛠 Set up QEMU
9367 uses : docker/setup-qemu-action@v2
9468
95- - name : 🛠 Set up Docker Buildx
69+ - name : 🧱 Set up Docker Buildx
9670 uses : docker/setup-buildx-action@v2
97- with :
98- driver-opts : network=host
9971
10072 - name : 🔐 Docker Login
10173 uses : docker/login-action@v2
@@ -110,44 +82,25 @@ jobs:
11082 echo "tag=subvortex/subvortex-wheel-builder:3.11-$HASH" >> $GITHUB_OUTPUT
11183
11284 - name : 🐋 Build & push wheel-builder (only if not exists)
85+ id : wheelbuilder
11386 run : |
114- if docker pull ${{ steps.meta.outputs.tag }} >/dev/null 2>&1; then
115- echo "✅ Image already exists: ${{ steps.meta.outputs.tag }}"
87+ TAG="${{ steps.meta.outputs.tag }}"
88+ LATEST_TAG="subvortex/subvortex-wheel-builder:latest"
89+
90+ if docker pull "$TAG" >/dev/null 2>&1; then
91+ echo "✅ Image already exists: $TAG"
11692 else
11793 echo "🚀 Building wheel-builder image"
11894 docker buildx build \
119- --platform linux/amd64,linux/arm64 \
120- --tag ${{ steps.meta.outputs.tag }} \
95+ --platform linux/amd64 \
96+ --tag "$TAG" \
97+ --tag "$LATEST_TAG" \
12198 --file subvortex/core/Dockerfile.builder \
12299 --push \
123100 .
124101 fi
125102
126- build :
127- needs : [discover, wheel-builder]
128- runs-on : ubuntu-latest
129- permissions :
130- contents : read
131- packages : write
132- strategy :
133- matrix :
134- include : ${{ fromJson(needs.discover.outputs.matrix).include }}
135-
136- steps :
137- - name : 🧾 Checkout repository
138- uses : actions/checkout@v3
139-
140- - name : 🛠 Set up QEMU
141- uses : docker/setup-qemu-action@v2
142-
143- - name : 🧱 Set up Docker Buildx
144- uses : docker/setup-buildx-action@v2
145-
146- - name : 🔐 Docker Login
147- uses : docker/login-action@v2
148- with :
149- username : ${{ secrets.DOCKER_USERNAME }}
150- password : ${{ secrets.DOCKER_PASSWORD }}
103+ echo "tag=$TAG" >> $GITHUB_OUTPUT
151104
152105 - name : 🧠 Determine tag and floating tags
153106 id : taginfo
@@ -166,30 +119,48 @@ jobs:
166119 - name : 🚀 Build and push version-tagged image (on tag push only)
167120 if : startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'
168121 run : |
169- COMP=${{ matrix.component }}
170- SERVICE=${{ matrix.service }}
171- IMAGE="subvortex/subvortex-$COMP-$SERVICE"
172- WHEEL_IMAGE="${{ needs.wheel-builder.outputs.tag }}"
173- RAW_VERSION_TAG="${{ steps.taginfo.outputs.version_tag }}"
174- VERSION_TAG="${RAW_VERSION_TAG#v}"
175- DOCKERFILE="subvortex/$COMP/$SERVICE/Dockerfile"
122+ COMP="${{ matrix.component }}"
123+ REPO_NAME="subvortex-${COMP//_/-}" && echo "$COMP"
124+ IMAGE="subvortex/$REPO_NAME"
125+ WHEEL_IMAGE="${{ steps.meta.outputs.tag }}"
126+ VERSION_TAG="${{ steps.taginfo.outputs.version_tag }}"
127+ VERSION="${VERSION_TAG#v}"
128+ DOCKERFILE="subvortex/$COMP/Dockerfile"
129+
130+ echo "🔍 Searching for component version... $COMP / $IMAGE"
131+ COMPONENT_PATH="subvortex/$COMP"
132+ if [ -f "$COMPONENT_PATH/pyproject.toml" ]; then
133+ echo "✅ Found pyproject.toml"
134+ COMPONENT_VERSION=$(grep -E '^version\s*=' "$COMPONENT_PATH/pyproject.toml" | head -1 | sed -E 's/version\s*=\s*"([^"]+)"/\1/')
135+ elif [ -f "$COMPONENT_PATH/version.py" ]; then
136+ echo "✅ Found version.py"
137+ COMPONENT_VERSION=$(python -c "import ast; f=open('$COMPONENT_PATH/version.py'); print([n.value.s for n in ast.walk(ast.parse(f.read())) if isinstance(n, ast.Assign) and n.targets[0].id == '__version__'][0])")
138+ else
139+ echo "❌ No version file found for component"
140+ exit 1
141+ fi
142+
143+ echo "🧾 Final versions:"
144+ echo "VERSION=$VERSION"
145+ echo "COMPONENT_VERSION=$COMPONENT_VERSION"
176146
177- echo "🚀 Building image $IMAGE:$VERSION_TAG "
147+ echo "🚀 Building image $IMAGE:$VERSION "
178148
179149 docker buildx build \
180150 --squash \
181- --platform linux/amd64,linux/arm64 \
151+ --platform linux/amd64 \
182152 --build-context wheelbuilder=docker-image://$WHEEL_IMAGE \
183- --build-arg VERSION=$VERSION_TAG \
184- --build-arg COMPONENT_VERSION=$VERSION_TAG \
185- --cache-from=type=gha,scope=wheels_${COMP}_${SERVICE}_${ ARCH} \
186- --cache-to=type=gha,mode=max,scope=wheels_${COMP}_${SERVICE}_${ ARCH} \
187- --tag $IMAGE:$VERSION_TAG \
153+ --build-arg VERSION=$VERSION \
154+ --build-arg COMPONENT_VERSION=$COMPONENT_VERSION \
155+ --cache-from=type=gha,scope=wheels_${COMP}_${ARCH} \
156+ --cache-to=type=gha,mode=max,scope=wheels_${COMP}_${ARCH} \
157+ --tag $IMAGE:$VERSION \
188158 --file $DOCKERFILE \
189159 --push \
190160 .
191161
192162 release :
163+ if : github.event_name == 'release'
193164 needs : [discover]
194165 runs-on : ubuntu-latest
195166 permissions :
@@ -232,54 +203,53 @@ jobs:
232203 - name : 🚀 Retag and push floating tags (on release or prerelease)
233204 if : github.event_name == 'release' && github.event.action != 'deleted'
234205 run : |
235- COMP=${{ matrix.component }}
236- SERVICE=${{ matrix.service }}
237- IMAGE="subvortex/subvortex-$COMP-$SERVICE "
206+ COMP=" ${{ matrix.component }}"
207+ REPO_NAME="subvortex-${COMP//_/-}" && echo "$COMP"
208+ IMAGE="subvortex/$REPO_NAME "
238209 RAW_VERSION_TAG="${{ steps.taginfo.outputs.version_tag }}"
239- VERSION_TAG ="${RAW_VERSION_TAG#v}"
210+ VERSION ="${RAW_VERSION_TAG#v}"
240211 FLOATING_TAGS="${{ steps.taginfo.outputs.floating_tags }}"
241212 IS_PRERELEASE=${{ github.event.release.prerelease }}
242213 IS_DRAFT=${{ github.event.release.draft }}
243-
214+
244215 echo "📦 Release type: prerelease=$IS_PRERELEASE, draft=$IS_DRAFT"
245216 echo "🏷️ Floating tags requested: $FLOATING_TAGS"
246-
217+
247218 if [ "$IS_DRAFT" = "true" ]; then
248219 echo "⏭️ Skipping draft release"
249220 exit 0
250221 fi
251-
252- echo "🔍 Getting manifest for $IMAGE:$VERSION_TAG "
253- docker buildx imagetools inspect $IMAGE:$VERSION_TAG
254-
222+
223+ echo "🔍 Getting manifest for $IMAGE:$VERSION "
224+ docker buildx imagetools inspect $IMAGE:$VERSION
225+
255226 for TAG in $FLOATING_TAGS; do
256227 # Skip "latest" for prereleases
257228 if [ "$IS_PRERELEASE" = "true" ] && [ "$TAG" = "latest" ]; then
258229 echo "⏭️ Skipping 'latest' tag for prerelease"
259230 continue
260231 fi
261-
262- echo "🔁 Creating manifest for $IMAGE:$TAG from $IMAGE:$VERSION_TAG "
232+
233+ echo "🔁 Creating manifest for $IMAGE:$TAG from $IMAGE:$VERSION "
263234 docker buildx imagetools create \
264235 --tag $IMAGE:$TAG \
265- $IMAGE:$VERSION_TAG
236+ $IMAGE:$VERSION
266237 done
267238
268239 - name : 🧹 Remove floating tags (on release or prerelease delete)
269240 if : github.event_name == 'release' && github.event.action == 'deleted'
270241 run : |
271- COMP=${{ matrix.component }}
272- SERVICE=${{ matrix.service }}
273- IMAGE="subvortex/subvortex-$COMP-$SERVICE "
242+ COMP=" ${{ matrix.component }}"
243+ REPO_NAME="subvortex-${COMP//_/-}" && echo "$COMP"
244+ IMAGE="subvortex/$REPO_NAME "
274245 RAW_VERSION_TAG="${{ github.event.release.tag_name }}"
275- VERSION_TAG ="${RAW_VERSION_TAG#v}"
246+ VERSION ="${RAW_VERSION_TAG#v}"
276247 FLOATING_TAGS="${{ steps.taginfo.outputs.floating_tags }}"
277248 USERNAME="${{ secrets.DOCKER_USERNAME }}"
278249 PASSWORD="${{ secrets.DOCKER_PASSWORD }}"
279- REPO_NAME="subvortex-$COMP-$SERVICE"
280250
281- echo "🗑️ Release deleted: $RAW_VERSION_TAG "
282- echo "🔍 Attempting to delete floating tags: $FLOATING_TAGS"
251+ echo "🗑️ Release deleted: $VERSION "
252+ echo "🔍 Handling floating tags: $FLOATING_TAGS"
283253
284254 echo "🔐 Requesting Docker Hub JWT token..."
285255 TOKEN=$(curl -s -X POST https://hub.docker.com/v2/users/login/ \
@@ -291,18 +261,50 @@ jobs:
291261 exit 1
292262 fi
293263
264+ echo "📦 Fetching all tags from Docker Hub (excluding deleted tag: $VERSION)..."
265+ ALL_TAGS=$(curl -s -H "Authorization: JWT $TOKEN" \
266+ "https://hub.docker.com/v2/repositories/$USERNAME/$REPO_NAME/tags?page_size=100" | jq -r '.results[].name' | grep -v "^$VERSION$")
267+
268+ RELEASE_TAGS=$(echo "$ALL_TAGS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' || true)
269+ PRERELEASE_TAGS=$(echo "$ALL_TAGS" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+-(alpha|rc)\.[0-9]+$' || true)
270+
294271 for TAG in $FLOATING_TAGS; do
295- echo "❌ Deleting tag: $IMAGE:$TAG"
296- RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
297- "https://hub.docker.com/v2/repositories/$USERNAME/$REPO_NAME/tags/$TAG/" \
298- -H "Authorization: JWT $TOKEN")
299-
300- if [ "$RESPONSE" = "204" ]; then
301- echo "✅ Successfully deleted $IMAGE:$TAG"
302- elif [ "$RESPONSE" = "404" ]; then
303- echo "⚠️ Tag $TAG not found (already deleted or never pushed)"
272+ echo "🔁 Handling floating tag: $TAG"
273+
274+ case "$TAG" in
275+ dev)
276+ TARGET=$(echo "$PRERELEASE_TAGS" | grep 'alpha' | sort -Vr | head -n1)
277+ ;;
278+ stage)
279+ TARGET=$(echo "$PRERELEASE_TAGS" | grep 'rc' | sort -Vr | head -n1)
280+ ;;
281+ latest)
282+ TARGET=$(echo "$RELEASE_TAGS" | sort -Vr | head -n1)
283+ ;;
284+ *)
285+ echo "⚠️ Unknown floating tag: $TAG"
286+ continue
287+ ;;
288+ esac
289+
290+ if [ -n "$TARGET" ]; then
291+ echo "🔄 Re-pointing $TAG to $TARGET as multi-platform manifest"
292+ echo "$PASSWORD" | docker login -u "$USERNAME" --password-stdin
293+ docker buildx imagetools create \
294+ --tag "$IMAGE:$TAG" \
295+ "$IMAGE:$TARGET"
296+ docker logout
304297 else
305- echo "❌ Failed to delete tag $TAG (HTTP $RESPONSE)"
298+ echo "🗑️ No matching version for $TAG. Deleting..."
299+ RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \
300+ "https://hub.docker.com/v2/repositories/$USERNAME/$REPO_NAME/tags/$TAG/" \
301+ -H "Authorization: JWT $TOKEN")
302+ if [ "$RESPONSE" = "204" ]; then
303+ echo "✅ Deleted $IMAGE:$TAG"
304+ elif [ "$RESPONSE" = "404" ]; then
305+ echo "⚠️ Tag $TAG not found"
306+ else
307+ echo "❌ Failed to delete $TAG (HTTP $RESPONSE)"
308+ fi
306309 fi
307310 done
308-
0 commit comments