Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Exclude everything by default…
*
# …then re-include only what the Dockerfile actually copies.
!build/libs/butcher.jar
123 changes: 123 additions & 0 deletions .github/workflows/butcher-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
name: Butcher release

on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to (re)release butcher for, e.g. v1.15.0'
required: true
type: string

concurrency: butcher-release

permissions:
contents: write # create GitHub Release + upload jar asset
packages: write # push image to GHCR

jobs:
release:
runs-on: ubuntu-latest
# Long enough for multi-arch builds, arm64 under QEMU emulation can be slow on first run before GHA cache is warm
timeout-minutes: 30

steps:
- name: Resolve tag
id: tag
env:
# `inputs.tag` is user-supplied (typed into the workflow_dispatch form)
INPUT_TAG: ${{ inputs.tag }}

run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
# Manually triggered, use the user-supplied tag
TAG="$INPUT_TAG"
else
# Automatically triggered, use the tag from the ref
TAG="$GITHUB_REF_NAME"
fi

# Guard: tag must look like `v<major>.<minor>.<patch>` with an optional pre-release suffix, e.g.
# `v1.15.0` or `v1.15.0-rc1`
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
echo "FAIL: tag '$TAG' must match pattern 'v<major>.<minor>.<patch>[-prerelease]'"
exit 1
fi

VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Releasing butcher for tag=$TAG (version=$VERSION)"

- name: Checkout tag commit
uses: actions/checkout@v6
with:
ref: ${{ steps.tag.outputs.tag }}

- name: Setup JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'corretto'
cache: 'gradle'

- name: Build butcher fat jar
# `-Prelease.version=<X>` is nebula-release's override for "use this version, skip inference." The tag name is the
# single source of truth: this step uses it, the Docker metadata step uses it, the GitHub Release step uses it.
#
# To verify the mechanism locally:
# ./gradlew butcherJar -Prelease.version=9.9.9
# unzip -p build/libs/butcher.jar META-INF/MANIFEST.MF \
# | grep Implementation-Version
# Expected: `Implementation-Version: 9.9.9`
run: ./gradlew butcherJar -Prelease.version=${{ steps.tag.outputs.version }}

- name: Set up QEMU
uses: docker/setup-qemu-action@v4

- name: Set up Docker Buildx
# QEMU enables cross-arch builds (ARM under emulation on amd64 runners). Buildx is the multi-platform-aware builder.
uses: docker/setup-buildx-action@v4

- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract Docker metadata
id: docker-meta
uses: docker/metadata-action@v6
with:
images: ghcr.io/vgv/butcher
# `:latest` moves only on a plain production release. A prerelease tag (e.g. v0.195.0-rc.1, version contains a `-`)
# still builds and pushes its own immutable `:0.195.0-rc.1` image, but must not hijack `:latest` — operators pulling
# `:latest` expect the last stable build.
#
# type=semver skips the floating {{major}} / {{major}}.{{minor}} tags for prerelease versions automatically — no guard needed there.
tags: |
type=raw,value=${{ steps.tag.outputs.version }}
type=raw,value=latest,enable=${{ !contains(steps.tag.outputs.version, '-') }}
type=semver,pattern={{major}}.{{minor}},value=${{ steps.tag.outputs.version }}
type=semver,pattern={{major}},value=${{ steps.tag.outputs.version }}

- name: Build and push Docker image
uses: docker/build-push-action@v7
with:
context: .
file: Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.docker-meta.outputs.tags }}
labels: ${{ steps.docker-meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Upload butcher.jar to GitHub Release
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.tag.outputs.tag }}
files: build/libs/butcher.jar
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
57 changes: 29 additions & 28 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
name: On pull request

