Skip to content

release: bump version to v0.1.1 #1

release: bump version to v0.1.1

release: bump version to v0.1.1 #1

name: Release - Production
on:
push:
tags:
- 'v*.*.*'
env:
REGISTRY: ghcr.io
BACKEND_IMAGE_NAME: nethserver/my/backend
SYNC_IMAGE_NAME: nethserver/my/sync
COLLECT_IMAGE_NAME: nethserver/my/collect
FRONTEND_IMAGE_NAME: nethserver/my/frontend
PROXY_IMAGE_NAME: nethserver/my/proxy
jobs:
build-and-release:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: false # Disable automatic cache, we'll handle it manually
- name: Cache Go modules
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
# Quality checks before release
- name: Check code formatting (backend)
working-directory: backend
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "Code is not formatted properly in backend:"
gofmt -s -l .
exit 1
fi
- name: Check code formatting (sync)
working-directory: sync
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "Code is not formatted properly in sync:"
gofmt -s -l .
exit 1
fi
- name: Check code formatting (collect)
working-directory: collect
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "Code is not formatted properly in collect:"
gofmt -s -l .
exit 1
fi
- name: Run backend linting
uses: golangci/golangci-lint-action@v6
with:
version: latest
working-directory: backend
args: --timeout=10m
- name: Run sync linting
uses: golangci/golangci-lint-action@v6
with:
version: latest
working-directory: sync
args: --timeout=10m
- name: Run collect linting
uses: golangci/golangci-lint-action@v6
with:
version: latest
working-directory: collect
args: --timeout=10m
- name: Run backend tests
working-directory: backend
run: go test ./...
- name: Run sync tests
working-directory: sync
run: make test
- name: Run collect tests
working-directory: collect
run: go test ./...
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Install frontend dependencies
working-directory: frontend
run: npm ci
- name: Check frontend formatting
working-directory: frontend
run: npm run format
- name: Run frontend linting
working-directory: frontend
run: npm run lint
- name: Run frontend type checking
working-directory: frontend
run: npm run type-check
- name: Run frontend tests
working-directory: frontend
run: npm run test
- name: Test frontend build
working-directory: frontend
run: npm run build
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
# Backend Release
- name: Build backend binary
working-directory: backend
run: |
# Build for multiple platforms
mkdir -p dist
# Linux AMD64
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/backend-linux-amd64 main.go
# Linux ARM64
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/backend-linux-arm64 main.go
# macOS AMD64
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/backend-darwin-amd64 main.go
# macOS ARM64
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/backend-darwin-arm64 main.go
# Create archives
cd dist
tar -czf backend-linux-amd64.tar.gz backend-linux-amd64
tar -czf backend-linux-arm64.tar.gz backend-linux-arm64
tar -czf backend-darwin-amd64.tar.gz backend-darwin-amd64
tar -czf backend-darwin-arm64.tar.gz backend-darwin-arm64
# Collect Release
- name: Build collect binary
working-directory: collect
run: |
# Build for multiple platforms
mkdir -p dist
# Linux AMD64
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/collect-linux-amd64 main.go
# Linux ARM64
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/collect-linux-arm64 main.go
# macOS AMD64
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/collect-darwin-amd64 main.go
# macOS ARM64
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/collect-darwin-arm64 main.go
# Create archives
cd dist
tar -czf collect-linux-amd64.tar.gz collect-linux-amd64
tar -czf collect-linux-arm64.tar.gz collect-linux-arm64
tar -czf collect-darwin-amd64.tar.gz collect-darwin-amd64
tar -czf collect-darwin-arm64.tar.gz collect-darwin-arm64
# Sync Release
- name: Build sync binary
working-directory: sync
run: |
# Build for multiple platforms
mkdir -p dist
# Linux AMD64
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/sync-linux-amd64 ./cmd/sync/main.go
# Linux ARM64
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/sync-linux-arm64 ./cmd/sync/main.go
# macOS AMD64
GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/sync-darwin-amd64 ./cmd/sync/main.go
# macOS ARM64
GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags="-s -w" -o dist/sync-darwin-arm64 ./cmd/sync/main.go
# Create archives with configs
cd dist
for binary in sync-*; do
if [[ "$binary" != *.tar.gz ]]; then
mkdir -p "${binary}-release"
cp "$binary" "${binary}-release/sync"
cp -r ../configs "${binary}-release/"
tar -czf "${binary}.tar.gz" "${binary}-release"
rm -rf "${binary}-release"
fi
done
# Docker Images with multi-platform support
- name: Extract backend metadata
id: backend_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}
tags: |
type=semver,pattern=v{{version}},value=${{ steps.version.outputs.VERSION }}
type=raw,value=latest
- name: Build and push backend Docker image
uses: docker/build-push-action@v5
with:
context: backend
file: backend/Containerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.backend_meta.outputs.tags }}
labels: |
org.opencontainers.image.title=Nethesis Operation Center Backend
org.opencontainers.image.description=Backend API for Nethesis Operation Center
org.opencontainers.image.version=${{ steps.version.outputs.VERSION }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for backend image availability
run: |
echo "Waiting for image to be available in registry..."
sleep 30
# Generate SBOM for backend
- name: Generate backend SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.BACKEND_IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
format: cyclonedx-json
output-file: backend-sbom.cdx.json
- name: Extract sync metadata
id: sync_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.SYNC_IMAGE_NAME }}
tags: |
type=semver,pattern=v{{version}},value=${{ steps.version.outputs.VERSION }}
type=raw,value=latest
- name: Build and push sync Docker image
uses: docker/build-push-action@v5
with:
context: sync
file: sync/Containerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.sync_meta.outputs.tags }}
labels: |
org.opencontainers.image.title=Nethesis Operation Center Sync
org.opencontainers.image.description=CLI tool for syncing RBAC configuration with Logto
org.opencontainers.image.version=${{ steps.version.outputs.VERSION }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for sync image availability
run: |
echo "Waiting for image to be available in registry..."
sleep 30
# Generate SBOM for sync
- name: Generate sync SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.SYNC_IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
format: cyclonedx-json
output-file: sync-sbom.cdx.json
- name: Extract collect metadata
id: collect_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.COLLECT_IMAGE_NAME }}
tags: |
type=semver,pattern=v{{version}},value=${{ steps.version.outputs.VERSION }}
type=raw,value=latest
- name: Build and push collect Docker image
uses: docker/build-push-action@v5
with:
context: collect
file: collect/Containerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.collect_meta.outputs.tags }}
labels: |
org.opencontainers.image.title=Nethesis Operation Center Collect
org.opencontainers.image.description=Collection service for Nethesis Operation Center
org.opencontainers.image.version=${{ steps.version.outputs.VERSION }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for collect image availability
run: |
echo "Waiting for image to be available in registry..."
sleep 30
# Generate SBOM for collect
- name: Generate collect SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.COLLECT_IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
format: cyclonedx-json
output-file: collect-sbom.cdx.json
- name: Extract frontend metadata
id: frontend_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}
tags: |
type=semver,pattern=v{{version}},value=${{ steps.version.outputs.VERSION }}
type=raw,value=latest
- name: Build and push frontend Docker image
uses: docker/build-push-action@v5
with:
context: frontend
file: frontend/Containerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.frontend_meta.outputs.tags }}
labels: |
org.opencontainers.image.title=Nethesis Operation Center Frontend
org.opencontainers.image.description=Frontend web application for Nethesis Operation Center
org.opencontainers.image.version=${{ steps.version.outputs.VERSION }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for frontend image availability
run: |
echo "Waiting for image to be available in registry..."
sleep 30
# Generate SBOM for frontend
- name: Generate frontend SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.FRONTEND_IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
format: cyclonedx-json
output-file: frontend-sbom.cdx.json
- name: Extract proxy metadata
id: proxy_meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.PROXY_IMAGE_NAME }}
tags: |
type=semver,pattern=v{{version}},value=${{ steps.version.outputs.VERSION }}
type=raw,value=latest
- name: Build and push proxy Docker image
uses: docker/build-push-action@v5
with:
context: proxy
file: proxy/Containerfile
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.proxy_meta.outputs.tags }}
labels: |
org.opencontainers.image.title=Nethesis Operation Center Proxy
org.opencontainers.image.description=Reverse proxy for Nethesis Operation Center
org.opencontainers.image.version=${{ steps.version.outputs.VERSION }}
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Wait for image to be available in registry
- name: Wait for proxy image availability
run: |
echo "Waiting for image to be available in registry..."
sleep 30
# Generate SBOM for proxy
- name: Generate proxy SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.PROXY_IMAGE_NAME }}:${{ steps.version.outputs.VERSION }}
format: cyclonedx-json
output-file: proxy-sbom.cdx.json
# Generate changelog
- name: Build Changelog
id: build_changelog
run: |
# Get previous tag for commit range
CURRENT_TAG="${{ steps.version.outputs.VERSION }}"
PREV_TAG=$(git tag --sort=version:refname | grep -B1 "$CURRENT_TAG" | head -n1)
# Set commit range
if [ "$PREV_TAG" = "$CURRENT_TAG" ] || [ -z "$PREV_TAG" ]; then
COMMIT_RANGE="$CURRENT_TAG"
echo "Generating changelog from beginning to $CURRENT_TAG"
else
COMMIT_RANGE="$PREV_TAG..$CURRENT_TAG"
echo "Generating changelog from $PREV_TAG to $CURRENT_TAG"
fi
# Initialize changelog
CHANGELOG=""
# Features (feat, feature, with optional scope)
FEATURES=$(git log --oneline --grep="^feat" --grep="^feat(" --grep="^feature" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$FEATURES" ]; then
CHANGELOG="${CHANGELOG}## ✨ Features"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$FEATURES"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Bug fixes (fix, bug, with optional scope)
FIXES=$(git log --oneline --grep="^fix" --grep="^fix(" --grep="^bug" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$FIXES" ]; then
CHANGELOG="${CHANGELOG}## 🐛 Bug Fixes"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$FIXES"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Performance improvements
PERF=$(git log --oneline --grep="^perf" --grep="^perf(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$PERF" ]; then
CHANGELOG="${CHANGELOG}## ⚡ Performance Improvements"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$PERF"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Refactoring
REFACTOR=$(git log --oneline --grep="^refactor" --grep="^refactor(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$REFACTOR" ]; then
CHANGELOG="${CHANGELOG}## 🔧 Refactoring"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$REFACTOR"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Documentation
DOCS=$(git log --oneline --grep="^docs" --grep="^docs(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$DOCS" ]; then
CHANGELOG="${CHANGELOG}## 📖 Documentation"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$DOCS"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Tests
TESTS=$(git log --oneline --grep="^test" --grep="^test(" --grep="^tests" --grep="^tests(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$TESTS" ]; then
CHANGELOG="${CHANGELOG}## 🧪 Tests"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$TESTS"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Build & CI
BUILD=$(git log --oneline --grep="^build" --grep="^build(" --grep="^ci" --grep="^ci(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$BUILD" ]; then
CHANGELOG="${CHANGELOG}## 🔨 Build & CI"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$BUILD"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Deployment
DEPLOY=$(git log --oneline --grep="^deploy" --grep="^deploy(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$DEPLOY" ]; then
CHANGELOG="${CHANGELOG}## 🌐 Deployment"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$DEPLOY"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Chores
CHORES=$(git log --oneline --grep="^chore" --grep="^chore(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$CHORES" ]; then
CHANGELOG="${CHANGELOG}## 🧹 Chores"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$CHORES"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Release commits
RELEASES=$(git log --oneline --grep="^release" --grep="^release(" "$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$RELEASES" ]; then
CHANGELOG="${CHANGELOG}## 🚀 Releases"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$RELEASES"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Other changes (everything else)
OTHERS=$(git log --oneline --invert-grep \
--grep="^feat" --grep="^feat(" --grep="^feature" \
--grep="^fix" --grep="^fix(" --grep="^bug" \
--grep="^perf" --grep="^perf(" \
--grep="^refactor" --grep="^refactor(" \
--grep="^docs" --grep="^docs(" \
--grep="^test" --grep="^test(" --grep="^tests" --grep="^tests(" \
--grep="^build" --grep="^build(" --grep="^ci" --grep="^ci(" \
--grep="^deploy" --grep="^deploy(" \
--grep="^chore" --grep="^chore(" \
--grep="^release" --grep="^release(" \
"$COMMIT_RANGE" 2>/dev/null || true)
if [ -n "$OTHERS" ]; then
CHANGELOG="${CHANGELOG}## 📝 Other Changes"$'\n'
while IFS= read -r line; do
CHANGELOG="${CHANGELOG}- ${line}"$'\n'
done <<< "$OTHERS"
CHANGELOG="${CHANGELOG}"$'\n'
fi
# Add container images
CHANGELOG="${CHANGELOG}## 🐳 Container Images"$'\n'
CHANGELOG="${CHANGELOG}- \`ghcr.io/nethserver/my/backend:${{ steps.version.outputs.VERSION }}\`"$'\n'
CHANGELOG="${CHANGELOG}- \`ghcr.io/nethserver/my/sync:${{ steps.version.outputs.VERSION }}\`"$'\n'
CHANGELOG="${CHANGELOG}- \`ghcr.io/nethserver/my/collect:${{ steps.version.outputs.VERSION }}\`"$'\n'
CHANGELOG="${CHANGELOG}- \`ghcr.io/nethserver/my/frontend:${{ steps.version.outputs.VERSION }}\`"$'\n'
CHANGELOG="${CHANGELOG}- \`ghcr.io/nethserver/my/proxy:${{ steps.version.outputs.VERSION }}\`"$'\n'
# Set output for next step
echo "CHANGELOG<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
# Create GitHub Release with generated changelog
- name: Create Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.VERSION }}
name: ${{ steps.version.outputs.VERSION }}
draft: false
prerelease: ${{ contains(steps.version.outputs.VERSION, '-') }}
body: ${{ steps.build_changelog.outputs.CHANGELOG }}
files: |
backend/dist/*.tar.gz
sync/dist/*.tar.gz
collect/dist/*.tar.gz
backend-sbom.cdx.json
sync-sbom.cdx.json
collect-sbom.cdx.json
frontend-sbom.cdx.json
proxy-sbom.cdx.json