From 6448a2fb7401ee8748129ab38479d39186a65bed Mon Sep 17 00:00:00 2001 From: David Zuckerman Date: Wed, 19 Nov 2025 09:39:30 -0800 Subject: [PATCH] Adding github actions --- .github/workflows/build.yml | 207 ++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 59 ++++++++++ Jenkinsfile | 22 ---- docker-compose.ci.yml | 28 +++++ docker-compose.yml | 35 +----- 5 files changed, 300 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml delete mode 100644 Jenkinsfile create mode 100644 docker-compose.ci.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..ef16fc47 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,207 @@ +name: Build / Test / Push + +on: + push: + branches: + - '**' + workflow_dispatch: + +env: + BUILD_SUFFIX: -build-${{ github.run_id }}_${{ github.run_attempt }} + DOCKER_METADATA_SET_OUTPUT_ENV: 'true' + +jobs: + build: + runs-on: ${{ matrix.runner }} + outputs: + image-arm64: ${{ steps.gen-output.outputs.image-arm64 }} + image-x64: ${{ steps.gen-output.outputs.image-x64 }} + strategy: + fail-fast: false + matrix: + runner: + - ubuntu-24.04 + - ubuntu-24.04-arm + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: build-meta + name: Docker meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: type=sha,suffix=${{ env.BUILD_SUFFIX }} + + # Build cache is shared among all builds of the same architecture + - id: cache-meta + name: Docker meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: type=raw,value=buildcache-${{ runner.arch }} + + - id: get-registry + name: Get the sanitized registry name + run: | + echo "registry=$(echo '${{ steps.build-meta.outputs.tags }}' | cut -f1 -d:)" | tee -a "$GITHUB_OUTPUT" + + - id: build + name: Build/push the arch-specific image + uses: docker/build-push-action@v6 + with: + cache-from: type=registry,ref=${{ steps.cache-meta.outputs.tags }} + cache-to: type=registry,ref=${{ steps.cache-meta.outputs.tags }},mode=max + labels: ${{ steps.build-meta.outputs.labels }} + provenance: mode=max + sbom: true + tags: ${{ steps.get-registry.outputs.registry }} + outputs: type=image,push-by-digest=true,push=true + + - id: gen-output + name: Write arch-specific image digest to outputs + run: | + echo "image-${RUNNER_ARCH,,}=${{ steps.get-registry.outputs.registry }}@${{ steps.build.outputs.digest }}" | tee -a "$GITHUB_OUTPUT" + + merge: + runs-on: ubuntu-24.04 + needs: build + env: + DOCKER_APP_IMAGE_ARM64: ${{ needs.build.outputs.image-arm64 }} + DOCKER_APP_IMAGE_X64: ${{ needs.build.outputs.image-x64 }} + outputs: + image: ${{ steps.meta.outputs.tags }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + name: Generate tag for the app image + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: type=sha,suffix=${{ env.BUILD_SUFFIX }} + + - name: Push the multi-platform app image + run: | + docker buildx imagetools create \ + --tag "$DOCKER_METADATA_OUTPUT_TAGS" \ + "$DOCKER_APP_IMAGE_ARM64" "$DOCKER_APP_IMAGE_X64" + + test: + runs-on: ubuntu-24.04 + needs: merge + env: + COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml + DOCKER_APP_IMAGE: ${{ needs.merge.outputs.image }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Compose + uses: docker/setup-compose-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup the stack + run: | + docker compose build --quiet + docker compose pull --quiet + docker compose up --wait + docker compose exec -e RAILS_ENV=test app rails db:await db:setup assets:precompile + docker compose exec -u root app chown -R altmedia:altmedia artifacts + + - name: Run RSpec + if: ${{ always() }} + run: | + docker compose exec -e RAILS_ENV=test app rake coverage + # docker compose exec -e RAILS_ENV=test app rake check + # docker compose exec -e RAILS_ENV=test app rspec --format progress --format html --out artifacts/rspec.html + + - name: Run Rubocop + if: ${{ always() }} + run: | + docker compose exec -e RAILS_ENV=test app rake rubocop + + - name: Run Brakeman + if: ${{ always() }} + run: | + docker compose exec -e RAILS_ENV=test app rake brakeman + + - name: Run ESLint + if: ${{ always() }} + run: | + docker compose exec -e RAILS_ENV=test app rake bundle:audit + + - name: Copy out artifacts + if: ${{ always() }} + run: | + docker compose cp app:/opt/app/artifacts ./ + docker compose logs > artifacts/docker-compose-services.log + docker compose config > artifacts/docker-compose.merged.yml + + - name: Upload the test report + if: ${{ always() }} + uses: actions/upload-artifact@v4 + with: + name: Framework Build Report (${{ github.run_id }}_${{ github.run_attempt }}) + path: artifacts/* + if-no-files-found: error + + push: + runs-on: ubuntu-24.04 + needs: + - merge + - test + env: + DOCKER_APP_IMAGE: ${{ needs.merge.outputs.image }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Produce permanent image tags + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha + type=ref,event=branch + type=raw,value=latest,enable={{is_default_branch}} + + - name: Retag and push the image + run: | + docker pull "$DOCKER_APP_IMAGE" + echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$DOCKER_APP_IMAGE" + docker push --all-tags "$(echo "$DOCKER_APP_IMAGE" | cut -f1 -d:)" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..5d908901 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Push Release Tags + +on: + push: + tags: + - '**' + workflow_dispatch: + +env: + DOCKER_METADATA_SET_OUTPUT_ENV: 'true' + +jobs: + retag: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine the sha-based image tag to retag + id: get-base-image + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: type=sha + + - name: Verify that the image was previously built + env: + BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }} + run: | + docker pull "$BASE_IMAGE" + + - name: Produce release tags + id: tag-meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + flavor: latest=false + tags: | + type=ref,event=tag + type=semver,pattern={{major}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{version}} + + - name: Retag the pulled image + env: + BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }} + run: | + echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$BASE_IMAGE" + docker push --all-tags "$(echo "$BASE_IMAGE" | cut -f1 -d:)" diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 7b380278..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,22 +0,0 @@ -dockerComposePipeline( - stack: [template: 'postgres-selenium'], - commands: [ - [ - [exec: 'rake check RAILS_ENV=test'], - 'rake js:eslint NODE_ENV=development', - 'rake rubocop', - 'rake brakeman', - 'rake bundle:audit' - ], - ], - artifacts: [ - junit : 'artifacts/rspec/**/*.xml', - html : [ - 'Code Coverage': 'artifacts/rcov', - 'RuboCop' : 'artifacts/rubocop', - 'Brakeman' : 'artifacts/brakeman', - 'ESLint' : 'artifacts/eslint' - ], - raw : 'artifacts/capybara/**' - ] -) diff --git a/docker-compose.ci.yml b/docker-compose.ci.yml new file mode 100644 index 00000000..99311c3e --- /dev/null +++ b/docker-compose.ci.yml @@ -0,0 +1,28 @@ +services: + db: + volumes: !reset + + app: + build: !reset + environment: + - CAPYBARA_SAVE_PATH=/opt/app/artifacts + image: ${DOCKER_APP_IMAGE} + depends_on: + - selenium + volumes: !override + - artifacts:/opt/app/artifacts + ports: !reset + + worker: + build: !reset + image: ${DOCKER_APP_IMAGE} + volumes: !reset + + selenium: + volumes: !override + - artifacts:/build + + dbeaver: !reset + +volumes: + artifacts: diff --git a/docker-compose.yml b/docker-compose.yml index af2495fc..eb239a5a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,8 +10,6 @@ services: condition: service_started selenium: condition: service_started - selenium-chrome: - condition: service_started init: true networks: default: @@ -29,7 +27,6 @@ services: depends_on: - db - selenium - - selenium-chrome ports: [] db: @@ -48,45 +45,25 @@ services: - ./:/build:rw selenium: - image: selenium/hub - # image: selenium/hub:4.10.0 + image: selenium/standalone-chromium networks: default: aliases: - selenium.test ports: - - 4442:4442 - - 4443:4443 - 4444:4444 + - 7900:7900 + shm_size: 2gb volumes: - ./:/build:rw - selenium-chrome: - image: selenium/node-chromium - # image: selenium/node-chrome:4.10.0 - # shm_size: 2gb - depends_on: - - selenium - environment: - - SE_EVENT_BUS_HOST=selenium - - SE_EVENT_BUS_PUBLISH_PORT=4442 - - SE_EVENT_BUS_SUBSCRIBE_PORT=4443 - ports: - # @note Use `open vnc://localhost:55900` to view the chrome session. - # Doesn't work if you pass the "headless" option via Capybara. - # Password = "secret" - - 55900:5900 - volumes: - - /dev/shm:/dev/shm:rw - - ./:/build:rw - dbeaver: image: dbeaver/cloudbeaver:latest - container_name: dbeaver + container_name: dbeaver depends_on: - db restart: always ports: - '8080:8978' - volumes: - - ./:/build:rw \ No newline at end of file + volumes: + - ./:/build:rw