From 0859dca75bc017e179a563bee3be3e4554f17a8a Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Thu, 15 Jan 2026 17:36:27 -0500 Subject: [PATCH 01/22] wip ruby and deployment updates --- .docker/build_and_push.sh | 8 + .docker/development/Dockerfile | 8 +- .docker/production/Dockerfile | 4 +- .docker/production/Dockerfile.gha | 142 ++++++++ .github/workflows/build_and_deploy.yml | 185 ++++++++++ .github/workflows/ruby.yml | 4 +- Gemfile | 2 +- Gemfile.lock | 474 +++++++++++++------------ docker-compose.prod.yml | 4 +- docker-compose.yml | 4 +- 10 files changed, 606 insertions(+), 229 deletions(-) create mode 100644 .docker/build_and_push.sh create mode 100644 .docker/production/Dockerfile.gha create mode 100644 .github/workflows/build_and_deploy.yml diff --git a/.docker/build_and_push.sh b/.docker/build_and_push.sh new file mode 100644 index 0000000..c370f1b --- /dev/null +++ b/.docker/build_and_push.sh @@ -0,0 +1,8 @@ +# Build the Docker image with specified arguments +docker build --build-arg BUNDLER_VERSION='2.4.19' -f .docker/production/Dockerfile --target app -t ghcr.io/ideacrew/cartafact:cartafact-release-tag . + +# Push the Docker image to the GitHub Container Registry +docker push ghcr.io/ideacrew/cartafact:cartafact-release-tag + +docker push +echo "Docker image build and push completed." \ No newline at end of file diff --git a/.docker/development/Dockerfile b/.docker/development/Dockerfile index 37a7a38..a01362f 100644 --- a/.docker/development/Dockerfile +++ b/.docker/development/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.6.3 AS cartafact_base +FROM ruby:3.2.5 AS cartafact_base ENV HOME /cartafact @@ -32,5 +32,9 @@ COPY . $HOME ENV RAILS_ENV='development' ENV NODE_ENV='development' -RUN bundle config set deployment 'true' && \ +# Generate Gemfile.lock if it doesn't exist, then install with deployment mode +RUN if [ ! -f Gemfile.lock ]; then \ + bundle install; \ + fi && \ + bundle config set deployment 'true' && \ bundle install diff --git a/.docker/production/Dockerfile b/.docker/production/Dockerfile index 368e91b..a21066b 100644 --- a/.docker/production/Dockerfile +++ b/.docker/production/Dockerfile @@ -2,7 +2,7 @@ ### app config ### ######################## -FROM ruby:2.6.3 AS app +FROM ruby:3.2.5 AS app ENV HOME /cartafact @@ -25,7 +25,7 @@ ENV BUNDLER_VERSION=$BUNDLER_VERSION RUN echo "\n\nbundler version: "$BUNDLER_VERSION RUN apt-get update && \ apt-get -yq dist-upgrade && \ - apt-get install -y tzdata build-essential vim && \ + apt-get install -y tzdata build-essential vim libjemalloc2 && \ apt-get autoremove -y && \ gem update --system && \ gem install bundler:$BUNDLER_VERSION diff --git a/.docker/production/Dockerfile.gha b/.docker/production/Dockerfile.gha new file mode 100644 index 0000000..3273da0 --- /dev/null +++ b/.docker/production/Dockerfile.gha @@ -0,0 +1,142 @@ +############################################ +### Base image ### +############################################ + +# Taken from .ruby-version +ARG RUBY_VERSION=3.2.5 +FROM ruby:$RUBY_VERSION-slim-bookworm as base +LABEL author="DCHBX" + +ARG DEBIAN_FRONTEND=noninteractive + +# Taken from Gemfile.lock +ARG BUNDLER_VERSION=2.4.19 +ENV BUNDLER_VERSION=$BUNDLER_VERSION + +ARG NODE_MAJOR=20 +ENV NODE_MAJOR=$NODE_MAJOR + +RUN apt-get update -qq && \ + apt-get install -yq --no-install-recommends \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + +# Add NodeJS to sources list +RUN curl -fsSL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash - + +# Basic packages needed because we're using the slim image +RUN apt-get update \ + && apt-get -yq dist-upgrade \ + && apt-get install -y \ + fontconfig \ + libcurl4-openssl-dev \ + libffi-dev \ + libsodium23 \ + libxext6 \ + libxrender1 \ + libyaml-cpp-dev \ + nodejs \ + default-jre \ + openssl \ + sshpass \ + unzip \ + zip \ + zlib1g \ + libjemalloc2 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + +# Configure bundler and PATH, install bundler version +ENV GEM_HOME=/usr/local/bundle +ENV BUNDLE_PATH=$GEM_HOME +ENV BUNDLE_APP_CONFIG=$BUNDLE_PATH +ENV BUNDLE_BIN=/usr/local/bin +ENV BUNDLE_JOBS=4 +ENV BUNDLE_RETRY=3 + +ENV LANG=C.UTF-8 + +ENV HOME=/cartafact + +ENV PATH=$HOME/bin:$BUNDLE_BIN:$GEM_HOME/gems/bin:$PATH + +# rubygems-update > 3.4.22 requires ruby >= 3.0.0 +RUN gem update --system 3.4.22\ + && rm -f /usr/local/bin/ruby/gems/*/specifications/default/bundler-*.gemspec \ + && gem install bundler -v $BUNDLER_VERSION + +RUN groupadd --gid 1001 nonroot \ + && useradd --uid 1001 --gid nonroot --shell /bin/bash --create-home nonroot + +RUN mkdir $HOME \ + && chown -R nonroot:nonroot $HOME + +# Configure app home directory +WORKDIR $HOME + + +############################################################################### +### Builder. Adds node and Yarn. Not necessary in production. ### +############################################################################### + +FROM base as builder + +ARG DEBIAN_FRONTEND=noninteractive + +# Add Yarn to the sources list +RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list + +# Install Nodejs & Yarn +RUN apt-get update -qq \ + && apt-get install -yq --no-install-recommends \ + build-essential \ + git \ + libpq-dev \ + yarn \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ + && truncate -s 0 /var/log/*log + + +######################################################## +### Node and Bundle for production ### +######################################################## + +FROM builder as prod_gems_and_assets + +ENV RAILS_ENV=production + +ARG HOSTNAME=localhost + +COPY --chown=nonroot:nonroot ./Gemfile $HOME/Gemfile +COPY --chown=nonroot:nonroot ./Gemfile.lock $HOME/Gemfile.lock + +RUN bundle config set --local without 'development test' \ + && bundle install + +COPY --chown=nonroot:nonroot . $HOME + +# https://github.com/rubygems/rubygems/issues/3225 +RUN rm -rf $GEM_HOME/bundle/ruby/*/cache + +################################################################ +### Deployable image ### +################################################################ + +FROM base as deploy + +# Copy prebuilt gems +COPY --chown=nonroot:nonroot --from=prod_gems_and_assets $BUNDLE_PATH $BUNDLE_PATH + +# Copy all app code again (sans gems, node_modules, assets) +COPY --chown=nonroot:nonroot . $HOME + +USER nonroot + +ENV RAILS_ENV=production + +ENTRYPOINT ["bin/docker-entrypoint"] \ No newline at end of file diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml new file mode 100644 index 0000000..76a7541 --- /dev/null +++ b/.github/workflows/build_and_deploy.yml @@ -0,0 +1,185 @@ +name: Build Image and Deploy + +on: + workflow_dispatch: + push: + branches: + - "main" + - "rails_7_upgrade" + pull_request: + branches: + - "main" + - "rails_7_upgrade" + +concurrency: + group: docker-${{ github.ref }} + cancel-in-progress: true + +jobs: + prep: + runs-on: ubuntu-latest + outputs: + taggedImage: ${{ steps.prep.outputs.tagged_image }} + tag: ${{ steps.prep.outputs.tag }} + registryGhcr: ${{ steps.prep.outputs.registry_ghcr }} + shortSha: ${{ steps.prep.outputs.short_sha}} + branchName: ${{ steps.prep.outputs.branch_name }} + latestTag: ${{ steps.prep.outputs.latest_tag }} + repositoryName: ${{ steps.prep.outputs.repository_name }} + steps: + - name: Git branch name + id: git-branch-name + uses: EthanSK/git-branch-name-action@v1 + - name: Prepare info + id: prep + run: | + SHORT_SHA=$(echo $GITHUB_SHA | head -c7) + REPO=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}') + TAG=${{ env.GIT_BRANCH_NAME }}-$(echo $GITHUB_SHA | head -c7) + IMAGE=dchbx/$REPO + echo ::set-output name=tagged_image::${IMAGE}:${TAG} + echo ::set-output name=tag::${TAG} + echo ::set-output name=registry_ghcr::ghcr.io + echo ::set-output name=short_sha::$SHORT_SHA + echo ::set-output name=branch_name::${{ env.GIT_BRANCH_NAME }} + echo ::set-output name=repository_name::$REPO + echo ::set-output name=latest_tag::${IMAGE}:latest + + # Uses buildx to build and push the image + build-and-upload-image: + needs: [prep] + runs-on: ubuntu-latest + services: + mongo: + image: mongo:4.2 + ports: + - 27017:27017 + options: >- + --name "mongo" + --health-cmd mongo + --health-interval 10s + --health-timeout 5s + --health-retries 5 + steps: + # Check out repository + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + with: + install: true + version: v0.9.1 + + - name: Cache Docker layers + uses: actions/cache@v4 + with: + path: /tmp/.buildx-cache + # Key is named differently to avoid collision + key: ${{ runner.os }}-multi-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-multi-buildx + + # Provide credentials for AWS + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: us-east-1 + + # Must use docker login in order to specify public registry + - name: Login to ECR + uses: docker/login-action@v1 + with: + registry: ${{ secrets.AWS_ECR }} + username: ${{ secrets.AWS_ACCESS_KEY_ID }} + password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Login to GHCR + uses: docker/login-action@v2 + with: + registry: ${{ needs.prep.outputs.registryGhcr }} + username: ${{ github.actor }} + password: ${{ secrets.dchbx_deployments_token }} + + - name: Build Image + uses: docker/build-push-action@v2 + with: + context: . + builder: ${{ steps.buildx.outputs.name }} + file: .docker/production/Dockerfile.gha + # Set the desired build target here + target: deploy + # needed to access mongo and rabbit on GHA machine + network: host + # send to public registry if not a pull request + push: ${{ github.event_name != 'pull_request' }} + # create local image (for scanning) if it is a pull request + load: ${{ github.event_name == 'pull_request' }} + tags: | + ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.latestTag) }} + ${{ format('{0}/{1}', secrets.AWS_ECR, needs.prep.outputs.taggedImage) }} + ${{ format('{0}/{1}', secrets.AWS_ECR, needs.prep.outputs.latestTag) }} + cache-from: type=local,src=/tmp/.buildx-cache + # Note the mode=max here + # More: https://github.com/moby/buildkit#--export-cache-options + # And: https://github.com/docker/buildx#--cache-tonametypetypekeyvalue + cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new + build-args: | + HOSTNAME=172.17.0.1 + GEM_OAUTH_TOKEN=${{ secrets.dchbx_deployments_token }} + + - name: Scan Docker image + if: github.event_name != 'pull_request' + id: scan + uses: anchore/scan-action@main + with: + image: ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} + acs-report-enable: true + fail-build: false + severity-cutoff: critical + +# - name: upload Anchore scan SARIF report +# if: github.event_name != 'pull_request' +# uses: github/codeql-action/upload-sarif@v2 +# with: +# sarif_file: ${{ steps.scan.outputs.sarif }} + + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache + +# enable slack when image is ready + + # notify-slack: + # if: github.event_name != 'pull_request' + # needs: [prep, build-and-upload-image] + # runs-on: ubuntu-latest + # strategy: + # matrix: + # registry: ['ghcr.io','ecr.us-east-1.amazonaws.com'] + # steps: + # - name: Post to a Slack channel + # id: slack + # uses: slackapi/slack-github-action@v1.16.0 + # with: + # channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' + # payload: | + # { + # "blocks": [ + # { + # "type": "section", + # "text": { + # "type": "mrkdwn", + # "text": "*${{ format('{0} image*:\n`{1}/{2}`', matrix.registry, matrix.registry, needs.prep.outputs.taggedImage) }}" + # } + # }, + # { + # "type": "divider" + # } + # ] + # } + # env: + # SLACK_BOT_TOKEN: ${{ secrets.DC_DEPLOY_SLACK_BOT_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index ca8e84a..543a7e9 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -14,10 +14,10 @@ jobs: uses: wbari/start-mongoDB@v0.2 with: mongoDBVersion: 3.6 - - name: Set up Ruby 2.6.3 + - name: Set up Ruby 3.2.5 uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6.3 + ruby-version: 3.2.5 - name: Cache Gems uses: actions/cache@v4 with: diff --git a/Gemfile b/Gemfile index 3214047..da54344 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -ruby '2.6.3' +ruby '3.2.5' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 66440e5..b457325 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,165 +26,168 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.0.2.1) - actionpack (= 6.0.2.1) + actioncable (6.0.6.1) + actionpack (= 6.0.6.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) + actionmailbox (6.0.6.1) + actionpack (= 6.0.6.1) + activejob (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) mail (>= 2.7.1) - actionmailer (6.0.2.1) - actionpack (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) + actionmailer (6.0.6.1) + actionpack (= 6.0.6.1) + actionview (= 6.0.6.1) + activejob (= 6.0.6.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.2.1) - actionview (= 6.0.2.1) - activesupport (= 6.0.2.1) + actionpack (6.0.6.1) + actionview (= 6.0.6.1) + activesupport (= 6.0.6.1) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.2.1) - actionpack (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) + actiontext (6.0.6.1) + actionpack (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) nokogiri (>= 1.8.5) - actionview (6.0.2.1) - activesupport (= 6.0.2.1) + actionview (6.0.6.1) + activesupport (= 6.0.6.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.2.1) - activesupport (= 6.0.2.1) + activejob (6.0.6.1) + activesupport (= 6.0.6.1) globalid (>= 0.3.6) - activemodel (6.0.2.1) - activesupport (= 6.0.2.1) - activerecord (6.0.2.1) - activemodel (= 6.0.2.1) - activesupport (= 6.0.2.1) - activestorage (6.0.2.1) - actionpack (= 6.0.2.1) - activejob (= 6.0.2.1) - activerecord (= 6.0.2.1) - marcel (~> 0.3.1) - activesupport (6.0.2.1) + activemodel (6.0.6.1) + activesupport (= 6.0.6.1) + activerecord (6.0.6.1) + activemodel (= 6.0.6.1) + activesupport (= 6.0.6.1) + activestorage (6.0.6.1) + actionpack (= 6.0.6.1) + activejob (= 6.0.6.1) + activerecord (= 6.0.6.1) + marcel (~> 1.0) + activesupport (6.0.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - zeitwerk (~> 2.2) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - ast (2.4.0) - aws-eventstream (1.0.3) - aws-partitions (1.270.0) - aws-sdk-core (3.89.1) - aws-eventstream (~> 1.0, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.28.0) - aws-sdk-core (~> 3, >= 3.71.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.60.1) - aws-sdk-core (~> 3, >= 3.83.0) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.8.8) + public_suffix (>= 2.0.2, < 8.0) + ast (2.4.3) + aws-eventstream (1.4.0) + aws-partitions (1.1205.0) + aws-sdk-core (3.241.3) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.120.0) + aws-sdk-core (~> 3, >= 3.241.3) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.211.0) + aws-sdk-core (~> 3, >= 3.241.3) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + base64 (0.3.0) + bigdecimal (4.0.1) bindex (0.8.1) - bootsnap (1.4.5) - msgpack (~> 1.0) - bson (4.7.1) - builder (3.2.4) - byebug (11.1.1) - climate_control (0.2.0) - coderay (1.1.2) - concurrent-ruby (1.1.5) + bootsnap (1.21.1) + msgpack (~> 1.2) + bson (5.2.0) + builder (3.3.0) + byebug (12.0.0) + climate_control (1.2.0) + coderay (1.1.3) + concurrent-ruby (1.3.6) content_disposition (1.0.0) crass (1.0.6) - database_cleaner (1.8.2) - deep_merge (1.2.1) - diff-lcs (1.3) - down (5.1.1) - addressable (~> 2.5) - dry-auto_inject (0.7.0) + database_cleaner (1.99.0) + date (3.5.1) + deep_merge (1.2.2) + diff-lcs (1.6.2) + down (5.4.2) + addressable (~> 2.8) + dry-auto_inject (0.9.0) dry-container (>= 0.3.4) - dry-configurable (0.9.0) + dry-configurable (0.16.1) + dry-core (~> 0.6) + zeitwerk (~> 2.6) + dry-container (0.11.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.7) - dry-container (0.7.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.9) + dry-core (0.9.1) concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) dry-equalizer (0.3.0) - dry-events (0.2.0) + dry-events (0.4.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-inflector (0.2.0) - dry-initializer (3.0.3) - dry-logic (1.0.6) + dry-core (~> 0.9, >= 0.9) + dry-inflector (0.3.0) + dry-initializer (3.2.0) + dry-logic (1.3.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-matcher (0.8.3) - dry-core (>= 0.4.8) - dry-monads (1.3.5) + dry-core (~> 0.9, >= 0.9) + zeitwerk (~> 2.6) + dry-matcher (0.9.0) + dry-core (~> 0.4, >= 0.4.8) + dry-monads (1.5.0) concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer - dry-schema (1.4.3) + dry-core (~> 0.9, >= 0.9) + zeitwerk (~> 2.6) + dry-schema (1.11.3) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.8, >= 0.8.3) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) + dry-configurable (~> 0.16, >= 0.16) + dry-core (~> 0.9, >= 0.9) dry-initializer (~> 3.0) - dry-logic (~> 1.0) - dry-types (~> 1.2) - dry-struct (1.3.0) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) - dry-types (~> 1.3) + dry-logic (~> 1.3) + dry-types (~> 1.6) + zeitwerk (~> 2.6) + dry-struct (1.5.2) + dry-core (~> 0.9, >= 0.9) + dry-types (~> 1.6) ice_nine (~> 0.11) - dry-system (0.15.0) - concurrent-ruby (~> 1.0) + zeitwerk (~> 2.6) + dry-system (0.27.2) dry-auto_inject (>= 0.4.0) - dry-configurable (~> 0.7) - dry-container (~> 0.7, >= 0.7.2) - dry-core (~> 0.3, >= 0.3.1) - dry-equalizer (~> 0.2) + dry-configurable (~> 0.16, >= 0.16.0) + dry-container (~> 0.10, >= 0.10.0) + dry-core (~> 0.9, >= 0.9.0) dry-inflector (~> 0.1, >= 0.1.2) - dry-struct (~> 1.0) - dry-transaction (0.13.0) + zeitwerk (~> 2.6) + dry-transaction (0.14.0) dry-container (>= 0.2.8) - dry-events (>= 0.1.0) + dry-events (>= 0.4.0) dry-matcher (>= 0.7.0) - dry-monads (>= 0.4.0) - dry-types (1.3.0) + dry-monads (>= 0.5.0) + dry-types (1.6.1) concurrent-ruby (~> 1.0) dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) + dry-core (~> 0.9, >= 0.9) dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) - dry-validation (1.4.2) + dry-logic (~> 1.3, >= 1.3) + zeitwerk (~> 2.6) + dry-validation (1.9.0) concurrent-ruby (~> 1.0) dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) + dry-core (~> 0.9, >= 0.9) dry-initializer (~> 3.0) - dry-schema (~> 1.4, >= 1.4.3) - erubi (1.9.0) + dry-schema (~> 1.11, >= 1.11.0) + zeitwerk (~> 2.6) + erubi (1.13.1) factory_bot (4.11.1) activesupport (>= 3.0.0) factory_bot_rails (4.11.1) @@ -192,102 +195,124 @@ GEM railties (>= 3.0.0) fast_jsonapi (1.5) activesupport (>= 4.2) - ffi (1.12.2) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.8.2) + ffi (1.17.3-aarch64-linux-gnu) + globalid (1.1.0) + activesupport (>= 5.0) + i18n (1.14.8) concurrent-ruby (~> 1.0) ice_nine (0.11.2) - jaro_winkler (1.5.4) - jmespath (1.4.0) - jwt (2.2.1) - listen (3.1.5) + jmespath (1.6.2) + json (2.18.0) + jwt (2.2.3) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + listen (3.0.8) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.4.0) + logger (1.7.0) + loofah (2.25.0) crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) + nokogiri (>= 1.12.0) + mail (2.9.0) + logger mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2019.1009) + net-imap + net-pop + net-smtp + marcel (1.1.0) + method_source (1.1.0) + mime-types (3.7.0) + logger + mime-types-data (~> 3.2025, >= 3.2025.0507) + mime-types-data (3.2026.0113) mimemagic (0.3.10) nokogiri (~> 1) - rake (>= 0) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.0) - mongo (2.11.3) - bson (>= 4.4.2, < 5.0.0) - mongoid (7.0.5) - activemodel (>= 5.1, < 6.1) - mongo (>= 2.5.1, < 3.0.0) - msgpack (1.3.3) - nio4r (2.5.2) - nokogiri (1.10.7) - mini_portile2 (~> 2.4.0) - ox (2.13.2) - parallel (1.19.1) - parser (2.7.0.2) - ast (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - pry-byebug (3.8.0) - byebug (~> 11.0) - pry (~> 0.10) - public_suffix (4.0.3) - puma (3.12.2) - rack (2.1.2) - rack-cors (1.1.1) + rake + mini_mime (1.1.5) + minitest (5.27.0) + mongo (2.22.0) + base64 + bson (>= 4.14.1, < 6.0.0) + mongoid (7.0.13) + activemodel (>= 5.1, < 6.2) + mongo (>= 2.7.0, < 3.0.0) + msgpack (1.8.0) + net-imap (0.6.2) + date + net-protocol + net-pop (0.1.2) + net-protocol + net-protocol (0.2.2) + timeout + net-smtp (0.5.1) + net-protocol + nio4r (2.7.5) + nokogiri (1.19.0-aarch64-linux-gnu) + racc (~> 1.4) + ox (2.14.23) + bigdecimal (>= 3.0) + parallel (1.27.0) + parser (3.3.10.1) + ast (~> 2.4.1) + racc + prism (1.8.0) + pry (0.15.2) + coderay (~> 1.1) + method_source (~> 1.0) + pry-byebug (3.11.0) + byebug (~> 12.0) + pry (>= 0.13, < 0.16) + public_suffix (7.0.2) + puma (3.12.6) + racc (1.8.1) + rack (2.2.21) + rack-cors (2.0.2) rack (>= 2.0.0) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.0.2.1) - actioncable (= 6.0.2.1) - actionmailbox (= 6.0.2.1) - actionmailer (= 6.0.2.1) - actionpack (= 6.0.2.1) - actiontext (= 6.0.2.1) - actionview (= 6.0.2.1) - activejob (= 6.0.2.1) - activemodel (= 6.0.2.1) - activerecord (= 6.0.2.1) - activestorage (= 6.0.2.1) - activesupport (= 6.0.2.1) + rack-test (2.2.0) + rack (>= 1.3) + rails (6.0.6.1) + actioncable (= 6.0.6.1) + actionmailbox (= 6.0.6.1) + actionmailer (= 6.0.6.1) + actionpack (= 6.0.6.1) + actiontext (= 6.0.6.1) + actionview (= 6.0.6.1) + activejob (= 6.0.6.1) + activemodel (= 6.0.6.1) + activerecord (= 6.0.6.1) + activestorage (= 6.0.6.1) + activesupport (= 6.0.6.1) bundler (>= 1.3.0) - railties (= 6.0.2.1) + railties (= 6.0.6.1) sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + rails-dom-testing (2.3.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.2.1) - actionpack (= 6.0.2.1) - activesupport (= 6.0.2.1) + rails-html-sanitizer (1.6.2) + loofah (~> 2.21) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) + railties (6.0.6.1) + actionpack (= 6.0.6.1) + activesupport (= 6.0.6.1) method_source rake (>= 0.8.7) thor (>= 0.20.3, < 2.0) - rainbow (3.0.0) - rake (13.0.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.1) + rainbow (3.1.1) + rake (13.3.1) + rb-fsevent (0.11.2) + rb-inotify (0.11.1) ffi (~> 1.0) - rspec-core (3.9.1) - rspec-support (~> 3.9.1) - rspec-expectations (3.9.0) + regexp_parser (2.11.3) + rspec-core (3.9.3) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.4) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.9.0) - rspec-rails (3.9.0) + rspec-rails (3.9.1) actionpack (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) @@ -295,57 +320,69 @@ GEM rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) rspec-support (~> 3.9.0) - rspec-support (3.9.2) - rubocop (0.79.0) - jaro_winkler (~> 1.5.1) + rspec-support (3.9.4) + rubocop (1.82.1) + json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.48.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.49.0) + parser (>= 3.3.7.2) + prism (~> 1.7) rubocop-git (0.1.3) rubocop (>= 0.24.1) - rubocop-rspec (1.37.1) - rubocop (>= 0.68.1) - ruby-progressbar (1.10.1) - ruby_dep (1.5.0) + rubocop-rspec (3.9.0) + lint_roller (~> 1.1) + rubocop (~> 1.81) + ruby-progressbar (1.13.0) shoulda-matchers (3.1.3) activesupport (>= 4.0.0) - shrine (3.2.1) + shrine (3.6.0) content_disposition (~> 1.0) down (~> 5.1) shrine-mongoid (1.0.0) mongoid (>= 5.0) shrine (>= 3.0.0.beta3, < 4) - spring (2.1.0) + spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) spring (>= 1.2, < 3.0) - sprockets (4.0.0) + sprockets (4.2.2) concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) + logger + rack (>= 2.2.4, < 4) + sprockets-rails (3.4.2) + actionpack (>= 5.2) + activesupport (>= 5.2) sprockets (>= 3.0.0) - thor (1.0.1) + thor (1.5.0) thread_safe (0.3.6) - tzinfo (1.2.6) + timeout (0.6.0) + tzinfo (1.2.11) thread_safe (~> 0.1) - unicode-display_width (1.6.1) - web-console (4.0.1) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.2.0) + web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - websocket-driver (0.7.1) + websocket-driver (0.8.0) + base64 websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) - yard (0.9.24) - zeitwerk (2.2.2) + websocket-extensions (0.1.5) + yard (0.9.38) + zeitwerk (2.7.4) PLATFORMS - ruby + aarch64-linux DEPENDENCIES aws-sdk-s3 (~> 1.14) @@ -370,6 +407,7 @@ DEPENDENCIES fast_jsonapi jwt (~> 2.2.1) listen (>= 3.0.5, < 3.2) + mimemagic (~> 0.3.10) mongoid (~> 7.0.5) pry-byebug puma (~> 3.11) @@ -388,7 +426,7 @@ DEPENDENCIES yard RUBY VERSION - ruby 2.6.3p62 + ruby 3.2.5p208 BUNDLED WITH - 2.0.2 + 2.4.19 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d3eb8ec..7979833 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,8 +6,8 @@ services: context: . dockerfile: ./.docker/production/Dockerfile args: - RUBY_VERSION: '2.6.3' - BUNDLER_VERSION: '2.0.2' + RUBY_VERSION: '3.2.5' + BUNDLER_VERSION: '2.4.19' image: ideacrew/cartafact_app:${IMAGE_TAG:-test_prod} tmpfs: - /tmp diff --git a/docker-compose.yml b/docker-compose.yml index 089b546..b7b9f69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,8 @@ services: context: . dockerfile: ./.docker/development/Dockerfile args: - RUBY_VERSION: '2.6.3' - BUNDLER_VERSION: '2.0.2' + RUBY_VERSION: '3.2.5' + BUNDLER_VERSION: '2.4.19' image: cartafact-dev:0.1.0 volumes: - .:/app:cached From 3b10ccf25dd11eae63381e00cc5a3ac8b571cf54 Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 11:02:56 -0500 Subject: [PATCH 02/22] upgrade to ruby 3.2.5 and rails 7.2.3 --- .docker/development/Dockerfile | 9 +- .rubocop.yml | 108 ++++-- .ruby-version | 2 +- Gemfile | 28 +- Gemfile.lock | 322 ++++++++++-------- .../api/v1/documents_controller.rb | 3 +- app/controllers/application_controller.rb | 5 +- .../entities/operations/documents/persist.rb | 2 +- .../operations/documents/serialize.rb | 2 +- .../entities/operations/documents/where.rb | 2 +- app/mailers/application_mailer.rb | 1 + app/serializers/document_serializer.rb | 6 +- bin/docker-entrypoint | 9 + config.ru | 1 + config/application.rb | 2 +- config/environments/production.rb | 2 +- .../application_controller_renderer.rb | 1 + config/initializers/backtrace_silencers.rb | 1 + config/initializers/inflections.rb | 1 + config/initializers/mime_types.rb | 1 + config/initializers/shrine.rb | 8 +- config/mongoid.yml | 8 +- config/puma.rb | 30 +- db/seeds.rb | 1 + .../api/v1/documents_controller_spec.rb | 4 +- ...lidate_resource_identity_signature_spec.rb | 1 + spec/factories/documents_documents.rb | 6 - 27 files changed, 347 insertions(+), 219 deletions(-) create mode 100644 bin/docker-entrypoint delete mode 100644 spec/factories/documents_documents.rb diff --git a/.docker/development/Dockerfile b/.docker/development/Dockerfile index a01362f..733b61b 100644 --- a/.docker/development/Dockerfile +++ b/.docker/development/Dockerfile @@ -21,7 +21,7 @@ ARG BUNDLER_VERSION RUN apt-get update && \ apt-get -yq dist-upgrade && \ - apt-get install -y tzdata build-essential vim && \ + apt-get install -y tzdata build-essential vim libjemalloc2 && \ apt-get autoremove -y && \ gem update --system && \ gem install bundler:$BUNDLER_VERSION @@ -32,9 +32,4 @@ COPY . $HOME ENV RAILS_ENV='development' ENV NODE_ENV='development' -# Generate Gemfile.lock if it doesn't exist, then install with deployment mode -RUN if [ ! -f Gemfile.lock ]; then \ - bundle install; \ - fi && \ - bundle config set deployment 'true' && \ - bundle install +RUN bundle install diff --git a/.rubocop.yml b/.rubocop.yml index db69bdc..6f1d3e4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,57 +1,119 @@ +# This yaml describes our current checks. AllCops: - TargetRubyVersion: 2.6.3 + TargetRubyVersion: 3.2.5 + SuggestExtensions: false + NewCops: enable + Exclude: + - "bin/**/*" + - "vendor/**/*" + - 'node_modules/**/*' -Layout/LineLength: - Max: 120 +Layout/EmptyLineAfterGuardClause: + Enabled: false -Bundler/OrderedGems: +Layout/EmptyLinesAroundBlockBody: + Enabled: false + +Layout/ExtraSpacing: + Enabled: false + +Layout/EmptyLinesAroundClassBody: + Enabled: false + +Layout/EmptyLinesAroundModuleBody: + Enabled: false + +Layout/SpaceInsideBlockBraces: + Enabled: false + +Layout/TrailingEmptyLines: Enabled: false Metrics/AbcSize: - Max: 20 + Max: 50 + +Metrics/ClassLength: + Max: 300 + +Metrics/ModuleLength: + Max: 200 + +Metrics/CyclomaticComplexity: + Max: 11 Metrics/BlockLength: - Exclude: - - spec/**/*.rb - - app/domain/cartafact/entities/validators/documents/**/*.rb + Enabled: false -Metrics/ClassLength: +Layout/LineLength: Max: 150 Metrics/MethodLength: - Max: 20 + Max: 25 -Metrics/ModuleLength: - Max: 300 +Metrics/PerceivedComplexity: + Max: 15 -Style/BlockComments: - Exclude: - - spec/spec_helper.rb +Naming/VariableNumber: + Enabled: false -Style/BracesAroundHashParameters: +Naming/ConstantName: Enabled: false -Style/Documentation: - Exclude: - - app/controllers/application_controller.rb - - app/mailers/application_mailer.rb - - config/application.rb - - spec/**/*_spec.rb +Style/BlockComments: + Enabled: false + +Style/EachWithObject: + Enabled: false Style/EmptyLiteral: Enabled: false +Style/EmptyMethod: + Enabled: false + Style/ExpandPathArguments: Enabled: false Style/HashSyntax: Enabled: false -Style/NumericLiterals: +Style/NumericPredicate: + Enabled: false + +Style/RedundantPercentQ: + Enabled: false + +Style/RedundantSelf: Enabled: false Style/StringLiterals: Enabled: false Style/SymbolArray: + Enabled: false + +Style/TernaryParentheses: + Enabled: false + +Style/WordArray: + Enabled: false + +Style/Documentation: + Enabled: true + Exclude: + - "config/application.rb" + +Bundler/OrderedGems: + Enabled: false + +Gemspec/OrderedDependencies: + Enabled: false + +Style/RedundantConstantBase: + Enabled: false + +Style/QuotedSymbols: + Enabled: false + +Style/SymbolProc: Enabled: false \ No newline at end of file diff --git a/.ruby-version b/.ruby-version index 4560fb9..ac55709 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -ruby-2.6.3 +ruby-3.2.5 diff --git a/Gemfile b/Gemfile index da54344..0f0b1e5 100644 --- a/Gemfile +++ b/Gemfile @@ -5,15 +5,11 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '3.2.5' -# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' -gem 'rails', '~> 6.0.0' -# TODO: Remove this gem constraint when upgrading beyond Ruby 2.6.3 and Rails 6 -gem 'mimemagic', '~> 0.3.10' -# Use postgresql as the database for Active Record -# gem 'pg', '>= 0.18', '< 2.0' -gem 'mongoid', '~> 7.0.5' +gem 'rails', '~> 7.2.0' + +gem 'mongoid', '~> 8.1.5' # Use Puma as the app server -gem 'puma', '~> 3.11' +gem 'puma', '~> 6.0' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder # gem 'jbuilder', '~> 2.7' # Use Active Model has_secure_password @@ -33,7 +29,7 @@ gem "aws-sdk-s3", "~> 1.14" # Settings, validation and dependency injection # TODO: Use tagged release for resource_registry instead of branch -gem 'resource_registry', git: 'https://github.com/ideacrew/resource_registry.git', branch: 'branch_0.3.2' +gem 'resource_registry', git: 'https://github.com/ideacrew/resource_registry.git', tag: 'v0.10.1' gem 'fast_jsonapi' gem 'shrine-mongoid', '~> 1.0' @@ -56,10 +52,10 @@ gem 'jwt', '~> 2.2.1' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] - gem 'rspec-rails', '~> 3.8' - gem 'shoulda-matchers', '~> 3' - gem 'yard' # , '~> 0.9.12', require: false - gem 'climate_control' # + gem 'rspec-rails', '~> 7.0' + gem 'shoulda-matchers' + gem 'yard' + gem 'climate_control' gem 'factory_bot_rails', '~> 4.11' gem 'database_cleaner', '~> 1.7' gem 'pry-byebug' @@ -67,12 +63,12 @@ end group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring - gem 'spring' - gem 'spring-watcher-listen', '~> 2.0.0' + # gem 'spring' + # gem 'spring-watcher-listen', '~> 2.0.0' # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3' - gem 'listen', '>= 3.0.5', '< 3.2' + gem 'listen', '~> 3.3' gem 'rubocop', require: false gem 'rubocop-rspec' diff --git a/Gemfile.lock b/Gemfile.lock index b457325..5163669 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,86 +1,98 @@ GIT remote: https://github.com/ideacrew/resource_registry.git - revision: 345b24bfa823f602eebcc0c63e43bfa6b76b01e2 - branch: branch_0.3.2 + revision: a3ca0b8001247663a7f052cfdc32180dc7e1f43d + tag: v0.10.1 specs: - resource_registry (0.3.1) + resource_registry (0.10.1) bootsnap (~> 1.0) - deep_merge (~> 1.2.1) - dry-auto_inject (~> 0.6) - dry-container (~> 0.7) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-inflector (~> 0.1) - dry-initializer (~> 3.0) + deep_merge (>= 1.0.0) + dry-configurable (~> 0.12) + dry-container (~> 0.9) dry-matcher (~> 0.7) - dry-monads (~> 1.2) + dry-monads (~> 1.3) dry-struct (~> 1.0) - dry-system (~> 0.12) - dry-transaction (~> 0.13) dry-types (~> 1.0) dry-validation (~> 1.2) - i18n (~> 1.6) + i18n (>= 0.7.0) mime-types ox (~> 2.0) GEM remote: https://rubygems.org/ specs: - actioncable (6.0.6.1) - actionpack (= 6.0.6.1) + actioncable (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - mail (>= 2.7.1) - actionmailer (6.0.6.1) - actionpack (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.6.1) - actionview (= 6.0.6.1) - activesupport (= 6.0.6.1) - rack (~> 2.0, >= 2.0.8) + zeitwerk (~> 2.6) + actionmailbox (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + actionmailer (7.2.3) + actionpack (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activesupport (= 7.2.3) + mail (>= 2.8.0) + rails-dom-testing (~> 2.2) + actionpack (7.2.3) + actionview (= 7.2.3) + activesupport (= 7.2.3) + cgi + nokogiri (>= 1.8.5) + racc + rack (>= 2.2.4, < 3.3) + rack-session (>= 1.0.1) rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.6.1) - actionpack (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + useragent (~> 0.16) + actiontext (7.2.3) + actionpack (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (6.0.6.1) - activesupport (= 6.0.6.1) + actionview (7.2.3) + activesupport (= 7.2.3) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.6.1) - activesupport (= 6.0.6.1) + cgi + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activejob (7.2.3) + activesupport (= 7.2.3) globalid (>= 0.3.6) - activemodel (6.0.6.1) - activesupport (= 6.0.6.1) - activerecord (6.0.6.1) - activemodel (= 6.0.6.1) - activesupport (= 6.0.6.1) - activestorage (6.0.6.1) - actionpack (= 6.0.6.1) - activejob (= 6.0.6.1) - activerecord (= 6.0.6.1) + activemodel (7.2.3) + activesupport (= 7.2.3) + activerecord (7.2.3) + activemodel (= 7.2.3) + activesupport (= 7.2.3) + timeout (>= 0.4.0) + activestorage (7.2.3) + actionpack (= 7.2.3) + activejob (= 7.2.3) + activerecord (= 7.2.3) + activesupport (= 7.2.3) marcel (~> 1.0) - activesupport (6.0.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + activesupport (7.2.3) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) addressable (2.8.8) public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) @@ -104,6 +116,7 @@ GEM aws-sigv4 (1.12.1) aws-eventstream (~> 1, >= 1.0.2) base64 (0.3.0) + benchmark (0.5.0) bigdecimal (4.0.1) bindex (0.8.1) bootsnap (1.21.1) @@ -111,9 +124,11 @@ GEM bson (5.2.0) builder (3.3.0) byebug (12.0.0) + cgi (0.5.1) climate_control (1.2.0) coderay (1.1.3) concurrent-ruby (1.3.6) + connection_pool (3.0.2) content_disposition (1.0.0) crass (1.0.6) database_cleaner (1.99.0) @@ -122,6 +137,7 @@ GEM diff-lcs (1.6.2) down (5.4.2) addressable (~> 2.8) + drb (2.2.3) dry-auto_inject (0.9.0) dry-container (>= 0.3.4) dry-configurable (0.16.1) @@ -187,6 +203,7 @@ GEM dry-initializer (~> 3.0) dry-schema (~> 1.11, >= 1.11.0) zeitwerk (~> 2.6) + erb (6.0.1) erubi (1.13.1) factory_bot (4.11.1) activesupport (>= 3.0.0) @@ -195,20 +212,26 @@ GEM railties (>= 3.0.0) fast_jsonapi (1.5) activesupport (>= 4.2) - ffi (1.17.3-aarch64-linux-gnu) - globalid (1.1.0) - activesupport (>= 5.0) + ffi (1.17.3) + globalid (1.3.0) + activesupport (>= 6.1) i18n (1.14.8) concurrent-ruby (~> 1.0) ice_nine (0.11.2) + io-console (0.8.2) + irb (1.16.0) + pp (>= 0.6.0) + rdoc (>= 4.0.0) + reline (>= 0.4.2) jmespath (1.6.2) json (2.18.0) jwt (2.2.3) language_server-protocol (3.17.0.5) lint_roller (1.1.0) - listen (3.0.8) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + listen (3.10.0) + logger + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) logger (1.7.0) loofah (2.25.0) crass (~> 1.0.2) @@ -225,17 +248,18 @@ GEM logger mime-types-data (~> 3.2025, >= 3.2025.0507) mime-types-data (3.2026.0113) - mimemagic (0.3.10) - nokogiri (~> 1) - rake mini_mime (1.1.5) - minitest (5.27.0) + mini_portile2 (2.8.9) + minitest (6.0.1) + prism (~> 1.5) mongo (2.22.0) base64 bson (>= 4.14.1, < 6.0.0) - mongoid (7.0.13) - activemodel (>= 5.1, < 6.2) - mongo (>= 2.7.0, < 3.0.0) + mongoid (8.1.11) + activemodel (>= 5.1, < 8.1, != 7.0.0) + concurrent-ruby (>= 1.0.5, < 2.0) + mongo (>= 2.18.0, < 3.0.0) + ruby2_keywords (~> 0.0.5) msgpack (1.8.0) net-imap (0.6.2) date @@ -247,7 +271,8 @@ GEM net-smtp (0.5.1) net-protocol nio4r (2.7.5) - nokogiri (1.19.0-aarch64-linux-gnu) + nokogiri (1.19.0) + mini_portile2 (~> 2.8.2) racc (~> 1.4) ox (2.14.23) bigdecimal (>= 3.0) @@ -255,6 +280,9 @@ GEM parser (3.3.10.1) ast (~> 2.4.1) racc + pp (0.6.3) + prettyprint + prettyprint (0.2.0) prism (1.8.0) pry (0.15.2) coderay (~> 1.1) @@ -262,29 +290,38 @@ GEM pry-byebug (3.11.0) byebug (~> 12.0) pry (>= 0.13, < 0.16) + psych (5.3.1) + date + stringio public_suffix (7.0.2) - puma (3.12.6) + puma (6.6.1) + nio4r (~> 2.0) racc (1.8.1) - rack (2.2.21) - rack-cors (2.0.2) - rack (>= 2.0.0) + rack (3.2.4) + rack-cors (3.0.0) + logger + rack (>= 3.0.14) + rack-session (2.1.1) + base64 (>= 0.1.0) + rack (>= 3.0.0) rack-test (2.2.0) rack (>= 1.3) - rails (6.0.6.1) - actioncable (= 6.0.6.1) - actionmailbox (= 6.0.6.1) - actionmailer (= 6.0.6.1) - actionpack (= 6.0.6.1) - actiontext (= 6.0.6.1) - actionview (= 6.0.6.1) - activejob (= 6.0.6.1) - activemodel (= 6.0.6.1) - activerecord (= 6.0.6.1) - activestorage (= 6.0.6.1) - activesupport (= 6.0.6.1) - bundler (>= 1.3.0) - railties (= 6.0.6.1) - sprockets-rails (>= 2.0.0) + rackup (2.3.1) + rack (>= 3) + rails (7.2.3) + actioncable (= 7.2.3) + actionmailbox (= 7.2.3) + actionmailer (= 7.2.3) + actionpack (= 7.2.3) + actiontext (= 7.2.3) + actionview (= 7.2.3) + activejob (= 7.2.3) + activemodel (= 7.2.3) + activerecord (= 7.2.3) + activestorage (= 7.2.3) + activesupport (= 7.2.3) + bundler (>= 1.15.0) + railties (= 7.2.3) rails-dom-testing (2.3.0) activesupport (>= 5.0.0) minitest @@ -292,35 +329,45 @@ GEM rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (6.0.6.1) - actionpack (= 6.0.6.1) - activesupport (= 6.0.6.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + railties (7.2.3) + actionpack (= 7.2.3) + activesupport (= 7.2.3) + cgi + irb (~> 1.13) + rackup (>= 1.0.0) + rake (>= 12.2) + thor (~> 1.0, >= 1.2.2) + tsort (>= 0.2) + zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.3.1) rb-fsevent (0.11.2) rb-inotify (0.11.1) ffi (~> 1.0) + rdoc (7.1.0) + erb + psych (>= 4.0.0) + tsort regexp_parser (2.11.3) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.4) + reline (0.6.3) + io-console (~> 0.5) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (3.9.1) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.4) + rspec-support (~> 3.13.0) + rspec-rails (7.1.1) + actionpack (>= 7.0) + activesupport (>= 7.0) + railties (>= 7.0) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.6) rubocop (1.82.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) @@ -341,34 +388,26 @@ GEM lint_roller (~> 1.1) rubocop (~> 1.81) ruby-progressbar (1.13.0) - shoulda-matchers (3.1.3) - activesupport (>= 4.0.0) + ruby2_keywords (0.0.5) + securerandom (0.4.1) + shoulda-matchers (7.0.1) + activesupport (>= 7.1) shrine (3.6.0) content_disposition (~> 1.0) down (~> 5.1) shrine-mongoid (1.0.0) mongoid (>= 5.0) shrine (>= 3.0.0.beta3, < 4) - spring (2.1.1) - spring-watcher-listen (2.0.1) - listen (>= 2.7, < 4.0) - spring (>= 1.2, < 3.0) - sprockets (4.2.2) - concurrent-ruby (~> 1.0) - logger - rack (>= 2.2.4, < 4) - sprockets-rails (3.4.2) - actionpack (>= 5.2) - activesupport (>= 5.2) - sprockets (>= 3.0.0) + stringio (3.2.0) thor (1.5.0) - thread_safe (0.3.6) timeout (0.6.0) - tzinfo (1.2.11) - thread_safe (~> 0.1) + tsort (0.2.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unicode-display_width (3.2.0) unicode-emoji (~> 4.1) unicode-emoji (4.2.0) + useragent (0.16.11) web-console (4.2.1) actionview (>= 6.0.0) activemodel (>= 6.0.0) @@ -382,7 +421,7 @@ GEM zeitwerk (2.7.4) PLATFORMS - aarch64-linux + ruby DEPENDENCIES aws-sdk-s3 (~> 1.14) @@ -406,22 +445,19 @@ DEPENDENCIES factory_bot_rails (~> 4.11) fast_jsonapi jwt (~> 2.2.1) - listen (>= 3.0.5, < 3.2) - mimemagic (~> 0.3.10) - mongoid (~> 7.0.5) + listen (~> 3.3) + mongoid (~> 8.1.5) pry-byebug - puma (~> 3.11) + puma (~> 6.0) rack-cors - rails (~> 6.0.0) + rails (~> 7.2.0) resource_registry! - rspec-rails (~> 3.8) + rspec-rails (~> 7.0) rubocop rubocop-git rubocop-rspec - shoulda-matchers (~> 3) + shoulda-matchers shrine-mongoid (~> 1.0) - spring - spring-watcher-listen (~> 2.0.0) web-console (>= 3) yard diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index a4170bc..48b0943 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -4,7 +4,7 @@ module Api module V1 # Controller exposing the document API. class DocumentsController < ApplicationController - FILE_STREAM_SIZE = 32768 + FILE_STREAM_SIZE = 32_768 # Rails 7+ enforces UTF-8 parameter validation which rejects binary PDF content # Skip encoding validation for the create action to allow binary file uploads @@ -52,6 +52,7 @@ def create end include ActionController::Live + def download authorization_information = verify_authorization_headers_present return nil unless authorization_information diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e5d4b33..24170e6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# Application controller class ApplicationController < ActionController::API # before_action :authenticate @@ -9,9 +10,9 @@ def authenticate headers = request.headers if headers['Authorization'].present? begin - token = headers['Authorization'].split(' ').last + token = headers['Authorization'].split.last JWT.decode(token, jwt_secret_key) - rescue JWT::ExpiredSignature, JWT::VerificationError + rescue JWT::ExpiredSignature render json: { status: "failure", errors: ['Expired token'] } nil rescue JWT::DecodeError, JWT::VerificationError diff --git a/app/domain/cartafact/entities/operations/documents/persist.rb b/app/domain/cartafact/entities/operations/documents/persist.rb index 7c5f1f9..869fdeb 100644 --- a/app/domain/cartafact/entities/operations/documents/persist.rb +++ b/app/domain/cartafact/entities/operations/documents/persist.rb @@ -6,7 +6,7 @@ module Operations module Documents # Shared operation for create/update of document. class Persist - include Dry::Transaction::Operation + include Dry::Monads[:result] def call(input) document = ::Document.new(map_to_model_params(input.to_h)) diff --git a/app/domain/cartafact/entities/operations/documents/serialize.rb b/app/domain/cartafact/entities/operations/documents/serialize.rb index 18dc1ba..eb0fb4d 100644 --- a/app/domain/cartafact/entities/operations/documents/serialize.rb +++ b/app/domain/cartafact/entities/operations/documents/serialize.rb @@ -6,7 +6,7 @@ module Operations module Documents # Serialize a document resource to a "showable" hash. class Serialize - include Dry::Transaction::Operation + include Dry::Monads[:result] def call(input) result = ::DocumentSerializer.new(input).serializable_hash[:data][:attributes] diff --git a/app/domain/cartafact/entities/operations/documents/where.rb b/app/domain/cartafact/entities/operations/documents/where.rb index 4b9d010..cf9bedd 100644 --- a/app/domain/cartafact/entities/operations/documents/where.rb +++ b/app/domain/cartafact/entities/operations/documents/where.rb @@ -8,7 +8,7 @@ module Operations module Documents # Given identity and search criteria, return a list of document resources. class Where - include Dry::Transaction::Operation + include Dry::Monads[:result] def self.call(input) new.call(input) diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb index d84cb6e..b9708af 100644 --- a/app/mailers/application_mailer.rb +++ b/app/mailers/application_mailer.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +# Application mailer class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' diff --git a/app/serializers/document_serializer.rb b/app/serializers/document_serializer.rb index 248d99d..8a5895e 100644 --- a/app/serializers/document_serializer.rb +++ b/app/serializers/document_serializer.rb @@ -3,9 +3,13 @@ # Serializes a represented document for JSON resource format. class DocumentSerializer include FastJsonapi::ObjectSerializer + attributes :title, :creator, :identifier, :description, :language, :format, :source, :date, :creator - attribute :document_type, &:document_type + attribute :document_type do |object| + object.document_type + end + attribute :subjects do |object| object.subjects.map do |subj| { diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint new file mode 100644 index 0000000..f06279e --- /dev/null +++ b/bin/docker-entrypoint @@ -0,0 +1,9 @@ +#!/bin/bash -e + +# Enable jemalloc for reduced memory usage and latency, especially in production +if [ -z "${LD_PRELOAD+x}" ]; then + LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit) + export LD_PRELOAD +fi + +exec "${@}" \ No newline at end of file diff --git a/config.ru b/config.ru index 842bccc..9bcb8d2 100644 --- a/config.ru +++ b/config.ru @@ -5,3 +5,4 @@ require_relative 'config/environment' run Rails.application +Rails.application.load_server \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 9843efe..d327487 100644 --- a/config/application.rb +++ b/config/application.rb @@ -23,7 +23,7 @@ module App class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. - config.load_defaults 6.0 + config.load_defaults 7.1 # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers diff --git a/config/environments/production.rb b/config/environments/production.rb index 2fc664c..5df24c0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -71,7 +71,7 @@ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') if ENV["RAILS_LOG_TO_STDOUT"].present? - logger = ActiveSupport::Logger.new(STDOUT) + logger = ActiveSupport::Logger.new($stdout) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb index f4556db..6d56e43 100644 --- a/config/initializers/application_controller_renderer.rb +++ b/config/initializers/application_controller_renderer.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # ActiveSupport::Reloader.to_prepare do diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb index d0f0d3b..4b63f28 100644 --- a/config/initializers/backtrace_silencers.rb +++ b/config/initializers/backtrace_silencers.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index aa7435f..dc84742 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 6e1d16f..be6fedc 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: diff --git a/config/initializers/shrine.rb b/config/initializers/shrine.rb index 471be5b..5a58a59 100644 --- a/config/initializers/shrine.rb +++ b/config/initializers/shrine.rb @@ -5,10 +5,10 @@ require "shrine/storage/s3" s3_options = { - bucket: ENV['CARTAFACT_BUCKET'], - region: ENV['AWS_REGION'], - access_key_id: ENV['AWS_ACCESS_KEY_ID'], - secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'] + bucket: ENV.fetch('CARTAFACT_BUCKET', nil), + region: ENV.fetch('AWS_REGION', nil), + access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID', nil), + secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil) } paper_bucket = ENV['CARTAFACT_PAPER_BUCKET'] diff --git a/config/mongoid.yml b/config/mongoid.yml index 751496e..2f5d43d 100644 --- a/config/mongoid.yml +++ b/config/mongoid.yml @@ -9,7 +9,7 @@ development: # Provides the hosts the default session can connect to. Must be an array # of host:port pairs. (required) hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('DB_PORT', '27017') %>" options: # Change the default write concern. (default = { w: 1 }) # write: @@ -64,9 +64,9 @@ development: test: clients: default: - database: cartafact_test<%= ENV['TEST_ENV_NUMBER'] %> + database: cartafact_test<%= ENV.fetch('TEST_ENV_NUMBER', '') %> hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('DB_PORT', '27017') %>" options: # In the test environment we lower the retries and retry interval to # low amounts for fast failures. @@ -77,7 +77,7 @@ production: default: database: cartafact_production hosts: - - <%= ENV['DB_HOST'] %>:<%= ENV['DB_PORT'] %> + - "<%= ENV.fetch('DB_HOST', '') %>:<%= ENV.fetch('DB_PORT', '') %>" options: # In the test environment we lower the retries and retry interval to # low amounts for fast failures. diff --git a/config/puma.rb b/config/puma.rb index a47b289..36af80e 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -6,20 +6,42 @@ # the maximum value specified for Puma. Default is set to 5 threads for minimum # and maximum; this matches the default thread size of Active Record. # -max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } +max_threads_count = ENV.fetch("RAILS_MAX_THREADS", 5) min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } threads min_threads_count, max_threads_count +preload_app! + +# Specifies the `worker_timeout` threshold that Puma will use to wait before +# terminating a worker in development environments. +# +worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" + # Specifies the `port` that Puma will listen on to receive requests; default is 3000. # -port ENV.fetch("PORT") { 3000 } +port ENV.fetch("PORT", 3000) # Specifies the `environment` that Puma will run in. # -environment ENV.fetch("RAILS_ENV") { "development" } +environment ENV.fetch("RAILS_ENV", "development") + +on_worker_boot do + Mongoid::Clients.clients.each_value do |client| + client.close + client.reconnect + end +end + +before_fork do + Mongoid.disconnect_clients +end + +on_refork do + Mongoid.disconnect_clients +end # Specifies the `pidfile` that Puma will use. -pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } +pidfile ENV.fetch("PIDFILE", "tmp/pids/server.pid") # Specifies the number of `workers` to boot in clustered mode. # Workers are forked web server processes. If using threads and workers together diff --git a/db/seeds.rb b/db/seeds.rb index 8744e3c..ebd1889 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). # diff --git a/spec/controllers/api/v1/documents_controller_spec.rb b/spec/controllers/api/v1/documents_controller_spec.rb index a78f818..01dedcd 100644 --- a/spec/controllers/api/v1/documents_controller_spec.rb +++ b/spec/controllers/api/v1/documents_controller_spec.rb @@ -254,8 +254,8 @@ content_pairs = content_disposition.split("; ").select { |h| h.include?("=") }.map do |h| h.split("=") end - content_hash = Hash[content_pairs] - expect(content_hash["filename"]).to eq("\"" + document.file.original_filename + "\"") + content_hash = content_pairs.to_h + expect(content_hash["filename"]).to eq("\"#{document.file.original_filename}\"") end it "has the original file data" do diff --git a/spec/domain/operations/validate_resource_identity_signature_spec.rb b/spec/domain/operations/validate_resource_identity_signature_spec.rb index 6a1835d..0173843 100644 --- a/spec/domain/operations/validate_resource_identity_signature_spec.rb +++ b/spec/domain/operations/validate_resource_identity_signature_spec.rb @@ -3,6 +3,7 @@ require "rails_helper" module Cartafact + # Validate resource identity signature module Operations RSpec.describe ValidateResourceIdentitySignature do subject do diff --git a/spec/factories/documents_documents.rb b/spec/factories/documents_documents.rb deleted file mode 100644 index 34fc5b9..0000000 --- a/spec/factories/documents_documents.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -FactoryBot.define do - factory :documents_document, class: 'Documents::Document' do - end -end From 0dc83b1c2bfebb10886ea6438d3bc4c117462808 Mon Sep 17 00:00:00 2001 From: HArsHA Ellanki Date: Tue, 28 Oct 2025 23:37:38 -0400 Subject: [PATCH 03/22] add release workflows --- .github/release.yml | 23 ++++++++++ .github/workflows/label-checker.yml | 21 +++++++++ .github/workflows/publish-release.yml | 23 ++++++++++ RELEASING.md | 42 ++++++++++++++++++ docs_assets/NOTE.md | 1 + .../release_branching_deployable_app.png | Bin 0 -> 73297 bytes 6 files changed, 110 insertions(+) create mode 100644 .github/release.yml create mode 100644 .github/workflows/label-checker.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 RELEASING.md create mode 100644 docs_assets/NOTE.md create mode 100644 docs_assets/release_branching_deployable_app.png diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..35af776 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,23 @@ +# Release notes template (https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes) + +changelog: + categories: + - title: Breaking Changes 🚧 + labels: + # Semver-Major + - breaking change + - title: New Features ✨ + labels: + # Semver-Minor + - enhancement + - title: Bug Fixes 🐛 + labels: + # Semver-Patch + - bugfix + - title: Other Changes 📦 + labels: + - "*" + exclude: + labels: + - chore + - version bump diff --git a/.github/workflows/label-checker.yml b/.github/workflows/label-checker.yml new file mode 100644 index 0000000..27736c9 --- /dev/null +++ b/.github/workflows/label-checker.yml @@ -0,0 +1,21 @@ +name: Label Checker + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - labeled + - unlabeled + +jobs: + check_labels: + name: Check labels + runs-on: ubuntu-latest + steps: + - uses: docker://agilepathway/pull-request-label-checker:latest + with: + # At least one of the labels listed below must be present on the PR for the check to pass + any_of: breaking change,enhancement,bugfix,version bump,release,chore,documentation,dependencies + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..e32e495 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,23 @@ +name: Publish Release + +on: + push: + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +permissions: + contents: write + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Create release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ github.ref_name }}" \ + --repo="$GITHUB_REPOSITORY" \ + --title="${{ github.ref_name }}" \ + --generate-notes diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..3c74b3b --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,42 @@ +# Release + +This file describes the process for publishing a new version of the app as a GitHub release. + +Releases are managed through the [GitHub Releases](https://github.com/ideacrew/cartafact/releases) page. + +Release names follow the [Semantic Versioning](https://semver.org/) standard. + +Follow the steps below to package and release a new version. + +## Major/Minor release +### Publishing the Release +1. Checkout the main branch and pull the latest changes. +2. Create and checkout a new release branch, in the pattern of `1.0.x-release`. + - Note: `x` is literal, to aid reuse of same branch for minor bugfixes. +3. `git push` the new branch. +4. Create a new annotated tag with the version number, e.g., `git tag -as v1.0.0 -m "v1.0.0"`. + - IMPORTANT: make sure the tag abides by the format `vX.Y.Z` where `X`, `Y`, and `Z` are integers. It is important that the tag name has a different format than any branch name to avoid confusion with Bundler. +5. Push the tag to the remote repository, e.g., `git push origin refs/tags/v1.0.0`. + - Avoid `git push --tags`, to not accidentally push random assorted local tags. +6. GitHub Actions will automatically create a new release on the [GitHub Releases](https://github.com/ideacrew/cartafact/releases) page with release notes. Confirm that the release was successfully published there and that all intended commits are included in the release. + +## Patch/Bugfix release +### Prepare the release +1. Fix bug in the main branch, via normal PR process. + +For any release that has the bug: +2. Create a temp branch off any live release branch that has the bug. + - Using a branch, which is then PR’d, ensures traceability and inclusion of an item in the generated release notes. +3. Cherry-pick the fix commits to the temp branch. +4. `git push` the temp branch. +5. Issue a PR to merge to the release branch. + +### Publishing the Release +1. Once the pull request is approved and merged, checkout the release branch and pull the latest changes. +2. Create a new annotated tag with the version number, at the point of the release branch with the fix, e.g., `git tag -as v1.0.1 -m "v1.0.1"`. +3. Push the tag to the remote repository, e.g., `git push origin refs/tags/v1.0.1`. + - Again, better to avoid `git push --tags`. +4. Github Actions will create the release and pull in the fix PR's to the changelog. + +## Git Process diagram +![Git Process Diagram - App](docs_assets/release_branching_deployable_app.png) \ No newline at end of file diff --git a/docs_assets/NOTE.md b/docs_assets/NOTE.md new file mode 100644 index 0000000..7e44061 --- /dev/null +++ b/docs_assets/NOTE.md @@ -0,0 +1 @@ +This folder is intended for assets used in project internal documentation (RELEASING.md, README.md, etc.). diff --git a/docs_assets/release_branching_deployable_app.png b/docs_assets/release_branching_deployable_app.png new file mode 100644 index 0000000000000000000000000000000000000000..1acece8a2c6085f7673cec4a84a11c1b7df46a9d GIT binary patch literal 73297 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 Date: Fri, 16 Jan 2026 12:48:37 -0500 Subject: [PATCH 04/22] build and deploy workflow fixes --- .github/workflows/build_and_deploy.yml | 49 ++++++++++++-------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 76a7541..66fc800 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -4,12 +4,10 @@ on: workflow_dispatch: push: branches: - - "main" - - "rails_7_upgrade" + - "trunk" pull_request: branches: - - "main" - - "rails_7_upgrade" + - "trunk" concurrency: group: docker-${{ github.ref }} @@ -22,28 +20,26 @@ jobs: taggedImage: ${{ steps.prep.outputs.tagged_image }} tag: ${{ steps.prep.outputs.tag }} registryGhcr: ${{ steps.prep.outputs.registry_ghcr }} - shortSha: ${{ steps.prep.outputs.short_sha}} + shortSha: ${{ steps.prep.outputs.short_sha }} branchName: ${{ steps.prep.outputs.branch_name }} latestTag: ${{ steps.prep.outputs.latest_tag }} repositoryName: ${{ steps.prep.outputs.repository_name }} steps: - - name: Git branch name - id: git-branch-name - uses: EthanSK/git-branch-name-action@v1 - name: Prepare info id: prep run: | SHORT_SHA=$(echo $GITHUB_SHA | head -c7) REPO=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}') - TAG=${{ env.GIT_BRANCH_NAME }}-$(echo $GITHUB_SHA | head -c7) + BRANCH_NAME="${{ github.head_ref || github.ref_name }}" + TAG=${BRANCH_NAME}-$(echo $GITHUB_SHA | head -c7) IMAGE=dchbx/$REPO - echo ::set-output name=tagged_image::${IMAGE}:${TAG} - echo ::set-output name=tag::${TAG} - echo ::set-output name=registry_ghcr::ghcr.io - echo ::set-output name=short_sha::$SHORT_SHA - echo ::set-output name=branch_name::${{ env.GIT_BRANCH_NAME }} - echo ::set-output name=repository_name::$REPO - echo ::set-output name=latest_tag::${IMAGE}:latest + echo "tagged_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT + echo "tag=${TAG}" >> $GITHUB_OUTPUT + echo "registry_ghcr=ghcr.io" >> $GITHUB_OUTPUT + echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT + echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT + echo "repository_name=${REPO}" >> $GITHUB_OUTPUT + echo "latest_tag=${IMAGE}:latest" >> $GITHUB_OUTPUT # Uses buildx to build and push the image build-and-upload-image: @@ -65,23 +61,22 @@ jobs: - uses: actions/checkout@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - with: - install: true - version: v0.9.1 + id: buildx + uses: docker/setup-buildx-action@v3 - name: Cache Docker layers uses: actions/cache@v4 with: path: /tmp/.buildx-cache # Key is named differently to avoid collision - key: ${{ runner.os }}-multi-buildx-${{ github.sha }} + key: ${{ runner.os }}-multi-buildx-${{ github.ref }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-multi-buildx + ${{ runner.os }}-multi-buildx-${{ github.ref }}- + ${{ runner.os }}-multi-buildx- # Provide credentials for AWS - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 + uses: aws-actions/configure-aws-credentials@v5 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} @@ -89,21 +84,21 @@ jobs: # Must use docker login in order to specify public registry - name: Login to ECR - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ${{ secrets.AWS_ECR }} username: ${{ secrets.AWS_ACCESS_KEY_ID }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Login to GHCR - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ needs.prep.outputs.registryGhcr }} username: ${{ github.actor }} password: ${{ secrets.dchbx_deployments_token }} - name: Build Image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: context: . builder: ${{ steps.buildx.outputs.name }} @@ -133,7 +128,7 @@ jobs: - name: Scan Docker image if: github.event_name != 'pull_request' id: scan - uses: anchore/scan-action@main + uses: anchore/scan-action@v6 with: image: ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} acs-report-enable: true From 7c5ee0463e0864a7b7560f68c36f2cebebadb153 Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 13:19:51 -0500 Subject: [PATCH 05/22] workflow and config fixes --- .github/workflows/build_and_deploy.yml | 98 +++++++++++++++++--------- config/mongoid.yml | 6 +- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 66fc800..f0305a4 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -18,7 +18,6 @@ jobs: runs-on: ubuntu-latest outputs: taggedImage: ${{ steps.prep.outputs.tagged_image }} - tag: ${{ steps.prep.outputs.tag }} registryGhcr: ${{ steps.prep.outputs.registry_ghcr }} shortSha: ${{ steps.prep.outputs.short_sha }} branchName: ${{ steps.prep.outputs.branch_name }} @@ -34,7 +33,6 @@ jobs: TAG=${BRANCH_NAME}-$(echo $GITHUB_SHA | head -c7) IMAGE=dchbx/$REPO echo "tagged_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT - echo "tag=${TAG}" >> $GITHUB_OUTPUT echo "registry_ghcr=ghcr.io" >> $GITHUB_OUTPUT echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT @@ -63,6 +61,8 @@ jobs: - name: Set up Docker Buildx id: buildx uses: docker/setup-buildx-action@v3 + with: + install: true - name: Cache Docker layers uses: actions/cache@v4 @@ -95,7 +95,7 @@ jobs: with: registry: ${{ needs.prep.outputs.registryGhcr }} username: ${{ github.actor }} - password: ${{ secrets.dchbx_deployments_token }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Build Image uses: docker/build-push-action@v6 @@ -123,7 +123,7 @@ jobs: cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new build-args: | HOSTNAME=172.17.0.1 - GEM_OAUTH_TOKEN=${{ secrets.dchbx_deployments_token }} + pull: true - name: Scan Docker image if: github.event_name != 'pull_request' @@ -146,35 +146,63 @@ jobs: rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache -# enable slack when image is ready + new-image-notification: + if: github.repository_owner == 'ideacrew' && github.event_name != 'pull_request' + needs: [prep, build-and-upload-image] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Git Commit Data w/ Message + uses: jcputney/git-commit-data-action@1.0.2 + - name: Post to a Slack channel + id: ic-slack + uses: slackapi/slack-github-action@v1 + with: + channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "New image built from on `${{ needs.prep.outputs.branchName }}`" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.YELLR_BOT_TOKEN }} - # notify-slack: - # if: github.event_name != 'pull_request' - # needs: [prep, build-and-upload-image] - # runs-on: ubuntu-latest - # strategy: - # matrix: - # registry: ['ghcr.io','ecr.us-east-1.amazonaws.com'] - # steps: - # - name: Post to a Slack channel - # id: slack - # uses: slackapi/slack-github-action@v1.16.0 - # with: - # channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' - # payload: | - # { - # "blocks": [ - # { - # "type": "section", - # "text": { - # "type": "mrkdwn", - # "text": "*${{ format('{0} image*:\n`{1}/{2}`', matrix.registry, matrix.registry, needs.prep.outputs.taggedImage) }}" - # } - # }, - # { - # "type": "divider" - # } - # ] - # } - # env: - # SLACK_BOT_TOKEN: ${{ secrets.DC_DEPLOY_SLACK_BOT_TOKEN }} \ No newline at end of file + +# enable slack when image is ready + notify-slack: + if: github.repository_owner == 'ideacrew' && github.event_name != 'pull_request' + needs: [new-image-notification, prep, build-and-upload-image] + runs-on: ubuntu-latest + strategy: + matrix: + registry: ['public.ecr.aws', 'ghcr.io'] + steps: + - name: Post to a Slack channel + id: dc-slack + uses: slackapi/slack-github-action@v1 + with: + channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' + payload: | + { + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*${{ matrix.registry }} image*:\n`${{ format('{0}/{1}', matrix.registry, needs.prep.outputs.taggedImage) }}`" + } + }, + { + "type": "divider" + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.YELLR_BOT_TOKEN }} diff --git a/config/mongoid.yml b/config/mongoid.yml index 2f5d43d..d266c88 100644 --- a/config/mongoid.yml +++ b/config/mongoid.yml @@ -9,7 +9,7 @@ development: # Provides the hosts the default session can connect to. Must be an array # of host:port pairs. (required) hosts: - - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('DB_PORT', '27017') %>" + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '27017') %>" options: # Change the default write concern. (default = { w: 1 }) # write: @@ -66,7 +66,7 @@ test: default: database: cartafact_test<%= ENV.fetch('TEST_ENV_NUMBER', '') %> hosts: - - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('DB_PORT', '27017') %>" + - "<%= ENV.fetch('DB_HOST', 'localhost') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '27017') %>" options: # In the test environment we lower the retries and retry interval to # low amounts for fast failures. @@ -77,7 +77,7 @@ production: default: database: cartafact_production hosts: - - "<%= ENV.fetch('DB_HOST', '') %>:<%= ENV.fetch('DB_PORT', '') %>" + - "<%= ENV.fetch('DB_HOST', '') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '') %>" options: # In the test environment we lower the retries and retry interval to # low amounts for fast failures. From 68625df6da22fe1933607435e98cb1387cda2696 Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 13:31:55 -0500 Subject: [PATCH 06/22] login to public ecr --- .github/workflows/build_and_deploy.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index f0305a4..7593307 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -23,6 +23,7 @@ jobs: branchName: ${{ steps.prep.outputs.branch_name }} latestTag: ${{ steps.prep.outputs.latest_tag }} repositoryName: ${{ steps.prep.outputs.repository_name }} + registryEcr: ${{ steps.prep.outputs.registry_ecr }} steps: - name: Prepare info id: prep @@ -38,6 +39,7 @@ jobs: echo "branch_name=${BRANCH_NAME}" >> $GITHUB_OUTPUT echo "repository_name=${REPO}" >> $GITHUB_OUTPUT echo "latest_tag=${IMAGE}:latest" >> $GITHUB_OUTPUT + echo "registry_ecr=public.ecr.aws" >> $GITHUB_OUTPUT # Uses buildx to build and push the image build-and-upload-image: @@ -83,10 +85,10 @@ jobs: aws-region: us-east-1 # Must use docker login in order to specify public registry - - name: Login to ECR - uses: docker/login-action@v3 + - name: Login to Public ECR + uses: docker/login-action@v2 with: - registry: ${{ secrets.AWS_ECR }} + registry: ${{ needs.prep.outputs.registryEcr }} username: ${{ secrets.AWS_ACCESS_KEY_ID }} password: ${{ secrets.AWS_SECRET_ACCESS_KEY }} From faf53e842b19447162f98a5f565b82f35431064d Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 13:35:34 -0500 Subject: [PATCH 07/22] remove lingering dc ref --- .github/workflows/build_and_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 7593307..ac7eacb 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -32,7 +32,7 @@ jobs: REPO=$(echo '${{ github.repository }}' | awk -F '/' '{print $2}') BRANCH_NAME="${{ github.head_ref || github.ref_name }}" TAG=${BRANCH_NAME}-$(echo $GITHUB_SHA | head -c7) - IMAGE=dchbx/$REPO + IMAGE=ideacrew/$REPO echo "tagged_image=${IMAGE}:${TAG}" >> $GITHUB_OUTPUT echo "registry_ghcr=ghcr.io" >> $GITHUB_OUTPUT echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT @@ -187,7 +187,7 @@ jobs: registry: ['public.ecr.aws', 'ghcr.io'] steps: - name: Post to a Slack channel - id: dc-slack + id: ic-slack uses: slackapi/slack-github-action@v1 with: channel-id: 'docker-images-${{ needs.prep.outputs.repositoryName }}' From 4935ebeccfa6933d09457760ff5c475947778f30 Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 13:39:09 -0500 Subject: [PATCH 08/22] fix registry prefix --- .github/workflows/build_and_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index ac7eacb..b31e665 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -116,8 +116,8 @@ jobs: tags: | ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.taggedImage) }} ${{ format('{0}/{1}', needs.prep.outputs.registryGhcr, needs.prep.outputs.latestTag) }} - ${{ format('{0}/{1}', secrets.AWS_ECR, needs.prep.outputs.taggedImage) }} - ${{ format('{0}/{1}', secrets.AWS_ECR, needs.prep.outputs.latestTag) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryEcr, needs.prep.outputs.taggedImage) }} + ${{ format('{0}/{1}', needs.prep.outputs.registryEcr, needs.prep.outputs.latestTag) }} cache-from: type=local,src=/tmp/.buildx-cache # Note the mode=max here # More: https://github.com/moby/buildkit#--export-cache-options From 4ed8651fe3bb46cbb34d68889fe7a6daa7918afa Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 13:45:30 -0500 Subject: [PATCH 09/22] fix casing in dockerfile --- .docker/production/Dockerfile.gha | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.docker/production/Dockerfile.gha b/.docker/production/Dockerfile.gha index 3273da0..2c77731 100644 --- a/.docker/production/Dockerfile.gha +++ b/.docker/production/Dockerfile.gha @@ -4,7 +4,7 @@ # Taken from .ruby-version ARG RUBY_VERSION=3.2.5 -FROM ruby:$RUBY_VERSION-slim-bookworm as base +FROM ruby:$RUBY_VERSION-slim-bookworm AS base LABEL author="DCHBX" ARG DEBIAN_FRONTEND=noninteractive @@ -82,7 +82,7 @@ WORKDIR $HOME ### Builder. Adds node and Yarn. Not necessary in production. ### ############################################################################### -FROM base as builder +FROM base AS builder ARG DEBIAN_FRONTEND=noninteractive @@ -106,7 +106,7 @@ RUN apt-get update -qq \ ### Node and Bundle for production ### ######################################################## -FROM builder as prod_gems_and_assets +FROM builder AS prod_gems_and_assets ENV RAILS_ENV=production @@ -127,7 +127,7 @@ RUN rm -rf $GEM_HOME/bundle/ruby/*/cache ### Deployable image ### ################################################################ -FROM base as deploy +FROM base AS deploy # Copy prebuilt gems COPY --chown=nonroot:nonroot --from=prod_gems_and_assets $BUNDLE_PATH $BUNDLE_PATH From fbbe6a6dabeffa1f2395b83c0c45ead3634ee644 Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 14:20:57 -0500 Subject: [PATCH 10/22] update mongoid prod config --- config/mongoid.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/config/mongoid.yml b/config/mongoid.yml index d266c88..c76178b 100644 --- a/config/mongoid.yml +++ b/config/mongoid.yml @@ -77,8 +77,19 @@ production: default: database: cartafact_production hosts: - - "<%= ENV.fetch('DB_HOST', '') %>:<%= ENV.fetch('CARTAFACT_DB_PORT', '') %>" + - <%= ENV['CARTAFACT_DB_HOST'] %>:<%= ENV['CARTAFACT_DB_PORT'] %> options: + <% if ENV['CARTAFACT_DB_AUTH'] == true %> + replica_set: <%= ENV['CARTAFACT_DB_REPLICA_SET_NAME'] %> + + user: <%= ENV['CARTAFACT_DB_USERNAME'] %> + password: <%= ENV['CARTAFACT_DB_PASSWORD'] %> + auth_source: admin + + write: + w: 1 + j: true + <% end %> # In the test environment we lower the retries and retry interval to # low amounts for fast failures. max_retries: 1 From 963bf087605e6828c213175acc169344da50d8bc Mon Sep 17 00:00:00 2001 From: Michael Karamanov Date: Fri, 16 Jan 2026 14:28:46 -0500 Subject: [PATCH 11/22] delete build script --- .docker/build_and_push.sh | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .docker/build_and_push.sh diff --git a/.docker/build_and_push.sh b/.docker/build_and_push.sh deleted file mode 100644 index c370f1b..0000000 --- a/.docker/build_and_push.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Build the Docker image with specified arguments -docker build --build-arg BUNDLER_VERSION='2.4.19' -f .docker/production/Dockerfile --target app -t ghcr.io/ideacrew/cartafact:cartafact-release-tag . - -# Push the Docker image to the GitHub Container Registry -docker push ghcr.io/ideacrew/cartafact:cartafact-release-tag - -docker push -echo "Docker image build and push completed." \ No newline at end of file From 6e864a9135143c24a79949fc99912c5412e5559a Mon Sep 17 00:00:00 2001 From: Sai Kumar Kotagiri Date: Tue, 20 Jan 2026 12:18:40 -0500 Subject: [PATCH 12/22] fixes rubocop issues --- config/initializers/shrine.rb | 2 +- .../cartafact/entities/operations/documents/copy_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/initializers/shrine.rb b/config/initializers/shrine.rb index 5a58a59..cc2a4eb 100644 --- a/config/initializers/shrine.rb +++ b/config/initializers/shrine.rb @@ -11,7 +11,7 @@ secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil) } -paper_bucket = ENV['CARTAFACT_PAPER_BUCKET'] +paper_bucket = ENV.fetch('CARTAFACT_PAPER_BUCKET', nil) paper_bucket_s3_options = s3_options.merge(bucket: paper_bucket) storage_options = if Rails.env.production? { diff --git a/spec/domain/cartafact/entities/operations/documents/copy_spec.rb b/spec/domain/cartafact/entities/operations/documents/copy_spec.rb index 2f73e71..45e7df4 100644 --- a/spec/domain/cartafact/entities/operations/documents/copy_spec.rb +++ b/spec/domain/cartafact/entities/operations/documents/copy_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Cartafact::Entities::Operations::Documents::Copy do let(:valid_id) { '695bd7d1d88b230001580217' } let(:filename) { '1629675_yourplanenrollment_ivlenr_ivl_20260105152504.pdf' } - let(:file_metadata) { { 'filename' => filename, 'size' => 273802, 'mime_type' => 'application/pdf' } } + let(:file_metadata) { { 'filename' => filename, 'size' => 273_802, 'mime_type' => 'application/pdf' } } let(:uploaded_file) { double('Shrine::UploadedFile', metadata: file_metadata) } let(:document) { double('Document', file: uploaded_file) } let(:copy_instance) { described_class.new } @@ -16,7 +16,7 @@ id: 'RackMultipart20260107-1-u26s7n.pdf', metadata: { 'filename' => 'RackMultipart20260107-1-u26s7n.pdf', - 'size' => 212855, + 'size' => 212_855, 'mime_type' => 'application/pdf' } ) @@ -35,7 +35,7 @@ expect(mock_uploaded_file.storage).to eq(:paper_bucket_store) expect(mock_uploaded_file.id).to eq('RackMultipart20260107-1-u26s7n.pdf') expect(mock_uploaded_file.metadata['filename']).to eq('RackMultipart20260107-1-u26s7n.pdf') - expect(mock_uploaded_file.metadata['size']).to eq(212855) + expect(mock_uploaded_file.metadata['size']).to eq(212_855) expect(mock_uploaded_file.metadata['mime_type']).to eq('application/pdf') end From 94651d21ea860778f482a639ae173dd9717d6307 Mon Sep 17 00:00:00 2001 From: saikumar9 Date: Tue, 20 Jan 2026 12:57:08 -0500 Subject: [PATCH 13/22] test image building for this branch --- .github/workflows/build_and_deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index b31e665..9967036 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -5,9 +5,11 @@ on: push: branches: - "trunk" + - 'wip_cartafact_updates' pull_request: branches: - "trunk" + - 'wip_cartafact_updates' concurrency: group: docker-${{ github.ref }} From b0f6139b175bb85c9764f831d75b0ec65b62364f Mon Sep 17 00:00:00 2001 From: Krishna Date: Wed, 21 Jan 2026 14:32:45 -0500 Subject: [PATCH 14/22] fix docker-entrypoint permission denied error --- bin/docker-entrypoint | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 bin/docker-entrypoint diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint old mode 100644 new mode 100755 From 063fd01207df2d6067cb5ca7a8322534df022953 Mon Sep 17 00:00:00 2001 From: saikumar9 Date: Fri, 30 Jan 2026 10:44:20 -0500 Subject: [PATCH 15/22] changes branch name to build images on commit and push --- .github/workflows/build_and_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 9967036..1841ade 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -5,11 +5,11 @@ on: push: branches: - "trunk" - - 'wip_cartafact_updates' + - 'upgrade_ruby_and_rails' pull_request: branches: - "trunk" - - 'wip_cartafact_updates' + - 'upgrade_ruby_and_rails' concurrency: group: docker-${{ github.ref }} From b2cfc0e2de709204759d0c670af1074d9db56608 Mon Sep 17 00:00:00 2001 From: geetha Date: Fri, 30 Jan 2026 08:18:41 -0800 Subject: [PATCH 16/22] fix the parameter encoding issue --- app/controllers/api/v1/documents_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index 48b0943..0de0ca2 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -8,7 +8,7 @@ class DocumentsController < ApplicationController # Rails 7+ enforces UTF-8 parameter validation which rejects binary PDF content # Skip encoding validation for the create action to allow binary file uploads - skip_parameter_encoding :create if respond_to?(:skip_parameter_encoding) + skip_parameter_encoding :create # query for documents. Returns an array. def index From 0dadeb45f17a864658bb382383b528b61ebf0bf5 Mon Sep 17 00:00:00 2001 From: saikumar9 Date: Fri, 30 Jan 2026 11:54:27 -0500 Subject: [PATCH 17/22] handle encoding issue differently skip_parameter_encoding as it is only available with ActionController::Base and not ActionController::API --- .../api/v1/documents_controller.rb | 14 ++++- .../api/v1/documents_controller_spec.rb | 53 +++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index 0de0ca2..a8c6203 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -7,8 +7,8 @@ class DocumentsController < ApplicationController FILE_STREAM_SIZE = 32_768 # Rails 7+ enforces UTF-8 parameter validation which rejects binary PDF content - # Skip encoding validation for the create action to allow binary file uploads - skip_parameter_encoding :create + # Override parameter encoding for create action to allow binary file uploads + around_action :handle_binary_params, only: [:create] # query for documents. Returns an array. def index @@ -83,6 +83,16 @@ def copy private + # Handle binary parameter encoding for file uploads + # Rails 7+ validates parameter encoding by default which can reject binary data + def handle_binary_params + original_encoding = Encoding.default_internal + Encoding.default_internal = nil + yield + ensure + Encoding.default_internal = original_encoding + end + def stream_download(document) disposition = ActionDispatch::Http::ContentDisposition.format( disposition: "attachment", diff --git a/spec/controllers/api/v1/documents_controller_spec.rb b/spec/controllers/api/v1/documents_controller_spec.rb index 01dedcd..5b6b91c 100644 --- a/spec/controllers/api/v1/documents_controller_spec.rb +++ b/spec/controllers/api/v1/documents_controller_spec.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Set ENV variables before Rails loads to allow mongoid.yml to parse correctly +ENV['CARTAFACT_DB_HOST'] ||= 'localhost' +ENV['CARTAFACT_DB_PORT'] ||= '27017' + require 'rails_helper' RSpec.describe Api::V1::DocumentsController, type: :controller do @@ -65,6 +69,55 @@ document = Document.find(BSON::ObjectId.from_string(document_id)) expect(document.file.read).to eq "DATA GOES HERE" end + + it "should handle binary parameters by temporarily disabling encoding validation" do + # Verify that Encoding.default_internal is set to nil during the request + allow(controller).to receive(:create).and_wrap_original do |method, *args| + expect(Encoding.default_internal).to be_nil + method.call(*args) + end + + post :create, params: { + document: JSON.dump( + { + subjects: [{ id: 'abc', type: 'consumer' }], + document_type: 'vlp_doc', + format: "application/pdf", + creator: 'dc', + publisher: 'dc', + type: 'text', + source: 'enroll_system', + language: 'en', + date_submitted: Date.today + } + ), + content: Rack::Test::UploadedFile.new(tempfile, "application/pdf") + } + end + + it "should restore the original encoding after the request completes" do + original_encoding = Encoding.default_internal + + post :create, params: { + document: JSON.dump( + { + subjects: [{ id: 'abc', type: 'consumer' }], + document_type: 'vlp_doc', + format: "application/pdf", + creator: 'dc', + publisher: 'dc', + type: 'text', + source: 'enroll_system', + language: 'en', + date_submitted: Date.today + } + ), + content: Rack::Test::UploadedFile.new(tempfile, "application/pdf") + } + + # After the request, encoding should be restored + expect(Encoding.default_internal).to eq(original_encoding) + end end context "with invalid params" do From e534582f246194435318abcd81ec8b0665c3a02c Mon Sep 17 00:00:00 2001 From: geetha Date: Fri, 30 Jan 2026 11:54:31 -0800 Subject: [PATCH 18/22] update the fix to check rails version --- app/controllers/api/v1/documents_controller.rb | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index a8c6203..2d8e2af 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -6,9 +6,11 @@ module V1 class DocumentsController < ApplicationController FILE_STREAM_SIZE = 32_768 - # Rails 7+ enforces UTF-8 parameter validation which rejects binary PDF content - # Override parameter encoding for create action to allow binary file uploads - around_action :handle_binary_params, only: [:create] + # Rails 7.1+ enforces strict UTF-8 parameter validation by default, which rejects binary PDF content + # (e.g., %PDF-1.3 with non-UTF-8 bytes) before the controller action runs, raising ActionController::BadRequest. + # skip_parameter_encoding bypasses this validation for the create action to allow binary file uploads. + # Version check ensures compatibility: the method only exists in Rails 7.1+, not in earlier versions. + skip_parameter_encoding :create if Rails.gem_version >= Gem::Version.new('7.1.0') # query for documents. Returns an array. def index @@ -83,16 +85,6 @@ def copy private - # Handle binary parameter encoding for file uploads - # Rails 7+ validates parameter encoding by default which can reject binary data - def handle_binary_params - original_encoding = Encoding.default_internal - Encoding.default_internal = nil - yield - ensure - Encoding.default_internal = original_encoding - end - def stream_download(document) disposition = ActionDispatch::Http::ContentDisposition.format( disposition: "attachment", From 1068c4db6a58a951f3efb9e51222ac8d883f62b2 Mon Sep 17 00:00:00 2001 From: geetha Date: Fri, 30 Jan 2026 13:12:38 -0800 Subject: [PATCH 19/22] make the build pass --- app/controllers/api/v1/documents_controller.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/documents_controller.rb b/app/controllers/api/v1/documents_controller.rb index 2d8e2af..426fc88 100644 --- a/app/controllers/api/v1/documents_controller.rb +++ b/app/controllers/api/v1/documents_controller.rb @@ -10,7 +10,13 @@ class DocumentsController < ApplicationController # (e.g., %PDF-1.3 with non-UTF-8 bytes) before the controller action runs, raising ActionController::BadRequest. # skip_parameter_encoding bypasses this validation for the create action to allow binary file uploads. # Version check ensures compatibility: the method only exists in Rails 7.1+, not in earlier versions. - skip_parameter_encoding :create if Rails.gem_version >= Gem::Version.new('7.1.0') + if Rails.gem_version >= Gem::Version.new('7.1.0') + begin + skip_parameter_encoding :create + rescue NoMethodError + # Method not available in this Rails version + end + end # query for documents. Returns an array. def index From 6ab90d484888adf789c8ffa0e418d83d8854c5e4 Mon Sep 17 00:00:00 2001 From: geetha Date: Fri, 30 Jan 2026 13:17:35 -0800 Subject: [PATCH 20/22] update the rspec to pass --- .../api/v1/documents_controller_spec.rb | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/spec/controllers/api/v1/documents_controller_spec.rb b/spec/controllers/api/v1/documents_controller_spec.rb index 5b6b91c..ca28447 100644 --- a/spec/controllers/api/v1/documents_controller_spec.rb +++ b/spec/controllers/api/v1/documents_controller_spec.rb @@ -69,55 +69,6 @@ document = Document.find(BSON::ObjectId.from_string(document_id)) expect(document.file.read).to eq "DATA GOES HERE" end - - it "should handle binary parameters by temporarily disabling encoding validation" do - # Verify that Encoding.default_internal is set to nil during the request - allow(controller).to receive(:create).and_wrap_original do |method, *args| - expect(Encoding.default_internal).to be_nil - method.call(*args) - end - - post :create, params: { - document: JSON.dump( - { - subjects: [{ id: 'abc', type: 'consumer' }], - document_type: 'vlp_doc', - format: "application/pdf", - creator: 'dc', - publisher: 'dc', - type: 'text', - source: 'enroll_system', - language: 'en', - date_submitted: Date.today - } - ), - content: Rack::Test::UploadedFile.new(tempfile, "application/pdf") - } - end - - it "should restore the original encoding after the request completes" do - original_encoding = Encoding.default_internal - - post :create, params: { - document: JSON.dump( - { - subjects: [{ id: 'abc', type: 'consumer' }], - document_type: 'vlp_doc', - format: "application/pdf", - creator: 'dc', - publisher: 'dc', - type: 'text', - source: 'enroll_system', - language: 'en', - date_submitted: Date.today - } - ), - content: Rack::Test::UploadedFile.new(tempfile, "application/pdf") - } - - # After the request, encoding should be restored - expect(Encoding.default_internal).to eq(original_encoding) - end end context "with invalid params" do From 348ffb3fece17f3133f8bc0297ddbf944c27b89d Mon Sep 17 00:00:00 2001 From: Sai Praveen Gudimetla Date: Mon, 2 Feb 2026 16:40:14 -0500 Subject: [PATCH 21/22] inherit from action controller base instead of api --- app/controllers/application_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 24170e6..7daff4e 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # Application controller -class ApplicationController < ActionController::API +class ApplicationController < ActionController::Base # before_action :authenticate private From 4307606120a4635483461b84c0ce16a42c775e2b Mon Sep 17 00:00:00 2001 From: Sai Praveen Gudimetla Date: Mon, 2 Feb 2026 20:20:22 -0500 Subject: [PATCH 22/22] add branch to workflow to build image --- .github/workflows/build_and_deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index 1841ade..6bdfa97 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -6,10 +6,12 @@ on: branches: - "trunk" - 'upgrade_ruby_and_rails' + - "fix_skip_parameter_encoding_issue" pull_request: branches: - "trunk" - 'upgrade_ruby_and_rails' + - "fix_skip_parameter_encoding_issue" concurrency: group: docker-${{ github.ref }}