diff --git a/.github/workflows/build-and-push.yaml b/.github/workflows/build-and-push.yaml new file mode 100644 index 00000000..b5fe8082 --- /dev/null +++ b/.github/workflows/build-and-push.yaml @@ -0,0 +1,159 @@ +name: Build, Push, and Deploy +concurrency: + group: $${{ github.ref_name }} + +on: + push: + branches: + - 'develop' + - 'master' + tags: + - 'v.*' + pull_request: + types: + - 'opened' + - 'synchronize' + - 'reopened' + - 'closed' + branches: + - 'develop' + - 'master' + +jobs: + build: + name: Build Container Image + runs-on: ubuntu-latest + outputs: + image_tag: ${{ steps.deploy-info.outputs.image_tag }} + image_name: ${{ steps.deploy-info.outputs.image_name }} + environment_name: ${{ steps.deploy-info.outputs.environment_name }} + steps: + - + name: Checkout + uses: actions/checkout@v3 + - + name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + # list of Docker images to use as base name for tags + images: | + gcr.io/skyviewer/rubinobs-client,enable=${{ github.ref != 'master' && github.ref_type != 'tag' && github.base_ref == 'develop' }} + gcr.io/edc-int-6c5e/rubinobs-client,enable=${{ github.ref == 'master' || github.base_ref == 'master'}} + gcr.io/edc-prod-eef0/rubinobs-client,enable=${{ github.ref_type == 'tag'}} + flavor: | + latest=${{ github.event_name == 'push'}} + # generate Docker tags based on the following events/attributes + tags: | + type=schedule + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha + - + name: Parse deployment info + id: deploy-info + run: | + # Determine environment to deploy to + if ${{ github.ref != 'master' && github.ref_type != 'tag' && github.base_ref == 'develop' }}; then + environment_name="dev" + credentials_json='${{ secrets.DEV_SA_KEY }}' + elif ${{ github.ref == 'master' || github.base_ref == 'master'}}; then + environment_name="int" + credentials_json='${{ secrets.SKYVIEWER_INT_SERVICE_ACCOUNT }}' + elif ${{ github.ref_type == 'tag'}}; then + environment_name="prod" + credentials_json='${{ secrets.PIPELINE_EPO_PROD_PROJECT }}' + else + environment_name="" + credentials_json="" + fi + echo environment_name=$environment_name >> "$GITHUB_OUTPUT" + echo credentials_json=$credentials_json >> "$GITHUB_OUTPUT" + + # Parse container image tag to deploy + full_tag=$(echo "$DOCKER_METADATA_OUTPUT_JSON" | jq -r '.tags[] | limit(1; select(. | test(":sha-|:v.")))') + echo "Will use tag \"$full_tag\" for deployment." + echo image_tag=$(echo "$full_tag" | cut -f2 -d:) >> "$GITHUB_OUTPUT" + echo image_name=$(echo "$full_tag" | cut -f1 -d:) >> "$GITHUB_OUTPUT" + echo full_tag=$full_tag >> "$GITHUB_OUTPUT" + + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to GCP + uses: 'google-github-actions/auth@v2' + with: + credentials_json: ${{ steps.deploy-info.outputs.credentials_json }} + - + name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2' + - + run: gcloud --quiet auth configure-docker + - + name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-to: | + type=gha + cache-from: | + type=gha + build-args: | + RUN_BUILD=false + - + name: Summary + run: | + cat <<-EOT >> "$GITHUB_STEP_SUMMARY" + # Container Build Completed + + ## Tags + ${{ steps.meta.outputs.tags }} + EOT + + deploy: + name: Trigger deploy to ${{ needs.build.outputs.environment_name }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Generate Webhook Payload + id: payload + run: |- + PARAMETERS=( + client.image.tag=${{ needs.build.outputs.image_tag }} + client.image.repository=${{ needs.build.outputs.image_name }} + ) + DATA="{ + \"app_name\": \"rubinobs-site\", + \"environment_name\": \"${{ needs.build.outputs.environment_name }}\", + \"parameters\": $(jq -c -n '$ARGS.positional' --args ${PARAMETERS[@]}) + }" + echo "data=$(echo $DATA | jq -rc '.')" >> "$GITHUB_OUTPUT" + - uses: lasith-kg/dispatch-workflow@v1.7.0 + id: dispatch + name: Trigger Deployment + with: + dispatch-method: repository_dispatch + repo: edc-deploy + owner: lsst-epo + event-type: app_update_values + token: ${{ secrets.EDC_DEPLOY_GITHUB_TOKEN_FOR_REST_API }} + workflow-inputs: ${{ steps.payload.outputs.data }} + discover: true + - name: Await Run ID ${{ steps.dispatch.outputs.run-id }} + uses: Codex-/await-remote-run@v1.10.0 + with: + token: ${{ secrets.EDC_DEPLOY_GITHUB_TOKEN_FOR_REST_API }} + repo: edc-deploy + owner: lsst-epo + run_id: ${{ steps.dispatch.outputs.run-id }} + run_timeout_seconds: 1500 diff --git a/Dockerfile b/Dockerfile index aabfb314..d4e090a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,26 @@ -# This file is based on the official Next.js Docker example. https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile +FROM node:18-alpine AS base -# Rebuild the source code only when needed -FROM node:16-alpine AS builder +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat WORKDIR /app -COPY . /app -RUN apk add --no-cache libc6-compat git -RUN yarn install --frozen-lockfile +# Install dependencies based on the preferred package manager +COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ +RUN \ + if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ + elif [ -f package-lock.json ]; then npm ci; \ + elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \ + else echo "Lockfile not found." && exit 1; \ + fi + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . ARG NEXT_PUBLIC_API_URL=https://api.rubinobs.org/api ARG EDC_LOGGER_API_URL=https://us-central1-skyviewer.cloudfunctions.net/edc-logger @@ -19,24 +33,44 @@ ARG NEXT_PUBLIC_EFD_URL=https://hasura-e3g4rcii3q-uc.a.run.app/v1/graphql ARG NEXT_PUBLIC_HASURA_SECRET=_qfq_tMbyR4brJ@KHCzuJRU7 ARG NEXT_PUBLIC_RELEASE_URL=`https://noirlab.edu/public/api/v2/releases/{{ID}}/?lang={{SITE}}&translation_mode=fallback` -RUN npx browserslist@latest --update-db && yarn static:build +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL +ENV EDC_LOGGER_API_URL=$EDC_LOGGER_API_URL +ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL +ENV NEXT_PUBLIC_GOOGLE_APP_ID=$NEXT_PUBLIC_GOOGLE_APP_ID +ENV NEXT_PUBLIC_CONTACT_FORM_POST_URL=$NEXT_PUBLIC_CONTACT_FORM_POST_URL +ENV NEXT_PUBLIC_PLAUSIBLE_DOMAIN=$NEXT_PUBLIC_PLAUSIBLE_DOMAIN +ENV CLOUD_ENV=$CLOUD_ENV + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED 1 + +ARG RUN_BUILD="true" +ENV RUN_BUILD=${RUN_BUILD} + +RUN if $RUN_BUILD;then yarn static:build;fi + +# If using npm comment out above and use below instead +# RUN npm run build # Production image, copy all the files and run next -FROM node:16-alpine AS runner +FROM base AS runner WORKDIR /app -RUN addgroup -g 1001 -S nodejs -RUN adduser -S nextjs -u 1001 +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs COPY --from=builder --chown=nextjs:nodejs /app/ ./ USER nextjs -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry. -# ENV NEXT_TELEMETRY_DISABLED 1 - EXPOSE 8080 -CMD ["yarn", "start"] +ENV PORT 8080 + +CMD ["yarn", "start"] \ No newline at end of file