diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 6daa37f8..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,189 +0,0 @@ -version: 2.1 - -executors: - node: - docker: - - image: cimg/node:22.14 - environment: - IMAGE_NAME: mozilla/profiler-server - - # This executor is used for the python jobs (obviously). We want `pip` so that - # we can install `flake8`. We use the node flavor so that we have `yarn` - # preinstalled, but otherwise the exact version for node isn't important. - python: - docker: - - image: cimg/python:3.13-node - -orbs: - shellcheck: circleci/shellcheck@3.0.0 - -commands: - checkout-and-dependencies: - description: 'Checkout and install dependencies, managing a cache' - steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} - - v1-dependencies-{{ checksum "package.json" }} - - v1-dependencies- - # With --frozen-lockfile, the installation will fail if the lockfile is - # outdated compared to package.json. - - run: yarn install --frozen-lockfile - - save_cache: - paths: - - node_modules - key: v1-dependencies-{{ checksum "package.json" }}-{{ checksum "yarn.lock" }} - -workflows: - version: 2 - # This is the main workflow, triggered for every push and PR. - # Only docker-publish is triggered for push on the master and production - # banches only. - main: - jobs: - - tests - - python - - lint - - build - - yarn_lock - - typescript - - license-check - - shellcheck/check: - name: 'Shellcheck' - - docker - - docker-publish: - requires: - - docker - filters: - branches: - only: - - master - - production - - # This is a weekly workflow, just to deploy new versions of the docker images, - # so that updates to the underlying images are used and deployed. - # This is configured to run on Thursdays (4th day in the week, Sunday is 0). - weekly: - triggers: - - schedule: - cron: '0 0 * * 4' - filters: - branches: - only: - - master - - production - jobs: - - docker - - docker-publish: - requires: - - docker - -jobs: - lint: - executor: node - steps: - - checkout-and-dependencies - - run: yarn lint-js - - run: yarn prettier-run - - tests: - executor: node - steps: - - checkout-and-dependencies - - run: yarn test --runInBand - - python: - executor: python - steps: - - checkout - # This job needs flake8 to lint python code, let's install it here only - # It's fairly quick (2s) so we won't try to cache it. - - run: pip install --user flake8 - - run: yarn test-python - - run: yarn lint-python - - typescript: - executor: node - steps: - - checkout-and-dependencies - - run: yarn ts - - yarn_lock: - executor: node - steps: - - checkout-and-dependencies - - run: yarn test-lockfile - - build: - executor: node - steps: - - checkout-and-dependencies - - run: | - yarn build:clean - yarn build - - license-check: - executor: node - steps: - - checkout-and-dependencies - - run: yarn license-check - - docker: - executor: node - steps: - - checkout-and-dependencies - # This sets up a remote environment that's necessary to run docker commands. - - setup_remote_docker - - run: - name: 'Build the Docker image' - command: yarn docker:build --pull --build-arg circle_build_url=$CIRCLE_BUILD_URL - - run: - name: "Start the docker image in a container and check it's running" - command: | - cp .env.example .env - yarn docker:run:detached - # Wait up to 10 seconds that the server is launched. - timeout 10s sh \<<'EOF' - while ! docker exec profiler-server curl -i --silent --show-error --fail http://localhost:8000/__version__ ; do - sleep 1 - done - EOF - yarn docker:stop - - run: - name: Archive Docker image - command: docker save -o image.tar profiler-server:dev - # This makes it possible to load this image from subsequent jobs. - - persist_to_workspace: - root: . - paths: - - ./image.tar - - docker-publish: - executor: node - steps: - # Let's attach the workspace so that we can reload the saved image later. - - attach_workspace: - at: /tmp/workspace - # This sets up a remote environment that's necessary to run docker commands. - - setup_remote_docker - - run: - name: Load archived Docker image - command: docker load -i /tmp/workspace/image.tar - - run: - name: Publish Docker Image to Docker Hub - command: | - # Use both a version tag depending on the circle's build number - # and a latest tag that's depending on the branch name. - # Please keep the version in sync with the version file generated in - # bin/generate-version-file.js. - IMAGE_VERSION_TAG="0.0.${CIRCLE_BUILD_NUM}" - IMAGE_LATEST_TAG="${CIRCLE_BRANCH}-latest" - docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_LATEST_TAG - docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_VERSION_TAG - - # Deploy to docker hub using the environment variables passed from - # CircleCI configuration. - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin - docker push $IMAGE_NAME:$IMAGE_LATEST_TAG - docker push $IMAGE_NAME:$IMAGE_VERSION_TAG diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..813b085a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,233 @@ +name: CI + +on: + push: + branches: + - master + - production + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run lint-js + run: yarn lint-js + + - name: Run prettier check + run: yarn prettier-run + + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run tests + run: yarn test --runInBand + + python: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install Python dependencies + run: pip install --user flake8 + + - name: Install Node dependencies + run: yarn install --frozen-lockfile + + - name: Run Python tests + run: yarn test-python + + - name: Run Python lint + run: yarn lint-python + + typescript: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Run TypeScript check + run: yarn ts + + yarn_lock: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Test lockfile + run: yarn test-lockfile + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Build project + run: | + yarn build:clean + yarn build + + license-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Check licenses + run: yarn license-check + + shellcheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + + docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + run: yarn docker:build --pull --build-arg build_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} --build-arg github_ref_name=${{ github.ref_name }} + + - name: Test Docker image + run: | + cp .env.example .env + yarn docker:run:detached + # Wait up to 10 seconds that the server is launched. + timeout 10s sh <<'EOF' + while ! docker exec profiler-server curl -i --silent --show-error --fail http://localhost:8000/__version__ ; do + sleep 1 + done + EOF + yarn docker:stop + + - name: Save Docker image + run: docker save -o image.tar profiler-server:dev + + # This makes it possible to load this image from subsequent jobs. + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: docker-image + path: image.tar + retention-days: 1 + + # This job only runs on pushes to master/production branches, not on PRs. + docker-publish: + runs-on: ubuntu-latest + needs: docker + if: github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/production') + steps: + - name: Download Docker image artifact + uses: actions/download-artifact@v4 + with: + name: docker-image + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Load Docker image + run: docker load -i image.tar + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Publish Docker Image to Docker Hub + env: + IMAGE_NAME: mozilla/profiler-server + run: | + # Use both a version tag depending on the github's run number + # and a latest tag that's depending on the branch name. + # Please keep the version in sync with the version file generated in + # bin/generate-version-file.js. + IMAGE_VERSION_TAG="0.1.${{ github.run_number }}" + IMAGE_LATEST_TAG="${{ github.ref_name }}-latest" + docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_LATEST_TAG + docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_VERSION_TAG + docker push $IMAGE_NAME:$IMAGE_LATEST_TAG + docker push $IMAGE_NAME:$IMAGE_VERSION_TAG diff --git a/.github/workflows/weekly-docker.yml b/.github/workflows/weekly-docker.yml new file mode 100644 index 00000000..14e7b27a --- /dev/null +++ b/.github/workflows/weekly-docker.yml @@ -0,0 +1,90 @@ +name: Weekly Docker Build + +# This is a weekly workflow, just to deploy new versions of the docker images, +# so that updates to the underlying images are used and deployed. +# This is configured to run on Thursdays (4th day in the week, Sunday is 0). +on: + schedule: + - cron: '0 0 * * 4' + # Allows manual triggering + workflow_dispatch: + +jobs: + docker: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/production' + steps: + - uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'yarn' + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + run: yarn docker:build --pull --build-arg build_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} --build-arg github_ref_name=${{ github.ref_name }} + + - name: Test Docker image + run: | + cp .env.example .env + yarn docker:run:detached + # Wait up to 10 seconds that the server is launched. + timeout 10s sh <<'EOF' + while ! docker exec profiler-server curl -i --silent --show-error --fail http://localhost:8000/__version__ ; do + sleep 1 + done + EOF + yarn docker:stop + + - name: Save Docker image + run: docker save -o image.tar profiler-server:dev + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: docker-image + path: image.tar + retention-days: 1 + + docker-publish: + runs-on: ubuntu-latest + needs: docker + steps: + - name: Download Docker image artifact + uses: actions/download-artifact@v4 + with: + name: docker-image + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Load Docker image + run: docker load -i image.tar + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USER }} + password: ${{ secrets.DOCKER_PASS }} + + - name: Publish Docker Image to Docker Hub + env: + IMAGE_NAME: mozilla/profiler-server + run: | + # Use both a version tag depending on the github's run number + # and a latest tag that's depending on the branch name. + # Please keep the version in sync with the version file generated in + # bin/generate-version-file.js. + IMAGE_VERSION_TAG="0.1.${{ github.run_number }}" + IMAGE_LATEST_TAG="${{ github.ref_name }}-latest" + docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_LATEST_TAG + docker tag profiler-server:dev $IMAGE_NAME:$IMAGE_VERSION_TAG + docker push $IMAGE_NAME:$IMAGE_LATEST_TAG + docker push $IMAGE_NAME:$IMAGE_VERSION_TAG diff --git a/Dockerfile b/Dockerfile index 6150dd98..4989eacf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -50,10 +50,12 @@ WORKDIR /app RUN node -v RUN yarn -v -# This environment variable from CircleCI is needed when generating the -# version file. We pass it using the "arguments" mechanism from docker. -ARG circle_build_url -ENV CIRCLE_BUILD_URL=${circle_build_url} +# These environment variables from GitHub Actions are needed when generating the +# version file. We pass them using the "arguments" mechanism from docker. +ARG build_url +ARG github_ref_name +ENV BUILD_URL=${build_url} +ENV GITHUB_REF_NAME=${github_ref_name} # We run all these commands in one RUN command. The reason is that we don't save # the development dependencies in a layer, and we can also keep yarn's cache @@ -71,8 +73,8 @@ RUN set -x \ # Actually build the project. && yarn build:clean \ && NODE_ENV=production yarn build \ -# This script doesn't work outside of CircleCI - && if [ -n "$CIRCLE_BUILD_URL" ] ; then yarn generate-version-file ; fi \ +# This script doesn't work outside of GitHub Actions. + && if [ -n "$BUILD_URL" ] ; then yarn generate-version-file ; fi \ # Then keep only prod dependencies, that we'll copy over to the runtime # container in the next phase. && yarn install --frozen-lockfile --prod \ diff --git a/bin/generate-version-file.js b/bin/generate-version-file.js index e2758825..8b72db18 100644 --- a/bin/generate-version-file.js +++ b/bin/generate-version-file.js @@ -6,7 +6,7 @@ /* This script generates the version file, as requested by the dockerflow * requirements described in * https://github.com/mozilla-services/Dockerflow/blob/master/docs/version_object.md. - * It uses the environment variables set by CircleCI. + * It uses the environment variables set by GitHub Actions. */ const fs = require('fs'); @@ -16,9 +16,9 @@ const packageJson = require('../package.json'); function checkEnvironment() { // Let's check that we have our needed environment variable. - if (!process.env.CIRCLE_BUILD_URL) { + if (!process.env.BUILD_URL) { throw new Error( - 'The environment variable CIRCLE_BUILD_URL is missing. Are we running in CircleCI?' + 'The environment variable BUILD_URL is missing. Are we running in GitHub Actions?' ); } @@ -40,6 +40,12 @@ function getGitCommitHash() { } function findLocalBranch() { + // In CI environments like GitHub Actions, use the environment variable. + if (process.env.GITHUB_REF_NAME) { + return process.env.GITHUB_REF_NAME; + } + + // Otherwise try to get it from git. const branch = execFileSync( 'git', ['symbolic-ref', '--short', '-q', 'HEAD'], @@ -55,19 +61,19 @@ function writeVersionFile() { const commitHash = getGitCommitHash(); const branch = findLocalBranch(); - const buildUrl = process.env.CIRCLE_BUILD_URL || ''; + const buildUrl = process.env.BUILD_URL || ''; // Currently we generate the version from the build url. We're confident this // is always increasing, but for sure this isn't monotonic. In the future we // might want to use a tag instead. - const circleBuildNumResult = /\d+$/.exec(buildUrl); - if (!circleBuildNumResult) { + const buildNumResult = /\d+$/.exec(buildUrl); + if (!buildNumResult) { throw new Error( `We couldn't extract a build num from the build URL, this shouldn't happen. The full build url is: ${buildUrl}.` ); } // Please keep it in sync with the version used for the docker image in - // .circleci/config.yml. - const version = `0.0.${circleBuildNumResult[0]}`; + // .github/workflows/weekly-docker.yml. + const version = `0.1.${buildNumResult[0]}`; const distDir = 'dist'; const targetName = path.join(distDir, 'version.json'); diff --git a/bin/pre-install.js b/bin/pre-install.js index e7b1ba29..3ba4a359 100644 --- a/bin/pre-install.js +++ b/bin/pre-install.js @@ -120,15 +120,15 @@ function checkYarn(agents) { } function parseExpectedNodeVersion() { - // Let's fetch our minimal version from circleci's file + // Let's fetch our minimal version from GitHub Actions workflow file. const fs = require('fs'); - const circleConfig = fs.readFileSync('.circleci/config.yml', { + const workflowConfig = fs.readFileSync('.github/workflows/ci.yml', { encoding: 'utf8', }); - const expectedNodeVersion = /image: cimg\/node:([\d.]+)/.exec(circleConfig); + const expectedNodeVersion = /node-version: '([\d.]+)'/.exec(workflowConfig); if (!expectedNodeVersion) { throw new Error( - `Couldn't extract the node version from .circleci/config.yml.` + `Couldn't extract the node version from .github/workflows/ci.yml.` ); } return expectedNodeVersion[1]; diff --git a/src/routes/dockerflow.ts b/src/routes/dockerflow.ts index da9965b7..d9b9be1c 100644 --- a/src/routes/dockerflow.ts +++ b/src/routes/dockerflow.ts @@ -20,7 +20,7 @@ export function dockerFlowRoutes() { // "Respond to /__version__ with the contents of /app/version.json." // This file is generated by the script bin/generate-verison-file.js at build - // time in CircleCI and lives in dist/version.json. + // time in GitHub Actions and lives in dist/version.json. // We serve it directly if present, otherwise returns a 404. router.get('/__version__', async (ctx) => { // We try to get the version file in the current working directory. diff --git a/test/api/fixtures/version.json b/test/api/fixtures/version.json index 0cefe677..42ef9a9a 100644 --- a/test/api/fixtures/version.json +++ b/test/api/fixtures/version.json @@ -2,5 +2,5 @@ "source": "https://github.com/firefox-devtools/profiler-server", "version": "1.0", "commit": "ababf0c58db9e8760a2320c57ac404ce0f9c2d78", - "build": "https://circleci.com/gh/firefox-devtools/profiler-server/12" + "build": "https://github.com/firefox-devtools/profiler-server/actions/runs/12" }