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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@martikan
101 changes: 101 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Go secure Build and Deploy
on:
workflow_dispatch:
push:
tags:
- 'v*'

permissions: read-all

jobs:
# ==========================================
# INIT (It generates the ldflags for build)
# ==========================================
init:
runs-on: ubuntu-latest
outputs:
commit-date: ${{ steps.ldflags.outputs.commit-date }}
commit: ${{ steps.ldflags.outputs.commit }}
version: ${{ steps.ldflags.outputs.version }}
tree-state: ${{ steps.ldflags.outputs.tree-state }}
go-version: ${{ steps.go-version.outputs.go-version }}
steps:
- id: checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- id: go-version
run: echo "go-version=$(grep '^go ' go.mod | awk '{print $2}')" >> "$GITHUB_OUTPUT"
- id: ldflags
run: |
echo "commit-date=$(git log --date=iso8601-strict -1 --pretty=%ct)" >> "$GITHUB_OUTPUT"
echo "commit=$GITHUB_SHA" >> "$GITHUB_OUTPUT"
echo "version=$(git describe --tags --always --dirty | cut -c2-)" >> "$GITHUB_OUTPUT"
echo "tree-state=$(if git diff --quiet; then echo "clean"; else echo "dirty"; fi)" >> "$GITHUB_OUTPUT"

# ==========================================
# SECURE SLSA BUILD (Runs ONLY on Tags)
# ==========================================
slsa-build:
needs:
- init
if: startsWith(github.ref, 'refs/tags/v')
permissions:
id-token: write
contents: write
actions: read
strategy:
matrix:
os:
- linux
- darwin
arch:
- amd64
- arm64
uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v2.1.0
with:
go-version: ${{ needs.init.outputs.go-version }}
config-file: .slsa-goreleaser-${{matrix.os}}-${{matrix.arch}}.yml
evaluated-envs: "COMMIT_DATE:${{needs.init.outputs.commit-date}}, COMMIT:${{needs.init.outputs.commit}}, VERSION:${{needs.init.outputs.version}}, TREE_STATE:${{needs.init.outputs.tree-state}}"
# This uploads the secure raw binaries AND the .intoto.jsonl receipt to the GitHub Release
upload-assets: true
private-repository: true

# ==========================================
# DOCKER DEPLOY (Runs ONLY on Tags)
# ==========================================
docker-deploy:
needs: [slsa-build]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Download Secure Linux Binary
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Only download the Linux AMD64 binary for the Docker image
gh release download ${{ github.ref_name }} --pattern "artemisctl-linux-x64"
mv artemisctl-linux-x64 artemisctl
chmod +x artemisctl

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

- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/martikan/artemisctl:${{ github.ref_name }}
ghcr.io/martikan/artemisctl:latest
200 changes: 200 additions & 0 deletions .github/workflows/quality-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
name: quality-gate

on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]

permissions: read-all

jobs:
# ==========================================
# Formatting and linting
# ==========================================
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: 'go.sum'

- name: formatting
run: |
fmt=$(gofmt -l .)
if [ -n "$fmt" ]; then
echo "$fmt"
exit 1
fi

- name: static-check
run: go vet ./...


# ==========================================
# TEST & COVERAGE
# ==========================================
test-and-coverage:
runs-on: ubuntu-latest
needs: [lint]
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
cache-dependency-path: 'go.sum'

- name: Install dependencies
run: go mod download

- name: Run Tests & Generate Coverage
run: make coverage-ci


- name: Generate Coverage Report
run: |
total=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')

echo "Total coverage: $total%"

threshold=85

echo "TOTAL_COVERAGE=$total" >> $GITHUB_ENV
echo "COVERAGE_THRESHOLD=$threshold" >> $GITHUB_ENV

awk -v total="$total" -v threshold="$threshold" 'BEGIN {
if (total < threshold) {
print "COVERAGE_FAIL=true"
} else {
print "COVERAGE_FAIL=false"
}
}' >> $GITHUB_ENV

# Extract worst packages
awk '
NR>1 {
file=$1
gsub(/:.*/, "", file)

stmts=$2
count=$3

total[file]+=stmts
if (count>0) covered[file]+=stmts
}
END {
for (f in total) {
cov=100*covered[f]/total[f]
printf "%s %.2f\n", f, cov
}
}' coverage.out | sort -k2 -n | head -6 > worst_files.txt


# Build report
echo "## 📋 Coverage Report" >> coverage-report.md
echo "" >> coverage-report.md
echo "**Total Coverage:** **${total}%**" >> coverage-report.md
echo "" >> coverage-report.md
echo "### Worst Covered Files" >> coverage-report.md
echo "" >> coverage-report.md

