mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 15:51:26 +00:00
Merge pull request #8 from Dvorinka/ci/auto-release
CI / Infra (Compose + Scripts) (push) Canceled after 0s
CI / Backend (Go) (push) Canceled after 0s
CI / Frontend (React) (push) Canceled after 0s
CI / Compose boot smoke (push) Canceled after 0s
CI / Build & publish images (push) Canceled after 0s
CI / Release (push) Canceled after 0s
CI / Infra (Compose + Scripts) (push) Canceled after 0s
CI / Backend (Go) (push) Canceled after 0s
CI / Frontend (React) (push) Canceled after 0s
CI / Compose boot smoke (push) Canceled after 0s
CI / Build & publish images (push) Canceled after 0s
CI / Release (push) Canceled after 0s
feat(ci): release pipeline on merge to main
This commit is contained in:
8 files changed
+131
-11
No files matched your search
@@ -222,3 +222,96 @@ jobs:
|
||||
ignore-unfixed: true
|
||||
vuln-type: os,library
|
||||
severity: CRITICAL,HIGH
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
needs: [docker]
|
||||
if: github.event_name != 'pull_request'
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Conventional-commit bump. Tags pushed with GITHUB_TOKEN do not
|
||||
# retrigger this workflow, so images are retagged below instead.
|
||||
- name: Bump version and push tag
|
||||
if: github.ref == 'refs/heads/main'
|
||||
id: tag_version
|
||||
uses: mathieudutour/[email protected]
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
default_bump: false
|
||||
|
||||
- name: Resolve release tag
|
||||
id: release
|
||||
run: |
|
||||
if [ "${{ github.ref_type }}" = "tag" ]; then
|
||||
tag="${GITHUB_REF_NAME}"
|
||||
else
|
||||
tag="${{ steps.tag_version.outputs.new_tag }}"
|
||||
fi
|
||||
if [ -z "$tag" ]; then
|
||||
echo "No releasable commits since last tag - skipping release."
|
||||
fi
|
||||
echo "tag=$tag" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Setup Go
|
||||
if: steps.release.outputs.tag != ''
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: app/backend/go.mod
|
||||
cache: true
|
||||
|
||||
- name: Build CLI binaries
|
||||
if: steps.release.outputs.tag != ''
|
||||
working-directory: app/backend
|
||||
env:
|
||||
VERSION: ${{ steps.release.outputs.tag }}
|
||||
run: bash ../../scripts/build-cli.sh
|
||||
|
||||
- name: Checksums
|
||||
if: steps.release.outputs.tag != ''
|
||||
working-directory: app/backend/bin
|
||||
run: sha256sum containr-* > checksums.txt
|
||||
|
||||
- name: Login to GHCR
|
||||
if: steps.release.outputs.tag != '' && github.ref == 'refs/heads/main'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Tag pushes from github-tag-action do not trigger workflows, so the
|
||||
# release job retags the just-published :main images in-registry.
|
||||
- name: Tag release images in GHCR
|
||||
if: steps.release.outputs.tag != '' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
repo="${GITHUB_REPOSITORY,,}"
|
||||
for img in backend frontend; do
|
||||
docker buildx imagetools create \
|
||||
--tag "ghcr.io/${repo}-${img}:${TAG}" \
|
||||
--tag "ghcr.io/${repo}-${img}:latest" \
|
||||
"ghcr.io/${repo}-${img}:main"
|
||||
done
|
||||
|
||||
- name: Create GitHub Release
|
||||
if: steps.release.outputs.tag != ''
|
||||
working-directory: app/backend/bin
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.release.outputs.tag }}
|
||||
run: |
|
||||
# Delete-then-create keeps reruns idempotent: --clobber uploads
|
||||
# race GitHub's asset tombstones and intermittently 422. The git
|
||||
# tag itself is kept (no --cleanup-tag).
|
||||
gh release delete "$TAG" --yes 2>/dev/null || true
|
||||
gh release create "$TAG" --title "$TAG" --generate-notes \
|
||||
containr-* checksums.txt
|
||||
@@ -6,7 +6,15 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Injected at build time via -ldflags (see scripts/build-cli.sh).
|
||||
var (
|
||||
Version = "dev"
|
||||
BuildTime = "unknown"
|
||||
GitCommit = "unknown"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cli.SetVersion(fmt.Sprintf("%s (%s, %s)", Version, GitCommit, BuildTime))
|
||||
if err := cli.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -27,6 +27,12 @@ func Execute() {
|
||||
cobra.CheckErr(rootCmd.Execute())
|
||||
}
|
||||
|
||||
// SetVersion overrides the version reported by `containr --version`.
|
||||
// Called by main with the value injected via -ldflags at build time.
|
||||
func SetVersion(v string) {
|
||||
rootCmd.Version = v
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
// Bundles the repository docs/ tree into a generated TS module so the docs
|
||||
// browser renders instantly and offline. The GitHub sync layer refreshes it
|
||||
// at runtime when network is available.
|
||||
import { readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { join, relative } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const root = join(fileURLToPath(new URL('.', import.meta.url)), '..', '..', '..');
|
||||
const frontendRoot = join(fileURLToPath(new URL('.', import.meta.url)), '..');
|
||||
const root = join(frontendRoot, '..', '..');
|
||||
const docsDir = join(root, 'docs');
|
||||
const outFile = join(root, 'app/frontend/src/generated/docs-snapshot.ts');
|
||||
const outFile = join(frontendRoot, 'src/generated/docs-snapshot.ts');
|
||||
|
||||
// The published frontend image builds with only app/frontend as its Docker
|
||||
// context, so the repo docs tree is absent there — reuse the committed
|
||||
// snapshot instead of failing on ENOENT.
|
||||
if (!existsSync(docsDir)) {
|
||||
if (!existsSync(outFile)) {
|
||||
console.error('docs snapshot: docs/ missing and no committed snapshot to reuse');
|
||||
process.exit(1);
|
||||
}
|
||||
console.warn('docs snapshot: docs/ not found, reusing committed snapshot');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Directories that are internal working notes rather than user documentation.
|
||||
const SKIP_DIRS = new Set(['archive', 'design', 'superpowers']);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function LandingPage() {
|
||||
const sessionQuery = useAuthSession();
|
||||
const signedIn = Boolean(sessionQuery.data);
|
||||
|
||||
const projectsQuery = useQuery({ queryKey: ['landing-projects'], queryFn: listProjects, staleTime: 60_000 });
|
||||
const projectsQuery = useQuery({ queryKey: ['landing-projects'], queryFn: () => listProjects(), staleTime: 60_000 });
|
||||
const templatesQuery = useQuery({ queryKey: ['landing-templates'], queryFn: () => listTemplates(), staleTime: 60_000 });
|
||||
|
||||
const projects = projectsQuery.data ?? [];
|
||||
|
||||
@@ -160,7 +160,7 @@ export function ProjectsPage() {
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: ['projects'],
|
||||
enabled: !isDemoMode,
|
||||
queryFn: listProjects,
|
||||
queryFn: () => listProjects(),
|
||||
});
|
||||
const hostQuery = useQuery({
|
||||
queryKey: ['host-monitoring'],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
getApiBaseUrl,
|
||||
getAgentPublicBaseUrl,
|
||||
@@ -698,7 +698,7 @@ export function PeoplePage() {
|
||||
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: ['people-projects'],
|
||||
queryFn: listProjects,
|
||||
queryFn: () => listProjects(),
|
||||
});
|
||||
const auditLogsQuery = useQuery({
|
||||
queryKey: ['people-audit-logs'],
|
||||
@@ -1926,7 +1926,7 @@ function BindDatabasePanel({
|
||||
const [varKey, setVarKey] = useState('DATABASE_URL');
|
||||
const [bound, setBound] = useState(false);
|
||||
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: listProjects });
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: () => listProjects() });
|
||||
const servicesQuery = useQuery({
|
||||
queryKey: ['services', projectId],
|
||||
queryFn: () => listServicesByProject(projectId),
|
||||
@@ -2097,7 +2097,7 @@ export function HighAvailabilityPage() {
|
||||
const policiesQuery = useQuery({ queryKey: ['ha-policies'], queryFn: listFailoverPolicies });
|
||||
const alertsQuery = useQuery({ queryKey: ['ha-alerts'], queryFn: listActiveAlerts, refetchInterval: 15_000 });
|
||||
const healthQuery = useQuery({ queryKey: ['ha-health'], queryFn: listHealthResults, refetchInterval: 30_000 });
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: listProjects });
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: () => listProjects() });
|
||||
const servicesQuery = useQuery({
|
||||
queryKey: ['services', policyProjectId],
|
||||
queryFn: () => listServicesByProject(policyProjectId),
|
||||
@@ -2515,7 +2515,7 @@ export function SecurityPage() {
|
||||
const [scanType, setScanType] = useState<string>('comprehensive');
|
||||
const [scanServiceId, setScanServiceId] = useState('');
|
||||
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: listProjects });
|
||||
const projectsQuery = useQuery({ queryKey: ['projects'], queryFn: () => listProjects() });
|
||||
const servicesQuery = useQuery({
|
||||
queryKey: ['services', projectId],
|
||||
queryFn: () => listServicesByProject(projectId),
|
||||
|
||||
@@ -470,7 +470,7 @@ export function TemplatesPage() {
|
||||
const projectsQuery = useQuery({
|
||||
queryKey: ['template-projects'],
|
||||
enabled: !isDemoMode,
|
||||
queryFn: listProjects,
|
||||
queryFn: () => listProjects(),
|
||||
});
|
||||
|
||||
const templates = useMemo(
|
||||
|
||||
Reference in new issue
Block a user