From d4f228284e0cae77787a7405a62deb9fbeabf3fb Mon Sep 17 00:00:00 2001 From: Dmitriy Petrov Date: Wed, 19 Aug 2026 18:40:01 +0300 Subject: [PATCH] Add hard deploy option for Gateway workflow --- .gitea/workflows/gateway-build.yml | 21 +++++++++++++++++++++ src/shared/versions.ts | 2 +- test/server/deploy-split.test.js | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/gateway-build.yml b/.gitea/workflows/gateway-build.yml index d6aed6c..edd1ad2 100644 --- a/.gitea/workflows/gateway-build.yml +++ b/.gitea/workflows/gateway-build.yml @@ -4,6 +4,12 @@ on: push: branches: [master] workflow_dispatch: + inputs: + hard_deploy: + description: Always rebuild and deploy both Gateway images + required: false + default: false + type: boolean env: DEPLOY_PATH: /opt/vpn-proxy @@ -34,6 +40,8 @@ jobs: - name: Build and push gateway image id: gateway-build + env: + HARD_DEPLOY_INPUT: ${{ inputs.hard_deploy }} run: | set -euo pipefail cd repo @@ -44,6 +52,11 @@ jobs: DATAPLANE_IMAGE="${IMAGE}-dataplane" EVENT_NAME="${{ gitea.event_name }}" + case "${EVENT_NAME}:${HARD_DEPLOY_INPUT}" in + workflow_dispatch:true) HARD_DEPLOY=true ;; + workflow_dispatch:false|workflow_dispatch:|push:false|push:) HARD_DEPLOY=false ;; + *) echo "Invalid hard deploy request: ${EVENT_NAME}:${HARD_DEPLOY_INPUT}" >&2; exit 1 ;; + esac BEFORE_SHA="${{ gitea.event.before }}" ZERO_SHA="0000000000000000000000000000000000000000" if [ "$EVENT_NAME" = "push" ] \ @@ -76,6 +89,13 @@ jobs: none:none|control:control|dataplane:both|control+dataplane:both) ;; *) echo "Invalid runtime impact: ${RUNTIME_IMPACT}" >&2; exit 1 ;; esac + DOCKER_BUILD_OPTIONS=() + if [ "$HARD_DEPLOY" = "true" ]; then + echo "Hard deploy requested: forcing no-cache rebuild and deploy of both Gateway images." + AFFECTED_COMPONENTS="control+dataplane" + RESTART_SCOPE="both" + DOCKER_BUILD_OPTIONS=(--no-cache) + fi echo "Affected components: ${AFFECTED_COMPONENTS}" echo "Restart scope: ${RESTART_SCOPE}" echo "affected_components=${AFFECTED_COMPONENTS}" >> "$GITHUB_OUTPUT" @@ -132,6 +152,7 @@ jobs: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login "$REGISTRY_HOST" -u "${{ gitea.actor }}" --password-stdin DOCKER_BUILDKIT=1 docker build \ + "${DOCKER_BUILD_OPTIONS[@]}" \ --network host \ --pull=false \ --build-arg NODE_BUILD_IMAGE="${{ env.NODE_BUILD_IMAGE }}" \ diff --git a/src/shared/versions.ts b/src/shared/versions.ts index 66a176e..234be0c 100644 --- a/src/shared/versions.ts +++ b/src/shared/versions.ts @@ -1,7 +1,7 @@ export const HARBOR_VERSIONS = Object.freeze({ macClient: '0.28.0', gatewayClient: '0.29.0', - gatewayBackend: '0.29.0', + gatewayBackend: '0.29.1', }); export interface ParsedVersion { diff --git a/test/server/deploy-split.test.js b/test/server/deploy-split.test.js index f375ce0..d0f26db 100644 --- a/test/server/deploy-split.test.js +++ b/test/server/deploy-split.test.js @@ -33,6 +33,20 @@ test('gateway deploy updates control without recreating dataplane', () => { assert.doesNotMatch(workflow, /grep -Eq/); }); +test('manual hard deploy safely forces the existing full Gateway path', () => { + assert.match(workflow, /workflow_dispatch:\s*\n\s+inputs:\s*\n\s+hard_deploy:[\s\S]*default: false[\s\S]*type: boolean/); + assert.match(workflow, /env:\s*\n\s+HARD_DEPLOY_INPUT: \$\{\{ inputs\.hard_deploy \}\}/); + assert.match(workflow, /case "\$\{EVENT_NAME\}:\$\{HARD_DEPLOY_INPUT\}" in\s*\n\s+workflow_dispatch:true\) HARD_DEPLOY=true/); + assert.match(workflow, /workflow_dispatch:false\|workflow_dispatch:\|push:false\|push:\) HARD_DEPLOY=false/); + assert.match(workflow, /\*\) echo "Invalid hard deploy request:[^\n]+exit 1/); + assert.match(workflow, /Invalid runtime impact:[\s\S]*DOCKER_BUILD_OPTIONS=\(\)[\s\S]*if \[ "\$HARD_DEPLOY" = "true" \]/); + assert.match(workflow, /Hard deploy requested: forcing no-cache rebuild and deploy of both Gateway images\./); + assert.match(workflow, /AFFECTED_COMPONENTS="control\+dataplane"\s*\n\s+RESTART_SCOPE="both"\s*\n\s+DOCKER_BUILD_OPTIONS=\(--no-cache\)/); + assert.match(workflow, /DOCKER_BUILDKIT=1 docker build \\\n\s+"\$\{DOCKER_BUILD_OPTIONS\[@\]\}"/); + assert.match(workflow, /DOCKER_BUILD_OPTIONS=\(--no-cache\)[\s\S]*affected_components=\$\{AFFECTED_COMPONENTS\}[\s\S]*if \[ "\$RESTART_SCOPE" = "none" \]/); + assert.equal(workflow.match(/bash scripts\/deploy-gateway\.sh/g)?.length, 1); +}); + test('runtime images contain only compiled application modules', () => { for (const dockerfile of dockerfiles) { assert.match(dockerfile, /RUN npm run build:production/);