diff --git a/.github/workflows/container.yaml b/.github/workflows/container.yaml new file mode 100644 index 0000000..22d9669 --- /dev/null +++ b/.github/workflows/container.yaml @@ -0,0 +1,61 @@ +--- +name: Publish OCI Container +on: # yamllint disable-line rule:truthy + workflow_call: + inputs: + tag: + description: 'The tag to use for the container image' + required: false + type: string + workflow_dispatch: + inputs: + tag: + description: 'The tag to use for the container image' + required: false + type: string +jobs: + publish_github: + name: Publish the container image to GitHub Container Registry + runs-on: ubuntu-latest + strategy: + # Go hard on the builders + max-parallel: 5 + matrix: + alpine-version: ['3.20'] + ruby-version: ['3.4.1'] + steps: + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Publish to ghcr.io + env: + ALPINE_VERSION: ${{ matrix.alpine-version }} + REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUBY_VERSION: ${{ matrix.ruby-version }} + TAG: ${{ github.event.inputs.tag || '' }} + TRACE: ${{ secrets.ACTIONS_STEP_DEBUG || 'false' }} + # yamllint disable rule:line-length + run: | + if [ -z "$TAG" ] + then + [ "$TRACE" = true ] && printf 'No tag provided, getting tag from .version.txt\n' >&2 + if [ -f ".version.txt" ] + then + version=$(<.version.txt) + else + [ "$TRACE" = true ] && printf 'No .version.txt found, getting version from git describe --tags --abbrev=0\n' >&2 + version=$(git describe --tags --abbrev=0) + fi + else + [ "$TRACE" = true ] && printf 'Using provided tag %s\n' "$TAG" >&2 + version=$TAG + fi + [ "$TRACE" = 'true' ] && printf 'Calling ./ci/build_image.sh -vvp "%s"\n' "$version" >&2 + IMAGE_NAME=$(basename "$GITHUB_REPOSITORY") \ + GITHUB_TOKEN=$REGISTRY_TOKEN \ + ALPINE_VERSION=$ALPINE_VERSION \ + RUBY_VERSION=$RUBY_VERSION \ + ./ci/build_image.sh -vvp "$version" + # yamllint enable rule:line-length + shell: bash diff --git a/.github/workflows/conventional_commits.yaml b/.github/workflows/conventional_commits.yaml new file mode 100644 index 0000000..25469a2 --- /dev/null +++ b/.github/workflows/conventional_commits.yaml @@ -0,0 +1,63 @@ +--- +name: Conventional Commits And PR titles + +on: # yamllint disable-line rule:truthy + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + comventional_commits: + name: Validate Commit Subjects + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: webiny/action-conventional-commits@v1.3.0 + name: Validate Commit Subjects + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # All of the Angular commit types are allowed by default. + # Added types to this: + # * eyes - For observability related changes + # * sec - For security related changes + allowed-commit-types: "build,chore,ci,docs,eyes,feat,fix,perf,refactor,revert,sec,style,test" # yamllint disable-line rule:line-length + conventional_pr_title: + name: Validate PR title + runs-on: ubuntu-latest + steps: + - + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + id: validate-pr-title + name: Validate PR title + uses: amannn/action-semantic-pull-request@v5 + with: + # All of the Angular commit types are allowed by default. + # Added types to this: + # * eyes - For observability related changes + # * sec - For security related changes + types: | + build + chore + ci + docs + eyes + feat + fix + perf + refactor + revert + sec + style + test + # We don't enforce scopes + # scopes: + # - frontend + # - backend + # - ci + # We don't disallow any scopes + # disallowScopes: | + # release + wip: true diff --git a/.github/workflows/gem.yaml b/.github/workflows/gem.yaml new file mode 100644 index 0000000..55c0371 --- /dev/null +++ b/.github/workflows/gem.yaml @@ -0,0 +1,36 @@ +--- +name: Publish Ruby Gem +on: # yamllint disable-line rule:truthy + workflow_call: + workflow_dispatch: +jobs: + publish_gem: + name: Publish the gem to registries + runs-on: ubuntu-latest + strategy: + matrix: + registry: + - key: rubygems + secret: RUBYGEMS_TOKEN + - key: github + secret: GEM_TOKEN_GITHUB + steps: + - + name: Checkout repository + uses: actions/checkout@v4 + - + name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.4.1 + bundler-cache: false + - + name: Publish to ${{ matrix.registry }} + env: + TRACE: ${{ secrets.ACTIONS_STEP_DEBUG || 'false' }} + GEM_TOKEN: ${{ secrets[matrix.registry.secret] }} + REGISTRY: ${{ matrix.registry.key }} + run: | + bundle install + TRACE="$TRACE" GEM_TOKEN="$GEM_TOKEN" ./ci/publish-gem.sh "$REGISTRY" + shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yaml similarity index 94% rename from .github/workflows/main.yml rename to .github/workflows/main.yaml index c2d7d6b..1d328ec 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yaml @@ -1,11 +1,9 @@ name: Ruby on: - push: - branches: - - main + workflow_call: - pull_request: + workflow_dispatch: jobs: build: diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..ac0d878 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,17 @@ +--- +name: Publish + +on: # yamllint disable-line rule:truthy + workflow_dispatch: + workflow_call: + +jobs: + gem: + name: Build and publish gem + uses: ./.github/workflows/gem.yaml + secrets: inherit + + containers: + name: Build and publish OCI container images + uses: ./.github/workflows/container.yaml + secrets: inherit diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..6411443 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,51 @@ +--- +name: Release + +on: # yamllint disable-line rule:truthy + push: + branches: + - main + workflow_dispatch: + workflow_call: + +jobs: + validate: + name: Validations + uses: ./.github/workflows/validations.yaml + + release: + needs: [validate] + name: Create a release + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + steps: + - + uses: actions/checkout@v4 + id: git-checkout + with: + fetch-tags: true + - + uses: googleapis/release-please-action@v4 + id: release + with: + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} + - + id: debug + env: + RELEASE_CREATED: ${{ steps.release.outputs.release_created }} + TRACE: ${{ secrets.ACTIONS_STEP_DEBUG || 'false' }} + run: | + if [ "$TRACE" != 'false' ] + then + printf 'Release created: %s\n' "$RELEASE_CREATED" + fi + + publish: + if: needs.release.outputs.release_created + needs: release + name: Build and publish artifacts + uses: ./.github/workflows/publish.yaml + secrets: inherit diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml new file mode 100644 index 0000000..bd9ea2c --- /dev/null +++ b/.github/workflows/validations.yaml @@ -0,0 +1,12 @@ +--- +name: Validations + +on: # yamllint disable-line rule:truthy + workflow_dispatch: + workflow_call: + pull_request: + +jobs: + validate_ruby: + name: Ruby Tests + uses: ./.github/workflows/main.yaml diff --git a/.gitignore b/.gitignore index 9b27b13..460c863 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /tmp/ Gemfile.lock *.swp +*.gem diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 0000000..735db4a --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,32 @@ +{ + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "simple", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "draft": false, + "prerelease": false, + "version-file": ".version.txt", + "extra-files": [ + { + "type": "generic", + "path": "lib/sequel/pgt_outbox/version.rb" + }, + { + "type": "generic", + "path": "oci/Gemfile" + } + ], + "exclude-paths": [ + ".release-please-manifest.json", + ".version.txt", + "lib/sequel/pgt_outbox/version.rb", + ".rubocop.yml", + ".overcommit.yml", + "coverage/coverage.json" + ] + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..2be9c43 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.2.0" +} diff --git a/.version.txt b/.version.txt new file mode 100644 index 0000000..0ea3a94 --- /dev/null +++ b/.version.txt @@ -0,0 +1 @@ +0.2.0 diff --git a/README.md b/README.md deleted file mode 100644 index bf526d7..0000000 --- a/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Sequel::PgtOutbox - -TODO: Delete this and the text below, and describe your gem - -Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/sequel/pgt_outbox`. To experiment with that code, run `bin/console` for an interactive prompt. - -## Installation - -TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org. - -Install the gem and add to the application's Gemfile by executing: - -```bash -bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG -``` - -If bundler is not being used to manage dependencies, install the gem by executing: - -```bash -gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG -``` - -## Usage - -TODO: Write usage instructions here - -## Development - -After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. - -To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). - -## Contributing - -Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/sequel-pgt_outbox. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/sequel-pgt_outbox/blob/main/CODE_OF_CONDUCT.md). - -## License - -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). - -## Code of Conduct - -Everyone interacting in the Sequel::PgtOutbox project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/sequel-pgt_outbox/blob/main/CODE_OF_CONDUCT.md). diff --git a/Rakefile b/Rakefile index ddd5e95..a87922a 100644 --- a/Rakefile +++ b/Rakefile @@ -12,4 +12,20 @@ task :spec do sh 'bundle exec ./test/sequel/test_pgt_outbox.rb' end +desc 'Create the test database' +task :createdb do + require 'uri' + uri = URI.parse(ENV.fetch('PGT_SPEC_DB', 'postgres:///spgt_test')) + sh "createdb '#{File.basename(uri.path)}'" +end + +desc 'Drop the test database' +task :dropdb do + require 'uri' + uri = URI.parse(ENV.fetch('PGT_SPEC_DB', 'postgres:///spgt_test')) + sh "dropdb --if-exists '#{File.basename(uri.path)}'" +end + +task resetdb: %i[dropdb createdb] + task default: %i[rubocop spec] diff --git a/Readme.adoc b/Readme.adoc new file mode 100644 index 0000000..7866eb7 --- /dev/null +++ b/Readme.adoc @@ -0,0 +1,82 @@ += Transaction Outbox for Postgres + +This gem provides a way to implement the transaction outbox pattern using triggers (stored procedures) in Postgres. + +It utilizes the ruby Sequel ORM to interact with the database, but once it's set up, +it doesn't require any ruby nor Sequel code for the outbox population to operate. It +all runs on the PostgreSQL cluster, guaranteeing no matter how the database writes +occur, the outbox events will always be generated. + +== Installation + +Install the gem and add to the application's Gemfile by executing: + +```bash +bundle add sequel-pgt_outbox +``` + +If bundler is not being used to manage dependencies, install the gem by executing: + +```bash +gem install sequel-pgt_outbox +``` + +## Usage + +### Using the sequel cli + +```bash +sequel -r sequel/pgt_outbox postgres://localhost/mydb +Your database is stored in DB... +irb(main):001> DB.create_table(:foo) { primary_key :id; String :s; Integer :i } +=> nil +irb(main):002> function = DB.pgt_setup_outbox(:foo) +=> "pgt_outbox_foo_outbox" +irb(main):003> DB[:foo].insert(s: 'foo', i: 1) +=> 1 +irb(main):004> DB[:foo_outbox].first +=> nil +irb(main):005> DB.pgt_outbox_events(:foo, function) +=> +#, + @events=[:insert, :update, :delete], + @function="pgt_outbox_foo_outbox", + @name="pgt_outbox_foo_outbox", + @opts={when: nil}, + @table=:foo, + @trigger_opts={after: true, each_row: true}> +irb(main):006> DB[:foo].insert(s: 'bar', i: 2) +=> 2 +irb(main):007> DB[:foo_outbox].first +=> +{id: 1, + attempts: 0, + completed: nil, + created: 2025-02-15 18:20:30.28394 +0000, + updated: 2025-02-15 18:20:30.28394 +0000, + attempted: nil, + event_type: "foo_created", + last_error: nil, + data_before: nil, + data_after: "{\"i\": 2, \"s\": \"bar\", \"id\": 2}", + metadata: nil} +``` + +## Development + +After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. + +To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). + +## Contributing + +Bug reports and pull requests are welcome on GitHub at https://github.com/rubyists/sequel-pgt_outbox. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/rubyists/sequel-pgt_outbox/blob/main/CODE_OF_CONDUCT.md). + +## License + +The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). + +## Code of Conduct + +Everyone interacting in the Sequel::PgtOutbox project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rubyists/sequel-pgt_outbox/blob/main/CODE_OF_CONDUCT.md). diff --git a/ci/build_image.sh b/ci/build_image.sh new file mode 100755 index 0000000..01e4d83 --- /dev/null +++ b/ci/build_image.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash + +if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac +then + readlink=readlink +else + if greadlink -f . >/dev/null 2>&1 + then + readlink=greadlink + else + printf "You must install greadlink to use this (brew install coreutils)\n" >&2 + fi +fi # }}} + +# Set here to the full path to this script +me=${BASH_SOURCE[0]} +[ -L "$me" ] && me=$($readlink -f "$me") +here=$(cd "$(dirname "$me")" && pwd) +just_me=$(basename "$me") + +repo_top=$(git rev-parse --show-toplevel) +cd "$repo_top" || { + printf "Could not cd to %s\n" "$repo_top" >&2 + exit 1 +} + +base_dir=$(basename "$(pwd)") +: "${UBUNTU_VERSION:=bookworm}" +: "${BUILD_CONTEXT:=$(pwd)}" +: "${IMAGE_NAME:=$base_dir}" +: "${LICENSE:=MIT}" +: "${REGISTRY:=ghcr.io}" +: "${RUBY_VERSION:=3.3.6}" +: "${REGISTRY_TOKEN:=$GITHUB_TOKEN}" + +base_image_tag="$RUBY_VERSION-$UBUNTU_VERSION" +base_exists=$(skopeo list-tags docker://docker.io/ruby |jq -r "any(.Tags[] == \"$base_image_tag\"; .)") +if [ "$base_exists" = "false" ] +then + printf "Base image %s does not exist at docker.io/ruby, cannot build.\n" "$base_image_tag" >&2 + exit 99 +fi + +usage() { # {{{ + cat <<-EOT + Build an image, optionally pushing it to the registry + + Usage: $0 + Options: + -c CONTAINERFILE Path to the containerfile (default: ./oci/Containerfile) + -C CONTEXT Build context (default: $BUILD_CONTEXT) + -i NAME Name of the image (default: $IMAGE_NAME) + -l LICENSE License of the image (default: $LICENSE) + -r REGISTRY Registry to push the image to when -p is given (default: $REGISTRY) + -p Push the image to the registry + -h Show help / usage +EOT +} # }}} + +die() { # {{{ + local -i code + code=$1 + shift + error "$*" + printf "\n" >&2 + usage >&2 + # shellcheck disable=SC2086 + exit $code +} # }}} + +## Logging functions # {{{ +log() { # {{{ + printf "%s [%s] <%s> %s\n" "$(date '+%Y-%m-%d %H:%M:%S.%6N')" "$$" "${just_me:-$0}" "$*" +} # }}} + +debug() { # {{{ + [ $verbose -lt 2 ] && return 0 + # shellcheck disable=SC2059 + log_line=$(printf "$@") + log "[DEBUG] $log_line" >&2 +} # }}} + +warn() { # {{{ + # shellcheck disable=SC2059 + log_line=$(printf "$@") + log "[WARN] $log_line" >&2 +} # }}} + +error() { # {{{ + # shellcheck disable=SC2059 + log_line=$(printf "$@") + log "[ERROR] $log_line" >&2 +} # }}} + +info() { # {{{ + [ $verbose -lt 1 ] && return 0 + # shellcheck disable=SC2059 + log_line=$(printf "$@") + log "[INFO] $log_line" >&2 +} # }}} +# }}} + +push=0 +verbose=0 +while getopts :hpvc:C:i:l:r: opt # {{{ +do + case $opt in + c) + CONTAINERFILE=$OPTARG + ;; + C) + BUILD_CONTEXT=$OPTARG + ;; + i) + IMAGE_NAME=$OPTARG + ;; + l) + LICENSE=$OPTARG + ;; + r) + REGISTRY=$OPTARG + ;; + p) + push=1 + ;; + v) + verbose=$((verbose + 1)) + ;; + h) + usage + exit + ;; + :) + printf "Option %s requires an argument\n" "$OPTARG" >&2 + usage >&2 + exit 28 + ;; + ?) + printf "Invalid option '%s'\n" "$OPTARG" >&2 + usage >&2 + exit 27 + ;; + esac +done # }}} +shift $((OPTIND-1)) + +tag=$1 +[ -z "$tag" ] && die 1 "Missing image tag" +shift + +# Check for extra argument +if [ $# -gt 0 ]; then + # If we have the special argument '--' we shift it away, otherwise we die + [ "$1" != '--' ] && die 2 "Too many arguments" + # Once this is shifted away, the rest of the arguments are passed to the build command, below + shift +fi + +if [ -z "$CONTAINERFILE" ]; then + printf "No containerfile specified, looking for default locations\n" + for containerfile in Containerfile Dockerfile + do + if [ -f ./oci/"$containerfile" ]; then + debug "Found ./oci/%s\n" "$containerfile" + containerfile=./oci/"$containerfile" + break + fi + if [ -f "$containerfile" ]; then + debug "Found %s\n" "$containerfile" + break + fi + done +else + [ -f "$CONTAINERFILE" ] || die 3 "Containerfile '$CONTAINERFILE' not found" + debug "Using containerfile %s\n" "$CONTAINERFILE" + containerfile=$CONTAINERFILE +fi + +[ -f "$containerfile" ] || die 4 "No containerfile found" + +[ -d "$BUILD_CONTEXT" ] || die 5 "Build context '$BUILD_CONTEXT' not found" + +debug 'Building image from %s in in %s\n' "$containerfile" "$here" +# Build the image +if command -v podman 2>/dev/null +then + runtime=podman +elif command -v docker 2>/dev/null +then + runtime=docker +else + die 6 "No container runtime found" +fi + +revision=$(git rev-parse HEAD) +shortref=$(git rev-parse --short "$revision") +repo_url=$(git remote get-url upstream) +if [ -z "$repo_url" ] +then + die 7 "No remote found" +fi +if [[ $repo_url == *github.com/* ]] +then + owner_and_repo=${repo_url#*github.com/} +else + owner_and_repo=${repo_url##*:} +fi +# Get rid of the trailing .git +service=$(basename "$owner_and_repo" .git) +owner=$(dirname "$owner_and_repo") + +full_tag=$IMAGE_NAME:$tag-ruby$RUBY_VERSION-$UBUNTU_VERSION +# Pass any extra arguments to the build command ("$@" contains the rest of the arguments) +$runtime build --tag "$full_tag" "$@" \ + --label org.opencontainers.image.created="$(date --utc --iso-8601=seconds)" \ + --label org.opencontainers.image.description="Image for $service" \ + --label org.opencontainers.image.licenses="$LICENSE" \ + --label org.opencontainers.image.revision="$revision" \ + --label org.opencontainers.image.url="$repo_url" \ + --label org.opencontainers.image.title="$IMAGE_NAME" \ + --label org.opencontainers.image.source="Generated by ruby-automation's build_image.sh ($USER@$HOSTNAME)" \ + --label org.opencontainers.image.version="$full_tag" \ + --label shortref="$shortref" \ + --build-arg UBUNTU_VERSION="$UBUNTU_VERSION" \ + --build-arg RUBY_VERSION="$RUBY_VERSION" \ + -f "$containerfile" "$BUILD_CONTEXT" || die 8 "Failed to build image" + +[ $push -eq 1 ] || exit 0 +if ! $runtime login --get-login "$REGISTRY" >/dev/null 2>/dev/null +then + printf "Not logged in to '%s', trying to login\n" "$REGISTRY" >&2 + [ -z "$REGISTRY_TOKEN" ] && die 9 "No REGISTRY_TOKEN (nor GITHUB_TOKEN) set, cannot login" + printf "%s" "$REGISTRY_TOKEN" | $runtime login -u "$REGISTRY_TOKEN" --password-stdin "$REGISTRY" || die 10 "Failed to login to $REGISTRY" +fi + +# Split 1.2.3 into 1.2.3, 1.2, 1. We want to tag our image with all 3 of these +mapfile -t tags < <(echo "$tag" | awk -F'.' 'NF==3{print; print $1"."$2; print $1; next} NF==2{print; print $1; next} {print}') +for t in "${tags[@]}" +do + new_tag=$IMAGE_NAME:$t-ruby$RUBY_VERSION-$UBUNTU_VERSION + registry_image_name="$REGISTRY/$owner/$new_tag" + if [ "$runtime" = "podman" ] + then + if [ "$full_tag" != "$new_tag" ] + then + debug "Tagging %s as %s\n" "$full_tag" "$new_tag" + podman tag "$full_tag" "$new_tag" || die 11 "Failed to tag image $full_tag as $new_tag" + fi + podman push "$new_tag" "$registry_image_name" || die 12 "Failed to push image $new_tag to $registry_image_name" + else + debug "Tagging %s as %s\n" "$full_tag" "$registry_image_name" + docker tag "$full_tag" "$registry_image_name" || die 13 "Failed to tag image $full_tag as $registry_image_name" + docker push "$registry_image_name" || die 14 "Failed to push image $new_tag to $registry_image_name" + fi +done + +# vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash : diff --git a/ci/publish-gem.sh b/ci/publish-gem.sh new file mode 100755 index 0000000..d25682c --- /dev/null +++ b/ci/publish-gem.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash + +if readlink -f . >/dev/null 2>&1 # {{{ makes readlink work on mac +then + readlink=readlink +else + if greadlink -f . >/dev/null 2>&1 + then + readlink=greadlink + else + printf "You must install greadlink to use this (brew install coreutils)\n" >&2 + fi +fi # }}} + +# Set here to the full path to this script +me=${BASH_SOURCE[0]} +[ -L "$me" ] && me=$($readlink -f "$me") +here=$(cd "$(dirname "$me")" && pwd) +just_me=$(basename "$me") + +: "${GEM_NAME:=sequel-pgt_outbox}" +: "${GIT_ORG:=rubyists}" + +GEM_HOST=$1 +: "${GEM_HOST:=rubygems}" + +case "$GEM_HOST" in + rubygems) + gem_key='rubygems' + gem_host='https://rubygems.org' + ;; + github) + gem_key='github' + gem_host="https://rubygems.pkg.github.com/$GIT_ORG" + # Replace the gem host in the gemspec, so it allows pushing to the GitHub package registry + sed --in-place=.bak -e "s|https://rubygems.org|https://rubygems.pkg.github.com/$GIT_ORG|" "$here/../$GEM_NAME".gemspec + # Restore the original gemspec after the script finishes + trap 'mv -v "$here/../$GEM_NAME".gemspec.bak "$here/../$GEM_NAME".gemspec' EXIT + ;; + *) + printf 'Unknown GEM_HOST: %s\n' "$GEM_HOST" >&2 + exit 1 + ;; +esac + +# We only want this part running in CI, with no ~/.gem dir +# For local testing, you should have a ~/.gem/credentials file with +# the keys you need to push to rubygems or github +if [ ! -d ~/.gem ] +then + if [ -z "$GEM_TOKEN" ] + then + printf 'No GEM_TOKEN provided, cannot publish\n' >&2 + exit 1 + fi + mkdir -p ~/.gem + printf '%s\n:%s: %s\n' '---' "$gem_key" "$GEM_TOKEN" > ~/.gem/credentials + chmod 600 ~/.gem/credentials +fi + +if [ -f "$here"/../.version.txt ] +then + version=$(<"$here"/../.version.txt) +else + version=$(git describe --tags --abbrev=0 | sed -e 's/^v//') +fi + +gem="$(printf '%s-%s.gem' "$GEM_NAME" "$version")" +if [[ "${TRACE:-false}" == true || "${ACTIONS_STEP_DEBUG:-false}" == true ]] +then + printf "DEBUG: [%s] Building And Publishing %s to %s\n" "$just_me" "$gem" "$gem_host" >&2 +fi + +bundle exec gem build +bundle exec gem push -k "$gem_key" --host "$gem_host" "$gem" + +# vim: set foldmethod=marker et ts=4 sts=4 sw=4 ft=bash : diff --git a/lib/sequel/pgt_outbox/version.rb b/lib/sequel/pgt_outbox/version.rb index d746131..659a806 100644 --- a/lib/sequel/pgt_outbox/version.rb +++ b/lib/sequel/pgt_outbox/version.rb @@ -2,6 +2,8 @@ module Rubyists module PgtOutbox - VERSION = '0.1.0' + # x-release-please-start-version + VERSION = '0.2.0' + # x-release-please-end end end diff --git a/oci/Containerfile b/oci/Containerfile new file mode 100644 index 0000000..213955d --- /dev/null +++ b/oci/Containerfile @@ -0,0 +1,49 @@ +ARG UBUNTU_VERSION=bookworm +ARG RUBY_VERSION=3.4.1 +FROM docker.io/ruby:$RUBY_VERSION-$UBUNTU_VERSION AS build-env + +# Setting env up +ARG APP_ROOT=/app +ARG JOBS=8 +ENV LANG C.UTF-8 +ENV BUNDLE_SILENCE_ROOT_WARNING=1 + + +# Install dependencies needed for compilation +RUN apt-get update && \ + apt-get install -yq build-essential curl git libpq-dev postgresql-client + +WORKDIR $APP_ROOT + +COPY . . + +RUN mkdir -p ./vendor/cache && \ + bundle install && \ + bundle exec rake build && \ + mv pkg/*.gem ./vendor/cache/ && \ + cp oci/Gemfile . && \ + bundle install --jobs $JOBS + +# Remove folders not needed in resulting image +RUN rm -rf node_modules tmp/cache app/assets vendor/assets spec + +############### Build step done ############### +ARG UBUNTU_VERSION=bookworm +ARG RUBY_VERSION=3.3.4 +FROM docker.io/ruby:$RUBY_VERSION-$UBUNTU_VERSION + +ARG APP_ROOT=/app + +# install packages +RUN apt update && \ + apt install -qy curl bash runit && \ + useradd -m -u 1000 -s /bin/bash outbox + +# copy bundle from build-env +COPY --from=build-env /usr/local/bundle/ /usr/local/bundle/ + +USER 1000 +WORKDIR $APP_ROOT + + +CMD ['/bin/bash'] diff --git a/oci/Gemfile b/oci/Gemfile new file mode 100644 index 0000000..e820a89 --- /dev/null +++ b/oci/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# x-release-please-start-version +gem 'sequel-pgt_outbox', '= 0.2.0' +# x-release-please-end diff --git a/sequel-pgt_outbox.gemspec b/sequel-pgt_outbox.gemspec index 207eb83..08c1325 100644 --- a/sequel-pgt_outbox.gemspec +++ b/sequel-pgt_outbox.gemspec @@ -14,7 +14,7 @@ Gem::Specification.new do |spec| spec.license = 'MIT' spec.required_ruby_version = '>= 3.3.0' - spec.metadata['allowed_push_host'] = "TODO: Set to your gem server 'https://example.com'" + spec.metadata['allowed_push_host'] = 'https://rubygems.org' spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage