Skip to content

Error publishing pact via old api, post Rack 3 upgrade #841

@YOU54F

Description

@YOU54F

Background

There are two apis for publishing contracts

OLD: http://{broker}/pacts/provider/{provider}/consumer/{consumer}/version/{version]

and

NEW: http://{broker}/contracts/publish

The Pact Broker Client supports a feature flag

PACT_BROKER_FEATURES=publish_pacts_using_old_api

which allows the end user to revert to using the former endpoint

Problem

Since the Rack 3 upgrade, the old api for publishing pacts returns an error.

This came to light when I was upgrading pact_broker-client & pact_broker to use the rust core

  • ✅ Verifications of the endpoint with the Ruby core pass
  • ❌ Verifications of the endpoint with the Rust core fail
  • ❌ Publications via API or using the pact_broker-client fail when using the old endpoint

Pact-Ruby seems to be masking the issue, so it hasn't come to light

Stack Trace

Failed to publish Foo/Bar pact due to error: PactBroker::Client::Error - {"error":{"message":"unexpected end of input at line 1 column 1",
"reference":"JPaSjsCmMm",
"backtrace":["/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/json-2.12.2/lib/json/common.rb:338:in `parse'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/json-2.12.2/lib/json/common.rb:338:in `parse'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/pacts/parse.rb:8:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/pacts/content.rb:20:in `from_json'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/pacts/generate_sha.rb:24:in `extract_verifiable_content_for_sha'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/pacts/generate_sha.rb:16:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/pacts/service.rb:26:in `generate_sha'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/api/resources/pact.rb:131:in `pact_version_sha'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/api/resources/pact.rb:117:in `disallowed_modification?'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/api/resources/pact.rb:57:in `is_conflict?'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/flow.rb:500:in `p3'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/fsm.rb:31:in `block (2 levels) in run'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/fsm.rb:51:in `handle_exceptions'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/fsm.rb:31:in `block in run'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/fsm.rb:29:in `loop'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/decision/fsm.rb:29:in `run'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/dispatcher.rb:46:in `block in dispatch'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/as-notifications-1.0.2/lib/as/notifications.rb:161:in `instrument'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/events.rb:75:in `instrument'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webmachine-2.0.1/lib/webmachine/dispatcher.rb:45:in `dispatch'",
"/home/runner/work/pact_broker/pact_broker/lib/webmachine/adapters/rack3_adapter.rb:44:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:46:in `block in call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:37:in `each'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:37:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/database_transaction.rb:46:in `block in call_with_transaction'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sequel-5.96.0/lib/sequel/database/transactions.rb:264:in `_transaction'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sequel-5.96.0/lib/sequel/database/transactions.rb:239:in `block in transaction'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sequel-5.96.0/lib/sequel/connection_pool/timed_queue.rb:90:in `hold'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sequel-5.96.0/lib/sequel/database/connecting.rb:283:in `synchronize'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sequel-5.96.0/lib/sequel/database/transactions.rb:197:in `transaction'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/database_transaction.rb:45:in `call_with_transaction'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/database_transaction.rb:24:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/no_auth.rb:9:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/builder.rb:283:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:46:in `block in call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:37:in `each'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/cascade.rb:37:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/urlmap.rb:76:in `block in call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/urlmap.rb:60:in `each'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/urlmap.rb:60:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/static.rb:162:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/hal_browser/redirect.rb:20:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/set_base_url.rb:15:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/convert_file_extension_to_accept_header.rb:28:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/add_cache_header.rb:9:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/static.rb:162:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/static.rb:162:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/add_vary_header.rb:34:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/add_pact_broker_version_header.rb:14:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/reset_thread_data.rb:13:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/invalid_uri_protection.rb:26:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/application_context.rb:12:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/use_when.rb:31:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-protection-4.1.1/lib/rack/protection/content_security_policy.rb:73:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/rack/pact_broker/use_when.rb:29:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-protection-4.1.1/lib/rack/protection/xss_header.rb:20:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-protection-4.1.1/lib/rack/protection/json_csrf.rb:28:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-protection-4.1.1/lib/rack/protection/base.rb:53:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-protection-4.1.1/lib/rack/protection/frame_options.rb:33:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/builder.rb:283:in `call'",
"/home/runner/work/pact_broker/pact_broker/lib/pact_broker/app.rb:90:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/tempfile_reaper.rb:20:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/lint.rb:93:in `response'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/lint.rb:16:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/show_exceptions.rb:31:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/common_logger.rb:43:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/sinatra-4.1.1/lib/sinatra/base.rb:269:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rack-3.2.1/lib/rack/content_length.rb:20:in `call'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/rackup-2.2.1/lib/rackup/handler/webrick.rb:111:in `service'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webrick-1.9.1/lib/webrick/httpserver.rb:140:in `service'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webrick-1.9.1/lib/webrick/httpserver.rb:96:in `run'",
"/opt/hostedtoolcache/Ruby/3.2.9/x64/lib/ruby/gems/3.2.0/gems/webrick-1.9.1/lib/webrick/server.rb:310:in `block in start_thread'"]}}