echo "| Status | File | Coverage |" >> coverage-report.md
echo "|------|------|------|" >> coverage-report.md

while read file cov; do

cov_int=$(printf "%.0f" $cov)

if [ "$cov_int" -lt 70 ]; then
icon="🔴"
elif [ "$cov_int" -lt 80 ]; then
icon="🟡"
elif [ "$cov_int" -lt 90 ]; then
icon="🟢"
else
icon="⭐"
fi

echo "| $icon | $file | **${cov}%** |" >> coverage-report.md

done < worst_files.txt

- name: Add Job Summary
run: cat coverage-report.md >> $GITHUB_STEP_SUMMARY

- name: Comment/Update PR coverage report
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require('fs');

const reportPath = 'coverage-report.md';
if (!fs.existsSync(reportPath)) {
throw new Error(`Coverage report not found at ${reportPath}`);
}
const body = fs.readFileSync(reportPath, 'utf8');

const prNumber = context.payload.pull_request
? context.payload.pull_request.number
: (context.issue && context.issue.number ? context.issue.number : null);

if (!prNumber) {
throw new Error('No issue/PR number found in the current context.');
}

const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
per_page: 100,
}
);

const existing = comments.find(c =>
typeof c.body === 'string' && c.body.includes('📋 Coverage Report')
);

if (existing) {
core.info(`Updating comment ${existing.id}`);
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body,
});
} else {
core.info('Creating new comment');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: body,
});
}

- name: Enforce Coverage Threshold
if: always()
run: |
echo "Coverage: $TOTAL_COVERAGE%"
echo "Threshold: $COVERAGE_THRESHOLD%"

if [ "$COVERAGE_FAIL" = "true" ]; then
echo "Coverage below threshold!"
exit 1
fi
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,10 @@ go.work.sum
# env file
.env

bin/
bin/

# Subagent-driven-development scratch ledger
.superpowers/

# Superpowers plan/spec scaffolding (design docs live in docs/design/)
docs/superpowers/
12 changes: 12 additions & 0 deletions .slsa-goreleaser-darwin-amd64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: 1

env:
- GO111MODULE=on
- CGO_ENABLED=0
goos: darwin
goarch: amd64
main: ./cmd/artemisctl
binary: artemisctl-darwin-x64

ldflags:
- "-s -w -X main.Version={{ .Env.VERSION }}"
11 changes: 11 additions & 0 deletions .slsa-goreleaser-darwin-arm64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 1

env:
- GO111MODULE=on
- CGO_ENABLED=0
goos: darwin
goarch: arm64
main: ./cmd/artemisctl
binary: artemisctl-darwin-arm
ldflags:
- "-s -w -X main.Version={{ .Env.VERSION }}"
13 changes: 13 additions & 0 deletions .slsa-goreleaser-linux-amd64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 1

env:
- GO111MODULE=on
- CGO_ENABLED=0
- GOAMD64=v3
goos: linux
goarch: amd64
main: ./cmd/artemisctl
binary: artemisctl-linux-x64

ldflags:
- "-s -w -X main.Version={{ .Env.VERSION }}"
11 changes: 11 additions & 0 deletions .slsa-goreleaser-linux-arm64.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: 1

env:
- GO111MODULE=on
- CGO_ENABLED=0
goos: linux
goarch: arm64
main: ./cmd/artemisctl
binary: artemisctl-linux-arm
ldflags:
- "-s -w -X main.Version={{ .Env.VERSION }}"
27 changes: 27 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ==========================================
# ENTERPRISE SLSA-COMPLIANT DOCKERFILE
# ==========================================
# We rely exclusively on the binary compiled by the SLSA secure runner.

# We use Google's Distroless static image.
# It contains NO shell, NO package manager, and NO utilities.
# It only contains necessary CA certificates, timezone data, and a non-root user.
FROM gcr.io/distroless/static:nonroot

# Standard OCI labels for enterprise container registries
LABEL org.opencontainers.image.title="artemisctl" \
org.opencontainers.image.description="CLI tool to manage Activemq Artemis brokers" \
org.opencontainers.image.vendor="github.com/martikan/artemisctl"

# Ensure we operate in the root directory
WORKDIR /

# Copy the securely compiled binary passed in by GitHub Actions.
# The --chown flag guarantees our non-root user has perfect permissions.
COPY --chown=nonroot:nonroot artemisctl /artemisctl

# Explicitly drop privileges.
# UID 65532 is the heavily restricted 'nonroot' user built into Google's Distroless images.
USER 65532:65532

ENTRYPOINT ["/artemisctl"]
Loading
Loading