diff --git a/.github/workflows/deploy-catalog.yaml b/.github/workflows/deploy-catalog.yaml index a28cee1e1f9..677d2c638ca 100644 --- a/.github/workflows/deploy-catalog.yaml +++ b/.github/workflows/deploy-catalog.yaml @@ -1,4 +1,4 @@ -name: Deploy catalog to ECR +name: "Catalog: build & push" on: push: @@ -8,61 +8,87 @@ on: - '.github/workflows/deploy-catalog.yaml' - 'catalog/**' - 'shared/**' + pull_request: + paths: + - '.github/workflows/deploy-catalog.yaml' + - 'catalog/**' + - 'shared/**' jobs: - deploy-catalog-ecr: + build: runs-on: ubuntu-latest - defaults: - run: - working-directory: catalog - # These permissions are needed to interact with GitHub's OIDC Token endpoint. permissions: - id-token: write contents: read steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v7 + - uses: docker/setup-buildx-action@v4 + - name: Build image + uses: docker/build-push-action@v7 + with: + context: . + file: catalog/Dockerfile + tags: catalog:${{ github.sha }} + push: false + # On master, export a tarball for the push jobs below; on pull + # requests the build itself is the check and nothing consumes it. + outputs: ${{ github.event_name == 'push' && 'type=docker,dest=/tmp/catalog-image.tar' || '' }} + # One cache scope for both events: master builds warm the cache PRs + # read (the reverse is impossible — PR caches are isolated). + # ignore-error: fork PRs can't write the cache; worst case anywhere + # is a colder next build. + cache-from: type=gha,scope=catalog + cache-to: type=gha,mode=max,scope=catalog,ignore-error=true + - uses: actions/upload-artifact@v7 + if: github.event_name == 'push' with: - node-version-file: 'catalog/package.json' - cache: 'npm' - cache-dependency-path: 'catalog/package-lock.json' - - run: npm ci - - run: npm run build - - name: Configure AWS credentials from Prod account - uses: aws-actions/configure-aws-credentials@v6 + name: catalog-image + path: /tmp/catalog-image.tar + retention-days: 1 + + push: + if: github.event_name == 'push' + needs: build + runs-on: ubuntu-latest + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + strategy: + # Targets are independent, so a failed one is retried alone via "Re-run + # failed jobs" — necessary for mp, which can't be re-pushed under the + # same tag, so any re-run replaying its successful push would fail. + fail-fast: false + matrix: + include: + - target: prod + role: arn:aws:iam::730278974607:role/github/GitHub-Quilt + region: us-east-1 + repository: quiltdata/catalog + - target: mp + role: arn:aws:iam::730278974607:role/github/GitHub-Quilt + region: us-east-1 + registry_id: "709825985650" + repository: quilt-data/quilt-payg-catalog + - target: govcloud + role: arn:aws-us-gov:iam::313325871032:role/github/GitHub-Quilt + region: us-gov-east-1 + repository: quiltdata/catalog + steps: + - uses: actions/download-artifact@v8 with: - role-to-assume: arn:aws:iam::730278974607:role/github/GitHub-Quilt - aws-region: us-east-1 - - name: Login to Prod ECR - id: login-prod-ecr - uses: aws-actions/amazon-ecr-login@v2 - - name: Login to MP ECR - id: login-mp-ecr - uses: aws-actions/amazon-ecr-login@v2 + name: catalog-image + path: /tmp + - run: docker load -i /tmp/catalog-image.tar + - uses: aws-actions/configure-aws-credentials@v6 with: - registries: 709825985650 - - name: Configure AWS credentials from GovCloud account - uses: aws-actions/configure-aws-credentials@v6 + role-to-assume: ${{ matrix.role }} + aws-region: ${{ matrix.region }} + - uses: aws-actions/amazon-ecr-login@v2 + id: ecr with: - role-to-assume: arn:aws-us-gov:iam::313325871032:role/github/GitHub-Quilt - aws-region: us-gov-east-1 - - name: Login to GovCloud ECR - id: login-govcloud-ecr - uses: aws-actions/amazon-ecr-login@v2 - - name: Build and push Docker image to Prod, MP and GovCloud ECR + registries: ${{ matrix.registry_id }} + - name: Tag and push env: - ECR_REGISTRY_PROD: ${{ steps.login-prod-ecr.outputs.registry }} - ECR_REGISTRY_GOVCLOUD: ${{ steps.login-govcloud-ecr.outputs.registry }} - ECR_REGISTRY_MP: ${{ steps.login-mp-ecr.outputs.registry }} - ECR_REPOSITORY: quiltdata/catalog - ECR_REPOSITORY_MP: quilt-data/quilt-payg-catalog - IMAGE_TAG: ${{ github.sha }} + IMAGE: ${{ steps.ecr.outputs.registry }}/${{ matrix.repository }}:${{ github.sha }} run: | - docker buildx build \ - -t $ECR_REGISTRY_PROD/$ECR_REPOSITORY:$IMAGE_TAG \ - -t $ECR_REGISTRY_GOVCLOUD/$ECR_REPOSITORY:$IMAGE_TAG \ - -t $ECR_REGISTRY_MP/$ECR_REPOSITORY_MP:$IMAGE_TAG \ - . - docker push $ECR_REGISTRY_PROD/$ECR_REPOSITORY:$IMAGE_TAG - docker push $ECR_REGISTRY_GOVCLOUD/$ECR_REPOSITORY:$IMAGE_TAG - docker push $ECR_REGISTRY_MP/$ECR_REPOSITORY_MP:$IMAGE_TAG + docker tag catalog:${{ github.sha }} "$IMAGE" + docker push "$IMAGE" diff --git a/catalog/.dockerignore b/catalog/.dockerignore deleted file mode 100644 index cf7098890e3..00000000000 --- a/catalog/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -**/node_modules diff --git a/catalog/Dockerfile b/catalog/Dockerfile index 1f23542ba76..3de38e597d8 100644 --- a/catalog/Dockerfile +++ b/catalog/Dockerfile @@ -1,3 +1,17 @@ +# Build context is the repo root, not catalog/ — the webpack build resolves +# modules from ../shared (see internals/webpack/webpack.base.js). +FROM node:26-trixie-slim@sha256:715e55e4b84e4bb0ff48e49b398a848f08e55daed8eb6a0ea1839ae53bc57583 AS build + +WORKDIR /src/catalog + +# Manifests only, so editing app sources doesn't invalidate `npm ci`. +COPY catalog/package.json catalog/package-lock.json ./ +RUN --mount=type=cache,target=/root/.npm npm ci + +COPY shared /src/shared +COPY catalog ./ +RUN npm run build + FROM amazonlinux:2023.11.20260406.2 MAINTAINER Quilt Data, Inc. support@quilt.bio @@ -13,16 +27,15 @@ RUN ln -sf /dev/stdout /var/log/nginx/access.log && \ ln -sf /dev/stderr /var/log/nginx/error.log # Set up nginx -COPY nginx.conf /etc/nginx/nginx.conf -COPY nginx-web.conf /etc/nginx/conf.d/default.conf +COPY catalog/nginx.conf /etc/nginx/nginx.conf +COPY catalog/nginx-web.conf /etc/nginx/conf.d/default.conf ARG NGINX_STATIC_DIR=/usr/share/nginx/html -# Copy pre-built catalog assets to nginx RUN rm -rf $NGINX_STATIC_DIR -COPY build $NGINX_STATIC_DIR +COPY --from=build /src/catalog/build $NGINX_STATIC_DIR # Copy config file -COPY config.json.tmpl config.json.tmpl +COPY catalog/config.json.tmpl config.json.tmpl RUN ln -s /tmp/config.json $NGINX_STATIC_DIR/config.json && \ ln -s /tmp/config.js $NGINX_STATIC_DIR/config.js diff --git a/catalog/Dockerfile.dockerignore b/catalog/Dockerfile.dockerignore new file mode 100644 index 00000000000..b5de573278b --- /dev/null +++ b/catalog/Dockerfile.dockerignore @@ -0,0 +1,17 @@ +# Scoped to catalog/Dockerfile (BuildKit reads .dockerignore), so +# other images' build contexts are unaffected. +# +# The context is the repo root, but the build only needs catalog/ and shared/. +# Allow-list rather than deny-list: a new top-level directory stays out by +# default instead of silently bloating the context. +* +!catalog +!shared + +# Local artifacts that would otherwise leak in from a dirty working tree. +# catalog/build especially: a stale one must not shadow the in-image build. +catalog/node_modules +catalog/build +catalog/coverage +catalog/stats.json +**/.DS_Store