Testing

I've created a simple script to

  • checkout a specific version of tthe pact_broker codebase
  • bundle install
  • install pact_broker-client
  • download a pact file
  • start the pact broker and wait for it to start
  • publish the pact

I have tested against

  • ❌ Locally via pact_broker repo (rackup)
  • ✅ Docker - Latest Pact Broker Image
  • ✅ Docker - Building latest pact broker image locally
  • ❌ Docker - Running Pact Broker locally, in ./pact_broker to use Gemfile.lock
  • ✅ PactFlow

I would have expected the Docker images to have all failed, but oddly running it on my mac with the same bundle fails, but passes with a freshly built local image 🤯

Edit: Mystery solved as to why it doesn't work across projects.

Running with

bundle exec puma
bundle exec rackup -s puma
bundle exec rackup -s webrick

Test Script

name: publish_pacts api test

on:
  push:
    branches:
      - issue/publish_pacts_using_old_api
  workflow_dispatch:
    inputs:
      git_commit:
        description: 'Git commit hash to checkout'
        required: false
        type: string

jobs:
  publish_pact_old_api:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest ]

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.git_commit || github.sha }}
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
      - run: gem install pact_broker-client
      - run: bundle install
      - run: bundle exec rackup &
      - run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for | sh -s -- localhost:9292 -t 120 -- echo broker is up
      - run: |
          curl -L https://raw.githubusercontent.com/pact-foundation/pact_broker/refs/heads/master/script/foo-bar.json -o foo-bar.json
          pact-broker publish foo-bar.json --consumer-app-version 1.2.26
        name: Publish pacts using old API
        shell: bash
        env:
          PACT_BROKER_FEATURES: publish_pacts_using_old_api
          PACT_BROKER_BASE_URL: http://localhost:9292

  publish_pact_new_api:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ ubuntu-latest ]

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.event.inputs.git_commit || github.sha }}
      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.2'
      - run: gem install pact_broker-client
      - run: bundle install
      - run: bundle exec rackup &
      - run: wget -qO- https://raw.githubusercontent.com/eficode/wait-for/v2.2.3/wait-for | sh -s -- localhost:9292 -t 120 -- echo broker is up
      - run: |
          curl -L https://raw.githubusercontent.com/pact-foundation/pact_broker/refs/heads/master/script/foo-bar.json -o foo-bar.json
          pact-broker publish foo-bar.json --consumer-app-version 1.2.26
        name: Publish pacts using new API
        shell: bash
        env:
          PACT_BROKER_BASE_URL: http://localhost:9292

Root Cause

Looks to have been introduced post the upgrade to Rack 3 - PR

  • ✅ Last commit on rack 2 - b3da850
  • ❌ First commit for rack 3 - ed1e8b2
  • ❌ First commit for rack 3 that shows issue - 3ac959d

It should be noted the first commit for Rack 3 upgrade failed due to headers not being downcased, and therefore the third commit shown, highlights the issue we can see today.

Rack 3 Upgrade guide for reference - https://github.com/rack/rack/blob/main/UPGRADE-GUIDE.md

bundle exec puma
bundle exec rackup -s puma
bundle exec rackup -s webrick

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions