diff --git a/Jenkinsfile b/Jenkinsfile index ca8cb9e..e2de18f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -16,6 +16,7 @@ pipeline { WEB_IMAGE_REPO = 'reg.nxsir.cn/live_recorder/app-web' HARBOR_CREDENTIALS = 'harbor_key' OPENLIST_CREDENTIALS = 'openlist_key' + NODE_CREDENTIALS = 'bbb939ea-4f01-4b47-aecb-c5ee2a551ef4' OPENLIST_BASE_URL = 'https://openlist.nxsir.cn' OPENLIST_REMOTE_DIR = '/yidongpan/构建产物/liverecorder' @@ -72,38 +73,43 @@ pipeline { currentBuild.description = "${env.IMMUTABLE_TAG}" } - sh ''' - set -euo pipefail - test "$(uname -m)" = x86_64 - for command_name in git sudo docker curl python3 npm node sha256sum stat tar; do - command -v "$command_name" >/dev/null - done - test -x "$DOTNET" - test -x "$FNPACK_BIN" - printf '%s %s\n' "$FNPACK_SHA256" "$FNPACK_BIN" | sha256sum --check --status - sudo -n true - sudo docker version >/dev/null - sudo docker buildx version + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { + sh ''' + set -euo pipefail + test "$(uname -m)" = x86_64 + for command_name in git sudo docker curl python3 npm node sha256sum stat tar; do + command -v "$command_name" >/dev/null + done + test -x "$DOTNET" + test -x "$FNPACK_BIN" + printf '%s %s\n' "$FNPACK_SHA256" "$FNPACK_BIN" | sha256sum --check --status + ./scripts/ci-docker.sh version >/dev/null + ./scripts/ci-docker.sh buildx version - available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }') - if [ "$available_kb" -lt 2097152 ]; then - echo "At least 2 GiB free workspace disk is required; available KiB: $available_kb" >&2 - exit 1 - fi + available_kb=$(df -Pk "$WORKSPACE" | awk 'NR == 2 { print $4 }') + if [ "$available_kb" -lt 2097152 ]; then + echo "At least 2 GiB free workspace disk is required; available KiB: $available_kb" >&2 + exit 1 + fi - mkdir -p \ - "$DOCKER_CONFIG" \ - "$NUGET_PACKAGES" \ - "$NUGET_HTTP_CACHE_PATH" \ - "$NUGET_PLUGINS_CACHE_PATH" \ - "$DOTNET_CLI_HOME" \ - "$NPM_CONFIG_CACHE" \ - "$LIVERECORDER_NUGET_FEED" \ - "$LIVERECORDER_BUILD_TMPDIR" \ - "$LIVERECORDER_VERIFY_TMPDIR" \ - "$(dirname -- "$FPK_PATH")" - chmod 700 "$DOCKER_CONFIG" - ''' + mkdir -p \ + "$DOCKER_CONFIG" \ + "$NUGET_PACKAGES" \ + "$NUGET_HTTP_CACHE_PATH" \ + "$NUGET_PLUGINS_CACHE_PATH" \ + "$DOTNET_CLI_HOME" \ + "$NPM_CONFIG_CACHE" \ + "$LIVERECORDER_NUGET_FEED" \ + "$LIVERECORDER_BUILD_TMPDIR" \ + "$LIVERECORDER_VERIFY_TMPDIR" \ + "$(dirname -- "$FPK_PATH")" + chmod 700 "$DOCKER_CONFIG" + ''' + } } } @@ -132,22 +138,28 @@ pipeline { stage('Prepare Docker Builder') { steps { - sh ''' - set -euo pipefail - if ! sudo docker run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then - sudo docker run --privileged --rm "$BINFMT_IMAGE" --install arm64 - fi - sudo docker run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m | grep -q aarch64 + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { + sh ''' + set -euo pipefail + if ! ./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m 2>/dev/null | grep -q aarch64; then + ./scripts/ci-docker.sh run --privileged --rm "$BINFMT_IMAGE" --install arm64 + fi + ./scripts/ci-docker.sh run --rm --platform linux/arm64 "$ARM_TEST_IMAGE" uname -m | grep -q aarch64 - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx create \ - --name "$BUILDER_NAME" \ - --driver docker-container \ - --driver-opt "image=$BUILDKIT_IMAGE" \ - --driver-opt network=host \ - --use - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx inspect "$BUILDER_NAME" --bootstrap - ''' + ./scripts/ci-docker.sh buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true + ./scripts/ci-docker.sh buildx create \ + --name "$BUILDER_NAME" \ + --driver docker-container \ + --driver-opt "image=$BUILDKIT_IMAGE" \ + --driver-opt network=host \ + --use + ./scripts/ci-docker.sh buildx inspect "$BUILDER_NAME" --bootstrap + ''' + } } } @@ -170,6 +182,11 @@ pipeline { stage('Build And Push amd64') { steps { + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { sh ''' set -euo pipefail arch=amd64 @@ -177,7 +194,7 @@ pipeline { api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch" web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch" - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \ + ./scripts/ci-docker.sh buildx build \ --builder "$BUILDER_NAME" --platform "$platform" --network host \ --progress=plain --provenance=false --no-cache \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ @@ -187,10 +204,10 @@ pipeline { --build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg no_proxy="$NO_PROXY_HOSTS" \ -f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --load . - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$api_ref" - sudo docker rmi "$api_ref" >/dev/null + ./scripts/ci-docker.sh push "$api_ref" + ./scripts/ci-docker.sh rmi "$api_ref" >/dev/null - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \ + ./scripts/ci-docker.sh buildx build \ --builder "$BUILDER_NAME" --platform "$platform" --network host \ --progress=plain --provenance=false --no-cache \ --build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \ @@ -203,14 +220,20 @@ pipeline { --build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg VITE_API_BASE_URL=/api \ -f frontend/Dockerfile -t "$web_ref" --load frontend - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$web_ref" - sudo docker rmi "$web_ref" >/dev/null + ./scripts/ci-docker.sh push "$web_ref" + ./scripts/ci-docker.sh rmi "$web_ref" >/dev/null ''' + } } } stage('Build And Push arm64') { steps { + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { sh ''' set -euo pipefail arch=arm64 @@ -218,7 +241,7 @@ pipeline { api_ref="$API_IMAGE_REPO:$IMMUTABLE_TAG-$arch" web_ref="$WEB_IMAGE_REPO:$IMMUTABLE_TAG-$arch" - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \ + ./scripts/ci-docker.sh buildx build \ --builder "$BUILDER_NAME" --platform "$platform" --network host \ --progress=plain --provenance=false --no-cache \ --build-arg HTTP_PROXY="$HTTP_PROXY_URL" \ @@ -228,10 +251,10 @@ pipeline { --build-arg https_proxy="$HTTP_PROXY_URL" \ --build-arg no_proxy="$NO_PROXY_HOSTS" \ -f src/LiveRecorder.WebApi/Dockerfile -t "$api_ref" --load . - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$api_ref" - sudo docker rmi "$api_ref" >/dev/null + ./scripts/ci-docker.sh push "$api_ref" + ./scripts/ci-docker.sh rmi "$api_ref" >/dev/null - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx build \ + ./scripts/ci-docker.sh buildx build \ --builder "$BUILDER_NAME" --platform "$platform" --network host \ --progress=plain --provenance=false --no-cache \ --build-arg NODE_IMAGE="$NODE_BUILD_IMAGE" \ @@ -244,34 +267,41 @@ pipeline { --build-arg no_proxy="$NO_PROXY_HOSTS" \ --build-arg VITE_API_BASE_URL=/api \ -f frontend/Dockerfile -t "$web_ref" --load frontend - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker push "$web_ref" - sudo docker rmi "$web_ref" >/dev/null + ./scripts/ci-docker.sh push "$web_ref" + ./scripts/ci-docker.sh rmi "$web_ref" >/dev/null ''' + } } } stage('Publish Multi-arch Manifests') { steps { + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { sh ''' set -euo pipefail for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do immutable_ref="$repository:$IMMUTABLE_TAG" - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \ + ./scripts/ci-docker.sh buildx imagetools create \ --tag "$immutable_ref" \ "$immutable_ref-amd64" "$immutable_ref-arm64" - manifest=$(sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools inspect --raw "$immutable_ref") + manifest=$(./scripts/ci-docker.sh buildx imagetools inspect --raw "$immutable_ref") printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"amd64"' printf '%s' "$manifest" | grep -q '"architecture"[[:space:]]*:[[:space:]]*"arm64"' done - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \ + ./scripts/ci-docker.sh buildx imagetools create \ --tag "$API_IMAGE_REPO:$APP_VERSION" --tag "$API_IMAGE_REPO:latest" \ "$API_IMAGE_REPO:$IMMUTABLE_TAG" - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx imagetools create \ + ./scripts/ci-docker.sh buildx imagetools create \ --tag "$WEB_IMAGE_REPO:$APP_VERSION" --tag "$WEB_IMAGE_REPO:latest" \ "$WEB_IMAGE_REPO:$IMMUTABLE_TAG" ''' + } } } @@ -319,23 +349,29 @@ pipeline { echo 'LiveRecorder pipeline failed; stable Docker tags were only updated if all architecture builds succeeded.' } always { - sh ''' - set +e - if [ -n "${BUILDER_NAME:-}" ]; then - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true - fi - if [ -n "${IMMUTABLE_TAG:-}" ]; then - for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do - for suffix in amd64 arm64; do - sudo docker rmi "$repository:$IMMUTABLE_TAG-$suffix" >/dev/null 2>&1 || true - done - sudo env DOCKER_CONFIG="$DOCKER_CONFIG" docker manifest rm "$repository:$IMMUTABLE_TAG" >/dev/null 2>&1 || true - done - fi - if [ -f "$DOCKER_CONFIG/config.json" ]; then - : >"$DOCKER_CONFIG/config.json" - fi - ''' + withCredentials([usernamePassword( + credentialsId: "${NODE_CREDENTIALS}", + usernameVariable: 'JENKINS_NODE_USERNAME', + passwordVariable: 'JENKINS_NODE_PASSWORD' + )]) { + sh ''' + set +e + if [ -x ./scripts/ci-docker.sh ] && [ -n "${BUILDER_NAME:-}" ]; then + ./scripts/ci-docker.sh buildx rm -f "$BUILDER_NAME" >/dev/null 2>&1 || true + fi + if [ -x ./scripts/ci-docker.sh ] && [ -n "${IMMUTABLE_TAG:-}" ]; then + for repository in "$API_IMAGE_REPO" "$WEB_IMAGE_REPO"; do + for suffix in amd64 arm64; do + ./scripts/ci-docker.sh rmi "$repository:$IMMUTABLE_TAG-$suffix" >/dev/null 2>&1 || true + done + ./scripts/ci-docker.sh manifest rm "$repository:$IMMUTABLE_TAG" >/dev/null 2>&1 || true + done + fi + if [ -f "$DOCKER_CONFIG/config.json" ]; then + : >"$DOCKER_CONFIG/config.json" + fi + ''' + } cleanWs(deleteDirs: true, notFailBuild: true) } } diff --git a/scripts/ci-docker.sh b/scripts/ci-docker.sh new file mode 100755 index 0000000..7d584cd --- /dev/null +++ b/scripts/ci-docker.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${JENKINS_NODE_PASSWORD:?JENKINS_NODE_PASSWORD is required}" + +printf '%s\n' "$JENKINS_NODE_PASSWORD" | \ + sudo -S -p '' -- \ + /usr/bin/env "DOCKER_CONFIG=${DOCKER_CONFIG:-}" \ + /usr/bin/docker "$@"