name: Publish Baron SSO Images on: workflow_dispatch: inputs: version_prefix: description: "stage/prod 공용 이미지 태그 prefix (예: v1.2606, 최종 태그는 v1.2606.<커밋해시4자리>)" required: true type: string jobs: publish-images: runs-on: ubuntu-latest steps: - name: Checkout dev branch uses: actions/checkout@v4 with: ref: dev - name: Validate publish inputs env: VERSION_PREFIX: ${{ github.event.inputs.version_prefix }} ADMINFRONT_URL: ${{ vars.ADMINFRONT_URL }} DEVFRONT_URL: ${{ vars.DEVFRONT_URL }} ORGFRONT_URL: ${{ vars.ORGFRONT_URL }} VITE_OIDC_AUTHORITY: ${{ vars.VITE_OIDC_AUTHORITY }} WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID: ${{ vars.WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID }} WORKS_DRIVE_ACCESS_TOKEN_INPUT: ${{ secrets.WORKS_DRIVE_ACCESS_TOKEN }} WORKS_DRIVE_ACCESS_TOKEN_FILE: ${{ vars.WORKS_DRIVE_ACCESS_TOKEN_FILE }} WORKS_DRIVE_ACCESS_TOKEN_CMD: ${{ vars.WORKS_DRIVE_ACCESS_TOKEN_CMD }} WORKS_DRIVE_OAUTH_CLIENT_ID: ${{ secrets.WORKS_DRIVE_OAUTH_CLIENT_ID }} WORKS_DRIVE_OAUTH_CLIENT_SECRET: ${{ secrets.WORKS_OAUTH_CLIENT_SECRET }} WORKS_DRIVE_OAUTH_REFRESH_TOKEN: ${{ secrets.WORKS_DRIVE_REFRESH_TOKEN }} run: | set -euo pipefail if ! printf '%s' "${VERSION_PREFIX}" | grep -Eq '^v[0-9]+\.[0-9]{4}$'; then echo "::error::version_prefix must look like vX.YYMM (got: ${VERSION_PREFIX})" exit 1 fi required_values=" ADMINFRONT_URL DEVFRONT_URL ORGFRONT_URL VITE_OIDC_AUTHORITY WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID " for key in ${required_values}; do if [ -z "${!key:-}" ]; then echo "::error::Missing required publish value: ${key}. Check Gitea repo variables/secrets." exit 1 fi done if [ -z "${WORKS_DRIVE_ACCESS_TOKEN_INPUT:-}" ] \ && [ -z "${WORKS_DRIVE_ACCESS_TOKEN_FILE:-}" ] \ && [ -z "${WORKS_DRIVE_ACCESS_TOKEN_CMD:-}" ] \ && [ -z "${WORKS_DRIVE_OAUTH_REFRESH_TOKEN:-}" ]; then echo "::error::Missing WORKS Drive access auth. Provide WORKS_DRIVE_ACCESS_TOKEN, WORKS_DRIVE_ACCESS_TOKEN_FILE, WORKS_DRIVE_ACCESS_TOKEN_CMD, or WORKS_DRIVE_OAUTH_REFRESH_TOKEN." exit 1 fi if [ -z "${WORKS_DRIVE_ACCESS_TOKEN_INPUT:-}" ] \ && [ -z "${WORKS_DRIVE_ACCESS_TOKEN_FILE:-}" ] \ && [ -z "${WORKS_DRIVE_ACCESS_TOKEN_CMD:-}" ] \ && [ -n "${WORKS_DRIVE_OAUTH_REFRESH_TOKEN:-}" ] \ && { [ -z "${WORKS_DRIVE_OAUTH_CLIENT_ID:-}" ] || [ -z "${WORKS_DRIVE_OAUTH_CLIENT_SECRET:-}" ]; }; then echo "::error::WORKS_DRIVE_OAUTH_CLIENT_ID and WORKS_DRIVE_OAUTH_CLIENT_SECRET are required when WORKS_DRIVE_OAUTH_REFRESH_TOKEN is the selected auth source." exit 1 fi - name: Compute commit-hash image tag id: version env: VERSION_PREFIX: ${{ github.event.inputs.version_prefix }} run: | set -euo pipefail short_sha="$(git rev-parse --short=4 HEAD)" if ! printf '%s' "${short_sha}" | grep -Eq '^[0-9a-f]{4}$'; then echo "::error::commit hash suffix must be 4 lowercase hexadecimal characters (got: ${short_sha})" exit 1 fi image_tag="${VERSION_PREFIX}.${short_sha}" echo "image_tag=${image_tag}" >> "${GITHUB_OUTPUT}" echo "Computed shared image tag: ${image_tag}" - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build backend image uses: docker/build-push-action@v5 with: context: ./backend file: ./backend/Dockerfile load: true tags: baron_sso/backend:${{ steps.version.outputs.image_tag }} provenance: false sbom: false - name: Build userfront image uses: docker/build-push-action@v5 with: context: . file: ./userfront/Dockerfile target: production load: true tags: baron_sso/userfront:${{ steps.version.outputs.image_tag }} provenance: false sbom: false - name: Build adminfront image uses: docker/build-push-action@v5 with: context: . file: ./adminfront/Dockerfile target: production load: true tags: baron_sso/adminfront:${{ steps.version.outputs.image_tag }} build-args: | VITE_ADMIN_PUBLIC_URL=${{ vars.ADMINFRONT_URL }} VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} VITE_OIDC_CLIENT_ID=adminfront ORGFRONT_URL=${{ vars.ORGFRONT_URL }} provenance: false sbom: false - name: Build devfront image uses: docker/build-push-action@v5 with: context: . file: ./devfront/Dockerfile target: production load: true tags: baron_sso/devfront:${{ steps.version.outputs.image_tag }} build-args: | VITE_DEVFRONT_PUBLIC_URL=${{ vars.DEVFRONT_URL }} VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} VITE_OIDC_CLIENT_ID=devfront ORGFRONT_URL=${{ vars.ORGFRONT_URL }} provenance: false sbom: false - name: Build orgfront image uses: docker/build-push-action@v5 with: context: . file: ./orgfront/Dockerfile target: production load: true tags: baron_sso/orgfront:${{ steps.version.outputs.image_tag }} build-args: | VITE_ORGFRONT_PUBLIC_URL=${{ vars.ORGFRONT_URL }} VITE_OIDC_AUTHORITY=${{ vars.VITE_OIDC_AUTHORITY }} VITE_OIDC_CLIENT_ID=orgfront provenance: false sbom: false - name: Verify built Docker images before WORKS upload env: IMAGE_TAG: ${{ steps.version.outputs.image_tag }} run: | set -euo pipefail for image in backend userfront adminfront devfront orgfront; do image_ref="baron_sso/${image}:${IMAGE_TAG}" echo "Checking built Docker image before WORKS upload: ${image_ref}" docker image inspect "${image_ref}" >/dev/null docker image ls "${image_ref}" done - name: Resolve WORKS Drive access token env: WORKS_DRIVE_ACCESS_TOKEN_INPUT: ${{ secrets.WORKS_DRIVE_ACCESS_TOKEN }} WORKS_DRIVE_ACCESS_TOKEN_FILE: ${{ vars.WORKS_DRIVE_ACCESS_TOKEN_FILE }} WORKS_DRIVE_ACCESS_TOKEN_CMD: ${{ vars.WORKS_DRIVE_ACCESS_TOKEN_CMD }} WORKS_DRIVE_OAUTH_CLIENT_ID: ${{ secrets.WORKS_DRIVE_OAUTH_CLIENT_ID }} WORKS_DRIVE_OAUTH_CLIENT_SECRET: ${{ secrets.WORKS_OAUTH_CLIENT_SECRET }} WORKS_DRIVE_OAUTH_REFRESH_TOKEN: ${{ secrets.WORKS_DRIVE_REFRESH_TOKEN }} WORKS_ADMIN_OAUTH_TOKEN_URL: ${{ vars.WORKS_ADMIN_OAUTH_TOKEN_URL }} run: | set -euo pipefail access_token="" rotated_refresh_token_file="${RUNNER_TEMP}/works-drive-rotated-refresh-token" if [ -n "${WORKS_DRIVE_ACCESS_TOKEN_INPUT:-}" ]; then access_token="${WORKS_DRIVE_ACCESS_TOKEN_INPUT}" elif [ -n "${WORKS_DRIVE_ACCESS_TOKEN_FILE:-}" ]; then access_token="$(sed -n '1p' "${WORKS_DRIVE_ACCESS_TOKEN_FILE}")" elif [ -n "${WORKS_DRIVE_ACCESS_TOKEN_CMD:-}" ]; then access_token="$(sh -c "${WORKS_DRIVE_ACCESS_TOKEN_CMD}")" else token_url="${WORKS_ADMIN_OAUTH_TOKEN_URL:-https://auth.worksmobile.com/oauth2/v2.0/token}" response="$(curl -sS -w $'\n%{http_code}' -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=refresh_token" \ --data-urlencode "refresh_token=${WORKS_DRIVE_OAUTH_REFRESH_TOKEN}" \ --data-urlencode "client_id=${WORKS_DRIVE_OAUTH_CLIENT_ID}" \ --data-urlencode "client_secret=${WORKS_DRIVE_OAUTH_CLIENT_SECRET}" \ "${token_url}")" http_status="$(tail -n 1 <<<"${response}")" response_body="$(sed '$d' <<<"${response}")" if [ "${http_status}" -lt 200 ] || [ "${http_status}" -ge 300 ]; then echo "::error::WORKS Drive access token refresh failed with HTTP ${http_status}." exit 1 fi access_token="$(jq -er '.access_token' <<<"${response_body}")" rotated_refresh_token="$(jq -r '.refresh_token // empty' <<<"${response_body}")" if [ -n "${rotated_refresh_token}" ]; then echo "::add-mask::${rotated_refresh_token}" printf '%s\n' "${rotated_refresh_token}" >"${rotated_refresh_token_file}" chmod 600 "${rotated_refresh_token_file}" echo "WORKS_DRIVE_ROTATED_REFRESH_TOKEN_FILE=${rotated_refresh_token_file}" >>"${GITHUB_ENV}" echo "::warning::WORKS returned a rotated refresh token. Persist it to the WORKS_DRIVE_OAUTH_REFRESH_TOKEN secret before old refresh tokens age out." fi fi if [ -z "${access_token}" ]; then echo "::error::WORKS Drive access token could not be resolved." exit 1 fi echo "::add-mask::${access_token}" echo "WORKS_DRIVE_ACCESS_TOKEN=${access_token}" >>"${GITHUB_ENV}" - name: Upload built images to WORKS Drive archive env: IMAGE_TAG: ${{ steps.version.outputs.image_tag }} WORKS_DRIVE_DOCKER_IMAGE_DIR: ${{ vars.WORKS_DRIVE_DOCKER_IMAGE_DIR }} WORKS_DRIVE_TARGET: sharedrive WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID: ${{ vars.WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID }} WORKS_DRIVE_DOCKER_IMAGE_PARENT_FILE_ID: ${{ vars.WORKS_DRIVE_DOCKER_IMAGE_PARENT_FILE_ID }} WORKS_ADMIN_API_BASE_URL: ${{ vars.WORKS_ADMIN_API_BASE_URL }} run: | set -euo pipefail : "${WORKS_DRIVE_DOCKER_IMAGE_DIR:=baron-sso}" required_values=" IMAGE_TAG WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID " for key in ${required_values}; do if [ -z "${!key:-}" ]; then echo "::error::Missing required WORKS image archive value: ${key}." exit 1 fi done images="backend userfront adminfront devfront orgfront" image_total=5 image_index=0 uploaded_images="" for image in ${images}; do image_index=$((image_index + 1)) image_ref="baron_sso/${image}:${IMAGE_TAG}" echo "WORKS image upload ${image_index}/${image_total}: ${image_ref}" docker image inspect "${image_ref}" >/dev/null if DOCKER_IMAGE_REF="${image_ref}" \ WORKS_DRIVE_DOCKER_IMAGE_DIR="${WORKS_DRIVE_DOCKER_IMAGE_DIR}" \ WORKS_DRIVE_SHARED_DRIVE_ID="${WORKS_DRIVE_DOCKER_IMAGE_DRIVE_ID}" \ WORKS_DRIVE_PARENT_FILE_ID="${WORKS_DRIVE_DOCKER_IMAGE_PARENT_FILE_ID:-}" \ WORKS_DOCKER_IMAGE_ARCHIVE_DIR="${RUNNER_TEMP}/baron-sso-docker-image-upload" \ scripts/docker-image/upload_works_drive.sh; then uploaded_images="${uploaded_images}${uploaded_images:+ }${image_ref}" echo "WORKS image upload completed: ${image_ref}" else upload_status="$?" echo "::error::WORKS image upload failed at ${image_index}/${image_total}: ${image_ref}" echo "Already uploaded images: ${uploaded_images:-none}" exit "${upload_status}" fi done echo "Uploaded WORKS image archives:" for image_ref in ${uploaded_images}; do echo " - ${image_ref}" done