Skip to content

Commit 3765403

Browse files
committed
ci: Optimize Docker image publishing workflow
1 parent cd03dcd commit 3765403

File tree

6 files changed

+165
-34
lines changed

6 files changed

+165
-34
lines changed

.github/workflows/CI.yml

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -128,56 +128,86 @@ jobs:
128128
needs: build
129129
runs-on: ubuntu-latest
130130
steps:
131-
- uses: actions/checkout@v3
131+
- uses: actions/checkout@v4
132132
with:
133133
fetch-depth: 0
134-
- name: commit push
135-
run: |
136-
docker info
137-
pwd
138-
ls
134+
135+
- name: Use Node.js
136+
uses: actions/setup-node@v4
137+
with:
138+
node-version: '20'
139+
cache: 'yarn'
140+
141+
- name: Cache Nx
142+
uses: actions/cache@v4
143+
with:
144+
path: .nx
145+
key: ${{ runner.os }}-nx-${{ hashFiles('**/yarn.lock') }}
146+
restore-keys: |
147+
${{ runner.os }}-nx-
148+
149+
- run: yarn install --frozen-lockfile
150+
139151
- name: docker login
140152
uses: docker/[email protected]
141153
with:
142154
registry: registry.cn-hongkong.aliyuncs.com
143155
username: devops@1148299792416188
144156
password: ${{ secrets.REGISTRY_HONGKONG_SECRET }}
145-
- name: Extract branch name
146-
shell: bash
147-
run: echo "BRANCH=${GITHUB_REF_NAME/\//_}" >> $GITHUB_OUTPUT
148-
id: extract_branch
149-
- name: Get current date
150-
id: date
151-
run: echo "TODAY=$(date +'%y%m%d_%H%M')" >> $GITHUB_OUTPUT
152-
- name: Get commit
153-
id: commitid
154-
run: echo "COMMIT_ID=${GITHUB_SHA:0:7}" >> $GITHUB_OUTPUT
155-
- name: image tag
156-
id: tag
157-
run: |
158-
# if triggered by tag, use tag as image tag
159-
if [[ $GITHUB_REF == refs/tags/* ]]; then
160-
echo "TAG=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
161-
else
162-
echo "TAG=${{ steps.extract_branch.outputs.BRANCH }}_${{ steps.date.outputs.TODAY }}_${{ steps.commitid.outputs.COMMIT_ID }}" >> $GITHUB_OUTPUT
163-
fi
164-
- name: env
157+
158+
- name: Get version from api/package.json
159+
id: get_version
165160
run: |
166-
echo "VITE_API_BASE_URL=/devtable-api/" > website/.env
167-
echo "VITE_WEBSITE_BASE_URL=/devtable/website/" >> website/.env
161+
VERSION=$(node -p "require('./api/package.json').version")
162+
echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT
163+
echo "Using version: ${VERSION}"
164+
165+
- name: Check if API image version is already published
166+
id: check_api
167+
continue-on-error: true
168+
run: yarn exec nx run api:is-docker-published-api
169+
170+
- name: Check if Website image version is already published
171+
id: check_website
172+
continue-on-error: true
173+
run: yarn exec nx run api:is-docker-published-website
174+
168175
- name: Download artifacts
169176
uses: actions/download-artifact@v4
170177
with:
171178
name: dist
179+
180+
- name: env
181+
run: |
182+
echo "VITE_API_BASE_URL=/devtable-api/" > website/.env
183+
echo "VITE_WEBSITE_BASE_URL=/devtable/website/" >> website/.env
184+
172185
- name: Setup buildx
173186
run: docker buildx create --use
174-
- name: Build table image
175-
run: docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile-api -t registry.cn-hongkong.aliyuncs.com/merico/table:${{ steps.tag.outputs.TAG }} . --push
176-
- name: Build table website image
177-
run: docker buildx build --platform linux/amd64,linux/arm64 -f Dockerfile-website -t registry.cn-hongkong.aliyuncs.com/merico/table-website:${{ steps.tag.outputs.TAG }} . --push
187+
188+
# Build and push API image if version is new
189+
- name: Build and push API image
190+
if: steps.check_api.outcome == 'success'
191+
run: |
192+
docker buildx build --platform linux/amd64,linux/arm64 \
193+
-f Dockerfile-api \
194+
-t registry.cn-hongkong.aliyuncs.com/merico/table:${{ steps.get_version.outputs.VERSION }} \
195+
-t registry.cn-hongkong.aliyuncs.com/merico/table:latest \
196+
. --push
197+
198+
# Build and push Website image if version is new
199+
- name: Build and push Website image
200+
if: steps.check_website.outcome == 'success'
201+
run: |
202+
docker buildx build --platform linux/amd64,linux/arm64 \
203+
-f Dockerfile-website \
204+
-t registry.cn-hongkong.aliyuncs.com/merico/table-website:${{ steps.get_version.outputs.VERSION }} \
205+
-t registry.cn-hongkong.aliyuncs.com/merico/table-website:latest \
206+
. --push
207+
178208
- name: Trigger GitLab CI pipeline
179-
# only runs if the previous step was successful
180-
if: success()
209+
# Only trigger if at least one image was newly published
210+
if: steps.check_api.outcome == 'success' || steps.check_website.outcome == 'success'
181211
uses: eic/trigger-gitlab-ci@v3
182212
with:
183213
token: ${{ secrets.GITLAB_TOKEN }}

api/project.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@
1818
"script": "dev"
1919
},
2020
"dependsOn": ["^dev"]
21+
},
22+
"is-docker-published-api": {
23+
"executor": "workspace-plugin:is-docker-published",
24+
"options": {
25+
"packageJson": "api/package.json",
26+
"registry": "registry.cn-hongkong.aliyuncs.com",
27+
"imageName": "merico/table"
28+
}
29+
},
30+
"is-docker-published-website": {
31+
"executor": "workspace-plugin:is-docker-published",
32+
"options": {
33+
"packageJson": "api/package.json",
34+
"registry": "registry.cn-hongkong.aliyuncs.com",
35+
"imageName": "merico/table-website"
36+
}
2137
}
2238
}
2339
}

tools/workspace-plugin/executors.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
"implementation": "./src/executors/is-published/executor",
55
"schema": "./src/executors/is-published/schema.json",
66
"description": "is-published executor"
7+
},
8+
"is-docker-published": {
9+
"implementation": "./src/executors/is-docker-published/executor",
10+
"schema": "./src/executors/is-docker-published/schema.json",
11+
"description": "is-docker-published executor"
712
}
813
}
914
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { IsDockerPublishedExecutorSchema } from './schema';
2+
import { execSync } from 'child_process';
3+
import { readFileSync } from 'fs';
4+
import { resolve } from 'path';
5+
6+
export default async function runExecutor(options: IsDockerPublishedExecutorSchema) {
7+
const { packageJson, registry, imageName } = options;
8+
9+
try {
10+
// Read package.json file
11+
const packageJsonPath = resolve(packageJson);
12+
const packageJsonContent = readFileSync(packageJsonPath, 'utf-8');
13+
const pkg = JSON.parse(packageJsonContent);
14+
15+
const version = pkg.version;
16+
17+
if (!version) {
18+
console.error('package.json must contain a "version" field');
19+
return {
20+
success: false,
21+
};
22+
}
23+
24+
const fullImageName = `${registry}/${imageName}:${version}`;
25+
console.log(`Checking if ${fullImageName} exists in registry...`);
26+
27+
try {
28+
// Use docker manifest inspect to check if image exists
29+
// This requires Docker CLI and prior authentication to the registry
30+
execSync(`docker manifest inspect ${fullImageName}`, {
31+
encoding: 'utf-8',
32+
stdio: ['pipe', 'pipe', 'pipe'],
33+
});
34+
35+
// If we reach here, the image exists
36+
console.error(`${fullImageName} already exists in registry`);
37+
return {
38+
success: false,
39+
};
40+
} catch (error) {
41+
// docker manifest inspect returns non-zero if image doesn't exist
42+
console.log(`${fullImageName} not found in registry - safe to publish`);
43+
return {
44+
success: true,
45+
};
46+
}
47+
} catch (error) {
48+
console.error(`Error reading package.json: ${error.message}`);
49+
return {
50+
success: false,
51+
};
52+
}
53+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface IsDockerPublishedExecutorSchema {
2+
packageJson: string;
3+
registry: string;
4+
imageName: string;
5+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"version": 2,
4+
"title": "IsDockerPublished executor",
5+
"description": "Check if a Docker image version is published to a registry and fail if it is",
6+
"type": "object",
7+
"properties": {
8+
"packageJson": {
9+
"type": "string",
10+
"description": "Path to the package.json file to read version from"
11+
},
12+
"registry": {
13+
"type": "string",
14+
"description": "Docker registry URL"
15+
},
16+
"imageName": {
17+
"type": "string",
18+
"description": "Docker image name (without registry prefix)"
19+
}
20+
},
21+
"required": ["packageJson", "registry", "imageName"]
22+
}

0 commit comments

Comments
 (0)