on:
pull_request:
paths-ignore:
- README.md
pull_request:
paths-ignore:
- README.md
- docs/**

concurrency: on-pull-request

jobs:
build:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'corretto'
cache: 'gradle'
- name: Setup JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'corretto'
cache: 'gradle'

- name: Build (by human)
if: github.actor != 'dependabot[bot]'
run: ./gradlew devSnapshot printDevSnapshotReleaseNote
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
- name: Build (by human)
if: github.actor != 'dependabot[bot]'
run: ./gradlew devSnapshot printDevSnapshotReleaseNote
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GITHUB_HEAD_REF: ${{ github.head_ref }}

- name: Build (by dependabot)
if: github.actor == 'dependabot[bot]'
run: ./gradlew test
- name: Build (by dependabot)
if: github.actor == 'dependabot[bot]'
run: ./gradlew test
61 changes: 31 additions & 30 deletions .github/workflows/push-to-main.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
name: On push to main

on:
push:
branches:
- main
paths-ignore:
- README.md
push:
branches:
- main
paths-ignore:
- README.md
- docs/**

concurrency: on-push-to-main

jobs:
build:
runs-on: ubuntu-latest
build:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Setup Git
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@users.noreply.github.com"
- name: Setup Git
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@users.noreply.github.com"

- name: Setup JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'corretto'
cache: 'gradle'
- name: Setup JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'corretto'
cache: 'gradle'

- name: Build
run: ./gradlew final closeAndReleaseStagingRepository printFinalReleaseNote
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
- name: Build
run: ./gradlew final closeAndReleaseStagingRepository printFinalReleaseNote
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_PASSWORD: ${{ secrets.GPG_SIGNING_PASSWORD }}
SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM eclipse-temurin:17-jre

LABEL org.opencontainers.image.source=https://github.com/vgv/kolbasa
LABEL org.opencontainers.image.title=butcher
LABEL org.opencontainers.image.description="CLI for managing a kolbasa cluster"

WORKDIR /work

COPY build/libs/butcher.jar /app/butcher.jar

ENTRYPOINT ["java", "-jar", "/app/butcher.jar"]
45 changes: 45 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,51 @@ tasks {
}
}

// ===== Butcher fat jar (build/libs/butcher.jar) =====
tasks.register<Jar>("butcherJar") {
group = "build"
description = "Builds the standalone butcher CLI fat jar"

archiveBaseName.set("butcher")
archiveClassifier.set("")
archiveVersion.set("")

// Reproducibility: same source → same output bytes. Useful for both
// CI cache hits and for the "same tag → same butcher.jar" guarantee
// we promised in Phase 1's failure-recovery section.
isPreserveFileTimestamps = false
isReproducibleFileOrder = true

manifest {
attributes(
"Main-Class" to "kolbasa.cluster.butcher.ButcherKt",
"Implementation-Title" to "butcher",
"Implementation-Version" to project.sanitizeVersion(),
// postgresql 42.7.x is a Multi-Release jar: it ships JDK11-specialized classes under META-INF/versions/11/
"Multi-Release" to true,
)
}

// Our own compiled classes + resources.
from(sourceSets.main.get().output)

// All runtime dependencies, unpacked. compileOnly deps (prometheus,
// opentelemetry) are deliberately NOT in runtimeClasspath, so they
// don't end up in the fat jar.
dependsOn(configurations.runtimeClasspath)
from({
configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) }
})

// Exclude per-dep MANIFEST.MF: each dep has its own; our `manifest { }`
// block above wins regardless, but excluding explicitly is clearer.
exclude("META-INF/MANIFEST.MF")
// Exclude a root module-info.class: we're an executable, not a JPMS module
exclude("/module-info.class")

duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}

tasks.withType<Sign> {
doFirst {
settingsProvider.validateGPGSecrets()
Expand Down
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ nebula = "20.2.0"
nexus = "2.0.0"

prometheus = "1.6.1"
opentelemetry = "1.61.0"
opentelemetry = "1.62.0"
opentelemetry-instrumentation = "2.27.0"
opentelemetry-instrumentation-incubator = "2.27.0-alpha"
opentelemetry-semconv = "1.41.0"
opentelemetry-semconv = "1.41.1"
hikaricp = "7.0.2"
postgresql = "42.7.11"

Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 3 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 1 addition & 1 deletion gradlew

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading