Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 64b4d5901b | |||
| dc51d52da6 | |||
| 9cd8f1589f | |||
| 4ad08fb09c | |||
| 2cd0f295f8 | |||
| 371559b78f | |||
| 4b74f2c9ab | |||
| 66fb44eab2 | |||
| 6b2b6e05bb | |||
| 3ae9b80622 | |||
| 0bd4ee95b3 | |||
| df583bc183 | |||
| 07d9440966 | |||
| 94c881184e | |||
| 18f4ef2126 | |||
| d7e9c627a8 | |||
| 93e70e6d66 | |||
| d496a67eae | |||
| 4b32e84c03 | |||
| c5e210f653 | |||
| a945a825f2 | |||
| 86a2422129 | |||
| cc81906d3b | |||
| 6bfd1b6c30 | |||
| 6a422fe293 |
@@ -1,56 +0,0 @@
|
|||||||
name: CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install linters
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y --no-install-recommends shellcheck yamllint
|
|
||||||
|
|
||||||
- name: Lint Markdown
|
|
||||||
uses: DavidAnson/markdownlint-cli2-action@v19
|
|
||||||
with:
|
|
||||||
globs: "**/*.md"
|
|
||||||
|
|
||||||
- name: Lint YAML
|
|
||||||
run: yamllint .
|
|
||||||
|
|
||||||
- name: Shellcheck
|
|
||||||
run: shellcheck scripts/*.sh
|
|
||||||
|
|
||||||
- name: Validate skill frontmatter
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
fail=0
|
|
||||||
for f in skills/*/SKILL.md; do
|
|
||||||
fm=$(awk 'BEGIN{c=0} /^---$/{c++; next} c==1{print} c>=2{exit}' "$f")
|
|
||||||
for key in name description; do
|
|
||||||
if ! printf '%s\n' "$fm" | grep -qE "^${key}:[[:space:]]"; then
|
|
||||||
echo "::error file=${f}::missing '${key}' in YAML frontmatter"
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
exit $fail
|
|
||||||
|
|
||||||
ci:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Validate JSON files
|
|
||||||
run: |
|
|
||||||
find . -name "*.json" -not -path "./.git/*" | while read -r f; do
|
|
||||||
python3 -m json.tool "$f" > /dev/null || { echo "::error file=$f::Invalid JSON"; exit 1; }
|
|
||||||
done
|
|
||||||
echo "All JSON files valid"
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
name: Promotion Gate
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
promotion_gate:
|
|
||||||
name: Promotion Gate
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Validate skills directory structure
|
|
||||||
run: |
|
|
||||||
set -e
|
|
||||||
fail=0
|
|
||||||
for dir in skills/*/; do
|
|
||||||
if [ ! -f "${dir}SKILL.md" ]; then
|
|
||||||
echo "::error::Missing SKILL.md in ${dir}"
|
|
||||||
fail=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exit $fail
|
|
||||||
@@ -0,0 +1,440 @@
|
|||||||
|
name: Plugin Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version (e.g. 1.0.0)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
node-version:
|
||||||
|
description: 'Node.js version to use'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: '22'
|
||||||
|
upstream-repo:
|
||||||
|
description: 'Upstream repo to fetch appVersion from (e.g. fenio/tns-csi). Leave empty to skip.'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
secrets:
|
||||||
|
RELEASE_APP_ID:
|
||||||
|
description: 'GitHub App ID for creating PRs (org blocks GITHUB_TOKEN from creating PRs)'
|
||||||
|
required: true
|
||||||
|
RELEASE_APP_PRIVATE_KEY:
|
||||||
|
description: 'GitHub App private key (PEM format)'
|
||||||
|
required: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: release
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-secrets:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
ready: ${{ steps.check.outputs.ready }}
|
||||||
|
steps:
|
||||||
|
- name: Verify RELEASE_APP_ID is configured
|
||||||
|
id: check
|
||||||
|
env:
|
||||||
|
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||||
|
run: |
|
||||||
|
if [ -z "$RELEASE_APP_ID" ]; then
|
||||||
|
echo "::notice::RELEASE_APP_ID org secret is not configured (see PRI-380). Release skipped — no artifacts will be created."
|
||||||
|
echo "ready=false" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "ready=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
ci:
|
||||||
|
needs: check-secrets
|
||||||
|
if: needs.check-secrets.outputs.ready == 'true'
|
||||||
|
uses: ./.github/workflows/plugin-ci.yaml
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.node-version }}
|
||||||
|
|
||||||
|
check-token-permissions:
|
||||||
|
needs: check-secrets
|
||||||
|
if: needs.check-secrets.outputs.ready == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
has_write: ${{ steps.check.outputs.has_write }}
|
||||||
|
steps:
|
||||||
|
- name: Generate GitHub App token
|
||||||
|
id: app-token
|
||||||
|
uses: actions/create-github-app-token@v3
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.RELEASE_APP_ID }}
|
||||||
|
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Check write permissions via API
|
||||||
|
id: check
|
||||||
|
run: |
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X POST \
|
||||||
|
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
|
||||||
|
-H "Accept: application/vnd.github+json" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/git/refs" \
|
||||||
|
-d '{"ref":"refs/heads/_release_check","sha":"${{ github.sha }}"}')
|
||||||
|
if [ "$HTTP_CODE" = "201" ]; then
|
||||||
|
echo "::notice::Token has write permission — cleaning up test ref."
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X DELETE \
|
||||||
|
-H "Authorization: Bearer ${{ steps.app-token.outputs.token }}" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/git/refs/heads/_release_check"
|
||||||
|
echo "has_write=true" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "$HTTP_CODE" = "403" ]; then
|
||||||
|
echo "::error::Token lacks write permission. Release cannot push tags or branches."
|
||||||
|
echo "has_write=false" >> $GITHUB_OUTPUT
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "::warning::Unexpected response ($HTTP_CODE) when checking write permission."
|
||||||
|
echo "has_write=false" >> $GITHUB_OUTPUT
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
check-tag:
|
||||||
|
needs: check-secrets
|
||||||
|
if: needs.check-secrets.outputs.ready == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
skip: ${{ steps.check.outputs.skip }}
|
||||||
|
steps:
|
||||||
|
- name: Check if tag already exists
|
||||||
|
id: check
|
||||||
|
run: |
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: Bearer ${{ github.token }}" \
|
||||||
|
"https://api.github.com/repos/${{ github.repository }}/git/refs/tags/v${{ inputs.version }}")
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "::notice::Tag v${{ inputs.version }} already exists. Release skipped (not an error)."
|
||||||
|
echo "skip=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "skip=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: [ci, check-tag, check-secrets, check-token-permissions]
|
||||||
|
if: needs.check-secrets.outputs.ready == 'true' && needs.check-tag.outputs.skip != 'true' && needs.check-token-permissions.outputs.has_write == 'true'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Validate version format
|
||||||
|
run: |
|
||||||
|
if [[ ! "${{ inputs.version }}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "Error: Version must be in X.Y.Z format"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Detect package manager
|
||||||
|
id: pkg-manager
|
||||||
|
run: |
|
||||||
|
if [ -f "pnpm-lock.yaml" ]; then
|
||||||
|
echo "manager=pnpm" >> $GITHUB_OUTPUT
|
||||||
|
echo "lockfile=pnpm-lock.yaml" >> $GITHUB_OUTPUT
|
||||||
|
# Check for packageManager field in package.json (Corepack pinning).
|
||||||
|
# pnpm/action-setup@v5 errors when packageManager is absent and no version
|
||||||
|
# is specified, so use Corepack for repos that have the field pinned and
|
||||||
|
# fall back to pnpm/action-setup with version: latest for repos that don't.
|
||||||
|
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
|
||||||
|
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||||
|
echo "lockfile=package-lock.json" >> $GITHUB_OUTPUT
|
||||||
|
echo "has_package_manager=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: ${{ inputs.node-version }}
|
||||||
|
# Only enable built-in npm caching here; pnpm caching is handled below
|
||||||
|
# after pnpm is installed (corepack is not available before setup-node).
|
||||||
|
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
|
||||||
|
|
||||||
|
- name: Setup pnpm (via Corepack, reads version from packageManager field)
|
||||||
|
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
|
||||||
|
run: |
|
||||||
|
npm install -g corepack
|
||||||
|
corepack enable pnpm
|
||||||
|
corepack install
|
||||||
|
|
||||||
|
- name: Setup pnpm (version latest)
|
||||||
|
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
|
||||||
|
uses: pnpm/action-setup@v5
|
||||||
|
with:
|
||||||
|
run_install: false
|
||||||
|
version: latest
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-store
|
||||||
|
if: steps.pkg-manager.outputs.manager == 'pnpm'
|
||||||
|
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache pnpm store
|
||||||
|
if: steps.pkg-manager.outputs.manager == 'pnpm'
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-store.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config --global user.name "github-actions[bot]"
|
||||||
|
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||||
|
|
||||||
|
- name: Update version in package.json
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
|
||||||
|
pnpm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||||
|
else
|
||||||
|
npm version ${{ inputs.version }} --no-git-tag-version --allow-same-version
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update artifacthub-pkg.yml
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
if [ -f artifacthub-pkg.yml ]; then
|
||||||
|
PKG_NAME=$(grep '^name:' artifacthub-pkg.yml | cut -d: -f2 | tr -d ' "')
|
||||||
|
else
|
||||||
|
PKG_NAME=$(jq -r .name package.json | sed 's|^@[^/]*/||')
|
||||||
|
fi
|
||||||
|
RELEASE_URL="https://github.com/${{ github.repository }}/releases/download/v${VERSION}/${PKG_NAME}-${VERSION}.tar.gz"
|
||||||
|
sed -i "s/^version:.*/version: \"${VERSION}\"/" artifacthub-pkg.yml
|
||||||
|
sed -i "s|headlamp/plugin/archive-url:.*|headlamp/plugin/archive-url: \"${RELEASE_URL}\"|" artifacthub-pkg.yml
|
||||||
|
|
||||||
|
- name: Update appVersion from upstream release
|
||||||
|
if: inputs.upstream-repo != ''
|
||||||
|
run: |
|
||||||
|
APP_VERSION=$(curl -sf "https://api.github.com/repos/${{ inputs.upstream-repo }}/releases/latest" | jq -r '.tag_name | ltrimstr("v")')
|
||||||
|
if [ -z "$APP_VERSION" ] || [ "$APP_VERSION" = "null" ]; then
|
||||||
|
echo "::warning::Could not fetch latest upstream release, skipping appVersion update"
|
||||||
|
else
|
||||||
|
sed -i "s|^appVersion:.*|appVersion: \"${APP_VERSION}\"|" artifacthub-pkg.yml
|
||||||
|
echo "appVersion set to ${APP_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
max_attempts=3
|
||||||
|
attempt=1
|
||||||
|
while [ $attempt -le $max_attempts ]; do
|
||||||
|
echo "Attempt $attempt of $max_attempts"
|
||||||
|
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
|
||||||
|
pnpm install --frozen-lockfile && break
|
||||||
|
else
|
||||||
|
npm ci && break
|
||||||
|
fi
|
||||||
|
if [ $attempt -lt $max_attempts ]; then
|
||||||
|
echo "::warning::Install step failed on attempt $attempt. Retrying in 5 seconds..."
|
||||||
|
sleep 5
|
||||||
|
fi
|
||||||
|
attempt=$((attempt + 1))
|
||||||
|
done
|
||||||
|
if [ $attempt -gt $max_attempts ]; then
|
||||||
|
echo "::error::Install step failed after $max_attempts attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build plugin
|
||||||
|
run: npx @kinvolk/headlamp-plugin build
|
||||||
|
|
||||||
|
- name: Package plugin
|
||||||
|
run: npx @kinvolk/headlamp-plugin package
|
||||||
|
|
||||||
|
- name: Prepare release tarball
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
# headlamp-plugin strips the @org/ prefix when naming tarballs.
|
||||||
|
# e.g. @privilegedescalation/headlamp-argocd-plugin -> headlamp-argocd-plugin
|
||||||
|
if [ -f artifacthub-pkg.yml ]; then
|
||||||
|
PKG_NAME=$(grep '^name:' artifacthub-pkg.yml | cut -d: -f2 | tr -d ' "')
|
||||||
|
else
|
||||||
|
PKG_NAME=$(jq -r .name package.json | sed 's|^@[^/]*/||')
|
||||||
|
fi
|
||||||
|
TARBALL="${PKG_NAME}-${VERSION}.tar.gz"
|
||||||
|
for f in *.tar.gz; do
|
||||||
|
[ "$f" != "$TARBALL" ] && mv "$f" "$TARBALL"
|
||||||
|
done
|
||||||
|
if [ ! -f "$TARBALL" ]; then
|
||||||
|
echo "Error: Expected tarball $TARBALL not found"
|
||||||
|
ls -la *.tar.gz 2>/dev/null || echo "No .tar.gz files found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "TARBALL=$TARBALL" >> $GITHUB_ENV
|
||||||
|
echo "PKG_NAME=$PKG_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Validate tarball
|
||||||
|
run: |
|
||||||
|
echo "Tarball: ${{ env.TARBALL }}"
|
||||||
|
ls -lh "${{ env.TARBALL }}"
|
||||||
|
tar -tzf "${{ env.TARBALL }}" | head -20
|
||||||
|
tar -tzf "${{ env.TARBALL }}" | grep -q "main.js" || { echo "Error: main.js not found in tarball"; exit 1; }
|
||||||
|
|
||||||
|
- name: Compute checksum
|
||||||
|
run: |
|
||||||
|
CHECKSUM=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||||
|
echo "CHECKSUM=$CHECKSUM" >> $GITHUB_ENV
|
||||||
|
sed -i "s|headlamp/plugin/archive-checksum:.*|headlamp/plugin/archive-checksum: sha256:${CHECKSUM}|" artifacthub-pkg.yml
|
||||||
|
|
||||||
|
- name: Commit and tag
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
BRANCH="release/v${VERSION}"
|
||||||
|
# If the release branch already exists (e.g. from a failed prior run),
|
||||||
|
# delete it so the re-trigger can proceed cleanly. The check-tag job
|
||||||
|
# above already skips when the tag exists, so we only reach here when
|
||||||
|
# the tag does NOT exist yet — safe to remove a stale branch.
|
||||||
|
if git ls-remote --exit-code origin "refs/heads/$BRANCH" 2>/dev/null; then
|
||||||
|
echo "::notice::Branch $BRANCH already exists — deleting for clean re-trigger."
|
||||||
|
git push origin --delete "$BRANCH"
|
||||||
|
fi
|
||||||
|
git checkout -b "$BRANCH"
|
||||||
|
git add package.json "${{ steps.pkg-manager.outputs.lockfile }}" artifacthub-pkg.yml
|
||||||
|
git commit -m "release: v${VERSION}"
|
||||||
|
git tag "v${VERSION}"
|
||||||
|
git push origin "$BRANCH"
|
||||||
|
git push origin "refs/tags/v${VERSION}"
|
||||||
|
|
||||||
|
- name: Generate GitHub App token
|
||||||
|
id: app-token
|
||||||
|
uses: actions/create-github-app-token@v3
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.RELEASE_APP_ID }}
|
||||||
|
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: "v${{ inputs.version }}"
|
||||||
|
files: ${{ env.TARBALL }}
|
||||||
|
fail_on_unmatched_files: false
|
||||||
|
generate_release_notes: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Install GitHub CLI
|
||||||
|
run: |
|
||||||
|
if ! command -v gh &>/dev/null; then
|
||||||
|
GH_VERSION="2.74.0"
|
||||||
|
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" -o /tmp/gh.tar.gz
|
||||||
|
tar -xzf /tmp/gh.tar.gz -C /tmp
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
mv "/tmp/gh_${GH_VERSION}_linux_amd64/bin/gh" "$HOME/.local/bin/gh"
|
||||||
|
rm -rf /tmp/gh.tar.gz "/tmp/gh_${GH_VERSION}_linux_amd64"
|
||||||
|
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||||
|
"$HOME/.local/bin/gh" --version
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Create PR for version bump
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
BODY=$(printf "Automated version bump and checksum update for v%s.\n\ncc @cpfarhood" "${VERSION}")
|
||||||
|
# Create PR only if an OPEN one doesn't already exist.
|
||||||
|
# Note: gh pr view also finds MERGED PRs; we must check for open ones explicitly
|
||||||
|
# so that a re-trigger after a stale-branch delete creates a fresh PR.
|
||||||
|
OPEN_PR=$(gh pr list --base main --head "release/v${VERSION}" --state open --json number --jq '.[0].number' 2>/dev/null)
|
||||||
|
if [ -z "$OPEN_PR" ]; then
|
||||||
|
gh pr create \
|
||||||
|
--title "release: v${VERSION}" \
|
||||||
|
--body "$BODY" \
|
||||||
|
--base main \
|
||||||
|
--head "release/v${VERSION}"
|
||||||
|
# Pull the number again to handle both create and pre-existing cases
|
||||||
|
OPEN_PR=$(gh pr list --base main --head "release/v${VERSION}" --state open --json number --jq '.[0].number' 2>/dev/null)
|
||||||
|
else
|
||||||
|
echo "::notice::Open PR #${OPEN_PR} for release/v${VERSION} already exists — skipping creation."
|
||||||
|
fi
|
||||||
|
# Guard: ensure we have a PR number before proceeding
|
||||||
|
if [ -z "$OPEN_PR" ]; then
|
||||||
|
echo "::error::Could not determine PR number for release/v${VERSION}."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "::notice::Working with PR #${OPEN_PR}"
|
||||||
|
|
||||||
|
# Check if PR was already merged (idempotency — safe to re-trigger after a stale branch)
|
||||||
|
MERGED_CHECK=$(gh pr view "$OPEN_PR" --json state --jq '.state' 2>/dev/null)
|
||||||
|
if [ "$MERGED_CHECK" = "MERGED" ]; then
|
||||||
|
echo "::notice::PR #${OPEN_PR} was already merged. Nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
# Determine whether to use --auto or not based on current status.
|
||||||
|
# Retry the status check up to 3 times with exponential back-off when
|
||||||
|
# GitHub is still computing the merge state (UNKNOWN state).
|
||||||
|
MAX_RETRIES=3
|
||||||
|
BACKOFF=3
|
||||||
|
MERGE_STATE=""
|
||||||
|
for i in $(seq 1 $MAX_RETRIES); do
|
||||||
|
MERGE_STATE=$(gh pr view "$OPEN_PR" --json mergeStateStatus --jq '.mergeStateStatus' 2>/dev/null)
|
||||||
|
if [ "$MERGE_STATE" != "UNKNOWN" ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ $i -lt $MAX_RETRIES ]; then
|
||||||
|
echo "PR merge state is UNKNOWN (GitHub still computing). Retry ${i}/${MAX_RETRIES} in ${BACKOFF}s..."
|
||||||
|
sleep $BACKOFF
|
||||||
|
BACKOFF=$((BACKOFF * 2))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$MERGE_STATE" = "BLOCKED" ] || [ "$MERGE_STATE" = "UNKNOWN" ]; then
|
||||||
|
echo "PR is $MERGE_STATE — attempting auto-merge (safe fallback, waits for branch protection checks)."
|
||||||
|
if gh pr merge "$OPEN_PR" --auto --squash --delete-branch 2>&1; then
|
||||||
|
echo "Auto-merge initiated successfully."
|
||||||
|
else
|
||||||
|
AUTO_MERGE_ERR=$?
|
||||||
|
# If --auto failed because auto-merge is disabled for this repo
|
||||||
|
# (autoMergeAllowed: false), fall back to --admin which merges
|
||||||
|
# regardless of branch protection rules. --admin requires GitHub
|
||||||
|
# App token, not GITHUB_TOKEN, so GH_TOKEN is already correct.
|
||||||
|
if gh pr merge "$OPEN_PR" --admin --squash --delete-branch 2>&1; then
|
||||||
|
echo "Auto-merge unavailable (autoMergeAllowed: false) — merged via --admin."
|
||||||
|
else
|
||||||
|
echo "::error::Both --auto and --admin merge failed. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "PR is $MERGE_STATE — merging directly."
|
||||||
|
gh pr merge "$OPEN_PR" --squash --delete-branch
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Verify checksums are consistent (main == tag == tarball)
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
TARBALL_CS=$(sha256sum "${{ env.TARBALL }}" | awk '{print $1}')
|
||||||
|
|
||||||
|
# Checksum recorded in the tag's artifacthub-pkg.yml
|
||||||
|
TAG_CS=$(git show "v${VERSION}:artifacthub-pkg.yml" 2>/dev/null | grep "archive-checksum" | awk '{print $2}' | sed 's/sha256://')
|
||||||
|
|
||||||
|
# Checksum now on main (after PR merge)
|
||||||
|
MAIN_CS=$(git fetch origin main 2>/dev/null; git show "origin/main:artifacthub-pkg.yml" | grep "archive-checksum" | awk '{print $2}' | sed 's/sha256://')
|
||||||
|
|
||||||
|
echo "Tarball SHA256 : $TARBALL_CS"
|
||||||
|
echo "Tag artifacthub: $TAG_CS"
|
||||||
|
echo "Main artifacthub: $MAIN_CS"
|
||||||
|
|
||||||
|
FAIL=0
|
||||||
|
[ "$TARBALL_CS" != "$TAG_CS" ] && echo "ERROR: tag checksum mismatch!" && FAIL=1
|
||||||
|
[ "$TARBALL_CS" != "$MAIN_CS" ] && echo "ERROR: main checksum mismatch!" && FAIL=1
|
||||||
|
[ "$FAIL" = "1" ] && exit 1
|
||||||
|
echo "All checksums consistent — ArtifactHub will index correctly."
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Markdownlint configuration for the org repo.
|
|
||||||
# Skill files intentionally use longer lines and emphasis-as-headings.
|
|
||||||
# Allow these patterns for skills directory.
|
|
||||||
|
|
||||||
# Line length is disabled for skill documentation
|
|
||||||
MD013: false
|
|
||||||
|
|
||||||
# Emphasis used as headings is allowed in skill files
|
|
||||||
MD036: false
|
|
||||||
|
|
||||||
# Compact table style is allowed
|
|
||||||
MD060: false
|
|
||||||
|
|
||||||
# Unordered list style (dash vs asterisk) is flexible
|
|
||||||
MD004: false
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
extends: default
|
|
||||||
|
|
||||||
rules:
|
|
||||||
line-length: disable
|
|
||||||
document-start: disable
|
|
||||||
truthy:
|
|
||||||
check-keys: false
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Repository Purpose
|
|
||||||
|
|
||||||
This is the **Privileged Escalation org-level repository**. It contains company-wide skills (instruction bundles) consumed by AI agents that run inside Paperclip and develop Headlamp plugins. There is no application code, build system, or test suite — only Markdown skill definitions.
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
- `skills/` — Company skill definitions, each in its own directory with a `SKILL.md` file
|
|
||||||
- `skills/safety/SKILL.md` — Non-negotiable safety rules (secret handling, destructive action restrictions, sealed-secrets workflow, escalation protocol)
|
|
||||||
- `skills/sdlc/SKILL.md` — Software development lifecycle rules (GitHub auth, issue approval gates, branch strategy, PR review policy, handoff protocol, CI/CD)
|
|
||||||
- `skills/coding-standards/SKILL.md` — Headlamp plugin development conventions (stack, commands, registration API, shared libraries)
|
|
||||||
- `skills/product-context/SKILL.md` — Product context (plugin portfolio, target users, competitive landscape, evaluation framework, feature spec template)
|
|
||||||
|
|
||||||
## Skill File Format
|
|
||||||
|
|
||||||
Each skill is a Markdown file with YAML frontmatter containing `name` and `description` fields:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
name: skill-name
|
|
||||||
description: >
|
|
||||||
One-line description of what the skill covers.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Skill Title
|
|
||||||
|
|
||||||
Content...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Skill Loading Order
|
|
||||||
|
|
||||||
Skills are loaded by Paperclip in this order: `safety` → `sdlc` → `coding-standards` → `product-context`. Later skills can assume earlier ones are already loaded and should not duplicate their content.
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
@@ -1,6 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"extends": [
|
|
||||||
"local>privilegedescalation/.github:renovate-config"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# CI Health Check Script
|
|
||||||
# Checks CI health across all privilegedescalation repos and reports failures
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Configuration
|
|
||||||
ORG="privilegedescalation"
|
|
||||||
MAX_AGE_DAYS=30
|
|
||||||
CRITICAL_THRESHOLD=3 # Number of consecutive failures to consider critical
|
|
||||||
|
|
||||||
# Colors for output
|
|
||||||
RED='\033[0;31m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
# Repos to monitor
|
|
||||||
REPOS=(
|
|
||||||
"org"
|
|
||||||
"infra"
|
|
||||||
"headlamp-sealed-secrets-plugin"
|
|
||||||
"headlamp-rook-plugin"
|
|
||||||
"headlamp-intel-gpu-plugin"
|
|
||||||
"headlamp-kube-vip-plugin"
|
|
||||||
"headlamp-tns-csi-plugin"
|
|
||||||
"headlamp-argocd-plugin"
|
|
||||||
"headlamp-polaris-plugin"
|
|
||||||
)
|
|
||||||
|
|
||||||
echo "=== CI Health Check for $ORG ==="
|
|
||||||
echo "Generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Track issues
|
|
||||||
FAILURES=()
|
|
||||||
STALE_REPOS=()
|
|
||||||
NO_CI_REPOS=()
|
|
||||||
|
|
||||||
for repo in "${REPOS[@]}"; do
|
|
||||||
echo "Checking $repo..."
|
|
||||||
|
|
||||||
# Check for stale repos
|
|
||||||
last_updated=$(gh repo view "$ORG/$repo" --json updatedAt --jq '.updatedAt' 2>/dev/null || echo "unknown")
|
|
||||||
if [[ "$last_updated" != "unknown" ]]; then
|
|
||||||
last_updated_date=$(date -d "$last_updated" +%s 2>/dev/null || echo "0")
|
|
||||||
cutoff_date=$(date -d "$MAX_AGE_DAYS days ago" +%s)
|
|
||||||
if [[ "$last_updated_date" -lt "$cutoff_date" ]]; then
|
|
||||||
STALE_REPOS+=("$repo (last updated: $last_updated)")
|
|
||||||
echo -e " ${YELLOW}⚠ Stale repo${NC}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check for CI workflows
|
|
||||||
workflow_count=$(gh api repos/"$ORG/$repo"/actions/workflows 2>/dev/null | jq -r '.total_count' || echo "0")
|
|
||||||
if [[ "$workflow_count" -eq 0 ]]; then
|
|
||||||
NO_CI_REPOS+=("$repo")
|
|
||||||
echo -e " ${YELLOW}⚠ No CI workflows configured${NC}"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check recent CI runs (exclude approval gates)
|
|
||||||
recent_failures=$(gh run list --repo "$ORG/$repo" --limit 10 \
|
|
||||||
--json status,conclusion,name \
|
|
||||||
| jq -r '.[] | select(.conclusion == "failure") | select(.name | contains("CI") or contains("E2E") or contains("ci") or contains("e2e")) | .conclusion' \
|
|
||||||
| wc -l)
|
|
||||||
|
|
||||||
if [[ "$recent_failures" -ge "$CRITICAL_THRESHOLD" ]]; then
|
|
||||||
FAILURES+=("$repo: $recent_failures recent CI/E2E failures")
|
|
||||||
echo -e " ${RED}✗ $recent_failures recent CI/E2E failures${NC}"
|
|
||||||
else
|
|
||||||
echo -e " ${GREEN}✓ CI healthy${NC}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
echo ""
|
|
||||||
echo "=== Summary ==="
|
|
||||||
|
|
||||||
if [[ ${#FAILURES[@]} -eq 0 && ${#STALE_REPOS[@]} -eq 0 && ${#NO_CI_REPOS[@]} -eq 0 ]]; then
|
|
||||||
echo -e "${GREEN}All systems healthy!${NC}"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
if [[ ${#FAILURES[@]} -gt 0 ]]; then
|
|
||||||
echo -e "${RED}CI Failures:${NC}"
|
|
||||||
for failure in "${FAILURES[@]}"; do
|
|
||||||
echo " - $failure"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#STALE_REPOS[@]} -gt 0 ]]; then
|
|
||||||
echo -e "${YELLOW}Stale Repos (no updates in $MAX_AGE_DAYS+ days):${NC}"
|
|
||||||
for stale in "${STALE_REPOS[@]}"; do
|
|
||||||
echo " - $stale"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#NO_CI_REPOS[@]} -gt 0 ]]; then
|
|
||||||
echo -e "${YELLOW}Repos without CI:${NC}"
|
|
||||||
for no_ci in "${NO_CI_REPOS[@]}"; do
|
|
||||||
echo " - $no_ci"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,67 +1,62 @@
|
|||||||
---
|
---
|
||||||
name: coding-standards
|
name: coding-standards
|
||||||
description: >
|
description: >
|
||||||
Coding standards for Privileged Escalation. Covers Headlamp plugin
|
Engineering quality bar for GroomBook code: priority ordering of correctness
|
||||||
development workflow, registration API, shared libraries, versioning,
|
vs. clarity vs. maintainability vs. performance vs. elegance, PR and test
|
||||||
dependency management, container registry, and distribution policy.
|
requirements, no-hardcoded-values rules, branch discipline, and the no-self-
|
||||||
|
merge contract.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Coding Standards
|
# Coding Standards
|
||||||
|
|
||||||
## Headlamp Plugins
|
These rules apply to any GroomBook agent that writes, reviews, or merges code.
|
||||||
|
|
||||||
All plugins extend [Headlamp](https://headlamp.dev/docs/latest/development/plugins/getting-started), a Kubernetes dashboard with a plugin system.
|
## Priority ordering
|
||||||
|
|
||||||
- **Language:** TypeScript + React 18, MUI v5
|
When making technical decisions, prioritize in this order:
|
||||||
- **Scaffolding:** `npx --yes @kinvolk/headlamp-plugin create <plugin-name>`
|
|
||||||
- **Entry point:** `src/index.tsx`
|
|
||||||
- **Linting:** ESLint via `@headlamp-k8s/eslint-config` + Prettier
|
|
||||||
- **Testing:** Vitest + React Testing Library
|
|
||||||
|
|
||||||
### Plugin Commands
|
1. **Correctness** — does it work? Does it handle edge cases? Have you proven it, not assumed it?
|
||||||
|
2. **Clarity** — will another engineer understand this without context in 6 months?
|
||||||
|
3. **Maintainability** — will it be safe to change?
|
||||||
|
4. **Performance** — fast enough for the use case? Profile before optimizing.
|
||||||
|
5. **Elegance** — nice if free; never trade any of the above for it.
|
||||||
|
|
||||||
Run from the plugin directory:
|
## Pull request discipline
|
||||||
|
|
||||||
| Command | Purpose |
|
* All changes go through a PR. **Never push directly to `dev`, `uat`, or `main`.**
|
||||||
|---|---|
|
* No agent merges their own PR.
|
||||||
| `npm run start` | Dev mode with hot reload |
|
* Always include `cc @cpfarhood` at the bottom of the PR body for visibility (not as a reviewer).
|
||||||
| `npm run build` | Production build (`dist/main.js`) |
|
|
||||||
| `npm run format` | Prettier format |
|
|
||||||
| `npm run lint` | ESLint check |
|
|
||||||
| `npm run lint-fix` | ESLint auto-fix |
|
|
||||||
| `npm run tsc` | Typecheck |
|
|
||||||
| `npm run test` | Vitest tests |
|
|
||||||
|
|
||||||
### Registration API
|
## Test requirements
|
||||||
|
|
||||||
Import from `@kinvolk/headlamp-plugin/lib`:
|
* **Every PR must include tests** for new code paths. No exceptions for "small" changes.
|
||||||
|
* Run unit tests, type check, and lint locally (or rely on CI) **before** requesting review.
|
||||||
|
* A PR without passing tests does not get approval.
|
||||||
|
* New code paths require coverage. No coverage = no approval.
|
||||||
|
|
||||||
- `registerAppBarAction()` — add components to the nav bar
|
## Code review tone
|
||||||
- `registerRoute()` — create new pages
|
|
||||||
- `registerSidebarEntry()` — add sidebar items
|
|
||||||
- `registerDetailsViewSection()` — extend resource detail views
|
|
||||||
- `registerPluginSettings()` — add plugin configuration UI
|
|
||||||
|
|
||||||
### K8s API Access
|
Hold a high bar. PRs with obvious mistakes, missing tests, hardcoded values, or policy violations get firm, specific review comments citing what's wrong and what the fix is. Cite the file and line. Suggest the fix when you know it. Don't sugarcoat — but be professional and constructive. "This looks wrong" is not a review comment.
|
||||||
|
|
||||||
```typescript
|
## Hardcoded values
|
||||||
import { K8s } from '@kinvolk/headlamp-plugin/lib';
|
|
||||||
const [pods, error] = K8s.ResourceClasses.Pod.useList();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Shared Libraries
|
* **Colors** use CSS variables / theme tokens. Never raw hex in components.
|
||||||
|
* **Strings** use constants or i18n. No magic strings.
|
||||||
|
* **Numbers** that aren't trivially obvious go in named constants.
|
||||||
|
* **No magic numbers** in business logic.
|
||||||
|
|
||||||
These are provided by Headlamp at runtime — **do not bundle them**:
|
## Secrets in code
|
||||||
React, React Router, Redux, MUI, Lodash, Monaco Editor, Notistack, Iconify.
|
|
||||||
|
|
||||||
## Versioning & Distribution
|
Secrets never touch source. See the `safety` skill for the SealedSecrets workflow. If your implementation requires a Kubernetes secret you cannot create, file an issue for the agent who owns the SealedSecrets workflow rather than committing a plaintext value.
|
||||||
|
|
||||||
- **All releases use SemVer.** ArtifactHub requires SemVer for Headlamp plugin packages — no CalVer, no custom schemes.
|
## Releases and versioning
|
||||||
- **Plugin distribution is ArtifactHub only.** Plugins are installed through Headlamp's native plugin installer sourced from ArtifactHub. No Helm charts, install scripts, or custom install mechanisms.
|
|
||||||
- **Container images go to `ghcr.io` only.** Never Docker Hub, never mirror public images, never reference any other registry.
|
|
||||||
|
|
||||||
## Dependency Management
|
All releases use CalVer (`YYYY.MMDD.PATCH`, e.g. `2026.0504.0`). No SemVer, no custom schemes.
|
||||||
|
|
||||||
- **Dependency updates are owned by Mend Renovate.** Never enable Dependabot, never create `.github/dependabot.yml`, never reference Dependabot in workflows or docs.
|
## Container images
|
||||||
- **No package mirrors.** Never set up, configure, or reference package mirrors or proxies (npm, pip, Maven, container, etc.). Always use upstream registries directly.
|
|
||||||
- **Security scanning uses local tools.** Run `npm audit` or `pnpm audit` for vulnerability scanning. Do not use the GitHub vulnerability alerts API.
|
Push to `ghcr.io` only. Never Docker Hub for first-party images.
|
||||||
|
|
||||||
|
## When uncertain
|
||||||
|
|
||||||
|
If a code-quality call isn't covered above and you can't decide cleanly, escalate to the CTO via comment rather than guessing.
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
---
|
|
||||||
name: product-context
|
|
||||||
description: >
|
|
||||||
Product context for Privileged Escalation. Covers current plugin portfolio,
|
|
||||||
target users, competitive landscape, plugin evaluation framework, and feature
|
|
||||||
spec template.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Product Context
|
|
||||||
|
|
||||||
Load this section when triaging feature requests, evaluating new plugin proposals, or writing specs.
|
|
||||||
|
|
||||||
## Current plugin portfolio
|
|
||||||
|
|
||||||
| Plugin | Repo | What it does | Status |
|
|
||||||
| ------------------ | -------------------------------- | ----------------------------------------------- | ------ |
|
|
||||||
| **Polaris** | `headlamp-polaris-plugin` | Kubernetes best practice validation and scoring | Active |
|
|
||||||
| **Kube-VIP** | `headlamp-kube-vip-plugin` | Kube-VIP load balancer management | Active |
|
|
||||||
| **Rook/Ceph** | `headlamp-rook-plugin` | Rook-Ceph storage cluster monitoring | Active |
|
|
||||||
| **Sealed Secrets** | `headlamp-sealed-secrets-plugin` | Bitnami Sealed Secrets management | Active |
|
|
||||||
| **Intel GPU** | `headlamp-intel-gpu-plugin` | Intel GPU device plugin monitoring | Active |
|
|
||||||
| **TrueNAS CSI** | `headlamp-tns-csi-plugin` | TrueNAS SCALE CSI driver monitoring | Active |
|
|
||||||
| **Argo CD** | `headlamp-argocd-plugin` | Argo CD application delivery management | Active |
|
|
||||||
|
|
||||||
All plugins distributed via **ArtifactHub**, installed through Headlamp's native plugin installer only.
|
|
||||||
|
|
||||||
## Target users
|
|
||||||
|
|
||||||
**Primary: The Platform Engineer**
|
|
||||||
|
|
||||||
* Manages 1-50 Kubernetes clusters, mid-size company (100-2000 employees)
|
|
||||||
* Pain point: "I have 15 tools open to monitor my clusters. I want one dashboard that shows me everything."
|
|
||||||
* Very high tech comfort. Knows Kubernetes deeply. Will read your source code.
|
|
||||||
* Will adopt a plugin in 5 minutes if it solves a real problem. Will drop it in 5 seconds if it's buggy or doesn't add value over `kubectl`.
|
|
||||||
|
|
||||||
**Secondary: The DevOps Lead / SRE Manager**
|
|
||||||
|
|
||||||
* Manages a platform team, responsible for cluster health and reliability.
|
|
||||||
* Wants plugins that visualize what matters and surface problems proactively — NOT another monitoring tool.
|
|
||||||
|
|
||||||
**Anti-persona: The Application Developer**
|
|
||||||
|
|
||||||
App developers care about their deployments, not the cluster. Features like "show me my pod logs" are already in Headlamp core. Don't build for them.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
**In scope**
|
|
||||||
|
|
||||||
* Headlamp plugins that visualize and manage specific Kubernetes ecosystem tools
|
|
||||||
* Plugins that surface operational insights not available in Headlamp core
|
|
||||||
* Plugins for CNCF projects and widely-adopted K8s ecosystem tools
|
|
||||||
* ArtifactHub packaging and distribution
|
|
||||||
|
|
||||||
**Explicitly out of scope**
|
|
||||||
|
|
||||||
* Plugins that duplicate Headlamp core functionality
|
|
||||||
* Non-Kubernetes tools
|
|
||||||
* Hosted/SaaS versions of plugins
|
|
||||||
* Helm-based or sidecar-based plugin installation
|
|
||||||
* Custom Headlamp forks
|
|
||||||
* Monitoring/alerting backends (we visualize, we don't collect metrics)
|
|
||||||
* Multi-cluster management
|
|
||||||
* CLI tools
|
|
||||||
|
|
||||||
## Competitive landscape
|
|
||||||
|
|
||||||
| Competitor | Where PRI differs |
|
|
||||||
| -------------------------------- | ----------------------------------------------------------------------------------- |
|
|
||||||
| **Headlamp core** | We extend it, not compete. If a feature belongs in core, contribute upstream. |
|
|
||||||
| **Lens** | Heavy, desktop-only, commercial. We make web-based, open source Headlamp better. |
|
|
||||||
| **k9s** | Different modality (TUI vs web). Not competitive. |
|
|
||||||
| **Komodor / Kubecost / Robusta** | Standalone products. Our plugins bring their insights INTO Headlamp. Complementary. |
|
|
||||||
|
|
||||||
PRI's moat: leading third-party Headlamp plugin developer. Plugins are free, open source, on ArtifactHub.
|
|
||||||
|
|
||||||
## Plugin evaluation framework
|
|
||||||
|
|
||||||
1. **Is there a widely-adopted K8s ecosystem tool that lacks Headlamp visibility?**
|
|
||||||
* Fewer than 1,000 GitHub stars or in alpha → too early. Close with "revisit when more mature."
|
|
||||||
* Already has a Headlamp plugin → duplicate. Close.
|
|
||||||
2. **Does the plugin add value over `kubectl` + the tool's own CLI/UI?**
|
|
||||||
* "It shows the same thing but in Headlamp" → weak value prop. Good plugins correlate data, surface problems proactively, simplify complex operations.
|
|
||||||
3. **Can Gandalf build and maintain it?**
|
|
||||||
* One engineer can maintain ~6-8 plugins at current complexity. We're at 7 now. New plugins mean either dropping an existing one or hiring.
|
|
||||||
4. **Is it installable via ArtifactHub without extras?**
|
|
||||||
* Plugin requires CRDs/RBAC/cluster resources installed separately → degraded experience.
|
|
||||||
* Unacceptable: plugin requires its own operator or sidecar.
|
|
||||||
|
|
||||||
**Priority tiers**
|
|
||||||
|
|
||||||
* **P0**: Bugs in existing plugins that break functionality or produce incorrect data
|
|
||||||
* **P1**: Enhancements to existing plugins users are requesting
|
|
||||||
* **P2**: New plugins for high-value K8s tools with clear user demand
|
|
||||||
* **P3**: Speculative plugins, cross-plugin features, UX experiments
|
|
||||||
|
|
||||||
## Feature spec template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Problem
|
|
||||||
What operational visibility or capability is missing? Who needs it? What do they do today instead?
|
|
||||||
|
|
||||||
## Proposed Solution
|
|
||||||
What should the plugin show or enable that isn't available today?
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
- [ ] Plugin displays...
|
|
||||||
- [ ] User can...
|
|
||||||
- [ ] Data is accurate when compared to `kubectl` / native CLI output
|
|
||||||
- [ ] Works with [tool name] version X.Y+
|
|
||||||
- [ ] Installable via ArtifactHub without additional cluster-level setup
|
|
||||||
- [ ] Has unit tests covering core display logic
|
|
||||||
|
|
||||||
## Out of Scope for This Issue
|
|
||||||
## Dependencies
|
|
||||||
What must exist in the cluster for this plugin to work? (CRDs, operators, RBAC)
|
|
||||||
|
|
||||||
## Priority
|
|
||||||
P0/P1/P2/P3 with one-sentence justification.
|
|
||||||
```
|
|
||||||
+14
-21
@@ -1,38 +1,31 @@
|
|||||||
---
|
---
|
||||||
name: safety
|
name: safety
|
||||||
description: >
|
description: >
|
||||||
Non-negotiable safety rules for all agents at Privileged Escalation. Covers
|
Non-negotiable safety rules for all GroomBook agents. Covers secret handling,
|
||||||
secret handling, destructive command restrictions, sealed-secrets workflow,
|
destructive-action gating, the SealedSecrets workflow, kubectl scope limits,
|
||||||
anti-impersonation rules, role-boundary rules for GitHub actions, and
|
and the escalation protocol when an action's safety is uncertain.
|
||||||
escalation protocol when uncertain.
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Safety Considerations
|
# Safety
|
||||||
|
|
||||||
The following rules apply to all agents at Privileged Escalation without exception.
|
The following rules apply to every GroomBook agent without exception.
|
||||||
|
|
||||||
## Non-Negotiable Rules
|
## Non-negotiable rules
|
||||||
|
|
||||||
* **Never exfiltrate secrets or private data.** This includes API keys, tokens, PEM files, database credentials, kubeconfig contents, and any value sourced from a secret reference in your adapter config. Do not log, comment, or return these values in any output.
|
* **Never exfiltrate secrets or private data.** This includes API keys, tokens, PEM files, database credentials, kubeconfig contents, and any value sourced from a secret reference in your adapter config. Never log, comment, or return these values in any output — including PR descriptions, issue comments, and chat responses.
|
||||||
|
|
||||||
* **Seek Board Approval for Destructive Actions.** Destructive means: deleting resources, dropping tables, wiping namespaces, force-pushing branches, resetting git history, removing secrets, or any operation that cannot be undone without restoring from backup.
|
* **Seek board approval before destructive actions.** "Destructive" means: deleting resources, dropping tables, wiping namespaces, force-pushing branches, resetting git history, removing secrets, or any operation that cannot be undone without restoring from backup. Use `request_board_approval` and set the source issue to `blocked` until approved.
|
||||||
|
|
||||||
* **No plaintext secrets in any repository.** Kubernetes secrets go through Bitnami Sealed Secrets (`kubeseal`). Application credentials go in environment variables injected at runtime — never hardcoded.
|
* **Never commit plaintext secrets.** Kubernetes secrets go through Bitnami Sealed Secrets (`kubeseal`). Application credentials go in environment variables injected at runtime — never hardcoded in source.
|
||||||
|
|
||||||
* **Do not use `kubectl create` in production.**
|
* **Never `kubectl apply` against production (`groombook`).** The production namespace is Flux-managed. Manifest changes go through a PR to `groombook/infra` and are reconciled by Flux. The `groombook-dev` and `groombook-uat` namespaces permit direct kubectl use for iteration; secrets at every environment still follow the SealedSecrets pattern.
|
||||||
The `privilegedescalation` namespace is Flux-managed. Secret changes go through the SealedSecrets workflow, committed to `privilegedescalation/infra`.
|
|
||||||
|
|
||||||
* **Never impersonate another agent or human.** Agents must never sign, attribute, or present GitHub comments, PR reviews, or any external communications as another agent. Every comment must accurately identify the authoring agent. Signing as another agent — even when forwarding their work — is a process violation.
|
* **Never `kubectl create secret` in production.** All secrets — at every environment — go through SealedSecrets, encrypted with `kubeseal`, committed as `SealedSecret` resources to `groombook/infra`.
|
||||||
|
|
||||||
* **Post GitHub comments only within your defined SDLC role.** An agent must not post a review type that belongs to another role, even if that role's agent has not yet completed its review:
|
* **Never bypass the merge gate.** No self-merging PRs. No pushing directly to `dev`, `uat`, or `main`. Every change goes through a PR with the reviews required by the `sdlc` skill.
|
||||||
- **Engineer bot** posts: implementation comments, CI results
|
|
||||||
- **QA bot** posts: QA reviews
|
|
||||||
- **UAT bot** posts: UAT reviews
|
|
||||||
- **CTO bot** posts: CTO reviews and approvals
|
|
||||||
- **CEO bot** posts: merge confirmations only
|
|
||||||
|
|
||||||
* **Never change another agent's model configuration.** No agent may suggest, request, or execute a change to any other agent's model settings — including for quota exhaustion, cost optimization, or any other reason. Quota issues must be escalated to the board. This is a non-negotiable board directive.
|
* **Never run `tofu` directly.** Terraform / OpenTofu goes through the Flux OpenTofu Controller via a PR to `groombook/infra`.
|
||||||
|
|
||||||
## If you are unsure
|
## If you are unsure
|
||||||
|
|
||||||
If you are unsure whether an action is safe, stop. Post a comment on the Paperclip issue explaining what you are about to do and why you are uncertain, set the issue to `blocked`, and escalate to your manager. Do not guess.
|
If you are unsure whether an action is safe, **stop**. Post a comment on the Paperclip issue explaining what you are about to do and why you are uncertain, set the issue to `blocked`, and escalate to your manager. Do not guess.
|
||||||
|
|||||||
+163
-115
@@ -1,181 +1,229 @@
|
|||||||
---
|
---
|
||||||
name: sdlc
|
name: sdlc
|
||||||
description: >
|
description: >
|
||||||
Software development lifecycle rules for Privileged Escalation. Covers GitHub
|
Software development lifecycle for GroomBook. Covers Gitea authentication,
|
||||||
issue approval gates, authentication, branch strategy, PR review policy,
|
branch strategy across Dev/UAT/Prod, the four-phase SDLC pipeline with
|
||||||
pipeline stages, CI/CD, and security review.
|
product analysis intake, PR review and merge policy, the handoff protocol,
|
||||||
|
status semantics, infrastructure layout, the canonical tools list, the
|
||||||
|
Gitea-origin issue board-approval gate, the cc-cpfarhood visibility rule,
|
||||||
|
the scheduled penetration testing program, and delegation model tier policy.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Software Development Lifecycle
|
# Software Development Lifecycle
|
||||||
|
|
||||||
## GitHub Authentication
|
## Gitea authentication
|
||||||
|
|
||||||
Access to GitHub is done via token in your env **Never** run `gh auth login` directly — it hangs headless agents.
|
**Use the `tea` CLI** with the `GITEA_TOKEN` environment variable for all Gitea operations. Configure it once:
|
||||||
|
|
||||||
## GitHub Issues — Board Approval Required
|
```bash
|
||||||
|
tea login add --url https://git.farh.net --token $GITEA_TOKEN --name groombook
|
||||||
|
```
|
||||||
|
|
||||||
**If a task originated from GitHub (`originKind: "github"` in the issue data), do not begin any work.** Immediately create a `request_board_approval`:
|
Gitea is the **primary source of truth**. Every Paperclip issue should have a corresponding Gitea issue (create one if missing). Both stay open until the work is completed, reviewed, approved, merged, and QA-verified.
|
||||||
|
|
||||||
```json
|
## Gitea-origin issue policy — board approval required
|
||||||
|
|
||||||
|
If a task originated from Gitea (`originKind: "gitea"`), **do not begin work**. Immediately create a board approval:
|
||||||
|
|
||||||
|
```
|
||||||
POST /api/companies/{companyId}/approvals
|
POST /api/companies/{companyId}/approvals
|
||||||
{
|
{
|
||||||
"type": "request_board_approval",
|
"type": "request_board_approval",
|
||||||
"requestedByAgentId": "{your-agent-id}",
|
"requestedByAgentId": "{your-agent-id}",
|
||||||
"issueIds": ["{issue-id}"],
|
"issueIds": ["{issueId}"],
|
||||||
"payload": {
|
"payload": {
|
||||||
"title": "Board approval required: GitHub issue",
|
"title": "Board approval required: Gitea issue",
|
||||||
"summary": "Summarize what the GitHub issue requests.",
|
"summary": "Summarize what the Gitea issue requests.",
|
||||||
"recommendedAction": "Approve to begin work.",
|
"recommendedAction": "Approve to begin work.",
|
||||||
"risks": ["Work begins without board review if approved."]
|
"risks": ["Work begins without board review if approved."]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Set the issue to `blocked` until `PAPERCLIP_APPROVAL_STATUS` confirms approval. Only proceed once approved.
|
Set the issue to `blocked` with a comment linking to the approval. Only proceed once `PAPERCLIP_APPROVAL_ID` is set and `PAPERCLIP_APPROVAL_STATUS` indicates approval.
|
||||||
|
|
||||||
## Branch Strategy
|
## Branch strategy
|
||||||
|
|
||||||
All plugin repositories use three long-lived branches representing a promotion chain:
|
Three long-lived branches map to the three deployment environments:
|
||||||
|
|
||||||
| Branch | Environment | Owner | Who merges to it |
|
| Branch | Environment | Who merges |
|
||||||
|--------|-------------|-------|-----------------|
|
|--------|-------------|-----------|
|
||||||
| `dev` | Development | Engineer | Engineer self-merges after CI passes |
|
| `dev` | Dev | CTO (after QA approval) |
|
||||||
| `uat` | User Acceptance Testing | QA (Regression Regina) | QA merges after code review |
|
| `uat` | UAT | CTO (promotes `dev` → `uat`) |
|
||||||
| `main` | Production | UAT (Pixel Patty) | UAT merges after browser validation |
|
| `main` | Production | CEO (promotes `uat` → `main`) |
|
||||||
|
|
||||||
**Engineers target `dev` via feature branches** — never push directly to any long-lived branch.
|
**Engineers always target `dev`** — never `uat` or `main` directly. Feature branches: `<agent-name>/<short-description>`.
|
||||||
|
|
||||||
Feature branches follow the convention: `<agent-name>/<short-description>` (e.g., `gandalf/add-sealed-secrets-list`).
|
## Pull requests
|
||||||
|
|
||||||
## Pull Requests
|
All changes happen via pull request. Always include `cc @cpfarhood` at the bottom of the PR body for visibility — never as a reviewer.
|
||||||
|
|
||||||
All changes must happen via pull request. Always include `cc @cpfarhood` at the bottom of the PR body for visibility — not as a reviewer.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gh pr create --title "..." --body "... cc @cpfarhood"
|
tea pr create --base dev --title "..." --body "... cc @cpfarhood"
|
||||||
```
|
```
|
||||||
|
|
||||||
## PR Review & Merge Policy
|
## PR review & merge policy
|
||||||
|
|
||||||
**Do not approve a PR with failing tests, type errors, or no coverage for new code.**
|
### Dev branch (`dev`)
|
||||||
|
|
||||||
### Promotion chain
|
- **QA** (Lint Roller) reviews the PR. Approve → hand to CTO. Fail → back to engineer directly with exact details.
|
||||||
|
- **CTO** (The Dogfather) reviews. Approve → CTO merges the `dev` PR. Fail → back to engineer.
|
||||||
|
|
||||||
Each promotion is a PR reviewed and merged by its gate owner:
|
### UAT branch (`uat`)
|
||||||
|
|
||||||
1. **feature → dev** — Engineer self-merges after CI passes. No review required. Dev is for validation, not quality gates.
|
- **CTO** opens and merges a `dev` → `uat` PR.
|
||||||
2. **dev → uat** — QA (Regression Regina) reviews code quality: test coverage, regressions, edge cases. QA merges to `uat` after approval.
|
|
||||||
3. **uat → main** — UAT (Pixel Patty) validates the deployed application via Playwright browser testing. UAT merges to `main` after validation passes. For detailed UAT testing procedures, see the `uat` company skill.
|
|
||||||
|
|
||||||
**Each gate owner has merge authority.** No separate merge step by another role. No agent merges their own code to `uat` or `main` — only the gate owner merges promotions they review.
|
### Main branch (`main`)
|
||||||
|
|
||||||
## Pipeline
|
- **CEO** (Scrubs McBarkley) reviews and merges the `uat` → `main` PR.
|
||||||
|
|
||||||
### Pipeline A: Plugin/Feature Changes
|
`@cpfarhood` is cc'd for visibility on all PRs — never as a reviewer.
|
||||||
|
|
||||||
```text
|
## SDLC pipeline
|
||||||
Engineer → PR to dev → self-merge → deploys to dev
|
|
||||||
→ Engineer validates on dev
|
### Phase 0 — Product analysis (feature intake)
|
||||||
→ PR from dev → uat → QA reviews → QA merges
|
|
||||||
→ Deploys to UAT environment
|
* Feature requests arrive at the CEO via Paperclip or Gitea Issues.
|
||||||
→ PR from uat → main → UAT validates → UAT merges
|
* CEO delegates to CMPO (Pawla Abdul) for review.
|
||||||
→ Production
|
* CMPO returns one of three decisions:
|
||||||
|
* **Accepted** → CEO routes to CTO for work breakdown.
|
||||||
|
* **Backlogged** → CEO handles prioritization.
|
||||||
|
* **Denied** → CEO closes as unplanned.
|
||||||
|
* CTO breaks accepted work into atomic tasks and assigns to Engineering.
|
||||||
|
|
||||||
|
### Phase 1 — Dev
|
||||||
|
|
||||||
|
1. **Engineer** (Flea Flicker) branches from `dev`, writes code. GitOps deploys to dev on demand.
|
||||||
|
2. **Engineer** opens a PR against `dev`. CI must pass.
|
||||||
|
3. **QA (Lint Roller)** reviews the PR. Fail → back to engineer.
|
||||||
|
4. QA approves and hands off to CTO.
|
||||||
|
5. **CTO (The Dogfather)** reviews the PR. Fail → back to engineer.
|
||||||
|
6. **CTO** merges the dev PR.
|
||||||
|
7. **CI** builds and deploys automatically to Dev (`https://dev.groombook.dev`).
|
||||||
|
|
||||||
|
### Phase 2 — UAT promotion
|
||||||
|
|
||||||
|
8. **CTO** opens and merges a PR from `dev` to `uat`.
|
||||||
|
9. **CI** builds and deploys automatically to UAT (`https://uat.groombook.dev`).
|
||||||
|
10. **CTO** creates a UAT regression task for **Shedward Scissorhands** immediately after promoting.
|
||||||
|
|
||||||
|
### Phase 3 — UAT testing & security
|
||||||
|
|
||||||
|
11. **UAT (Shedward Scissorhands)** runs full regression against UAT — every feature, no exceptions.
|
||||||
|
12. UAT fail → CTO redistributes to engineer (return to Phase 1).
|
||||||
|
13. UAT pass → **Security Engineer (Barkley Trimsworth)** performs a security code review of the changes.
|
||||||
|
14. Security fail → CTO redistributes to engineer (return to Phase 1).
|
||||||
|
|
||||||
|
### Phase 4 — Production
|
||||||
|
|
||||||
|
15. Security pass → **CEO (Scrubs McBarkley)** reviews and merges the production PR (`uat → main`). Fail → back to CTO.
|
||||||
|
16. **CI** deploys automatically to Production (`https://demo.groombook.dev`).
|
||||||
|
|
||||||
|
### Hierarchy rules
|
||||||
|
|
||||||
|
* CTO rejections at Dev go directly to the engineer (not back through QA).
|
||||||
|
* UAT failures (Shedward) go to CTO — CTO cascades to engineer.
|
||||||
|
* Security failures (Barkley) go to CTO — CTO cascades to engineer.
|
||||||
|
* CEO rejections at Prod go to CTO.
|
||||||
|
|
||||||
|
> **Penetration testing.** Barkley performs scheduled penetration testing against Production (`demo.groombook.dev`) and Demo independently of the PR workflow. Board-authorized; not triggered per-PR. Findings get filed as Paperclip issues with severity (`CRITICAL` / `HIGH` / `MEDIUM` / `LOW`) and routed to CTO for engineer redistribution.
|
||||||
|
|
||||||
|
## Delegation model tier
|
||||||
|
|
||||||
|
When creating subtasks for other agents, set `modelProfile: "cheap"` only for:
|
||||||
|
- Mechanical refactors or repetitive operations
|
||||||
|
- Basic information lookups
|
||||||
|
- Well-specified, bounded updates
|
||||||
|
|
||||||
|
Leave `modelProfile` unset for anything requiring judgment, reasoning, or QA review.
|
||||||
|
|
||||||
|
When in doubt, leave it unset.
|
||||||
|
|
||||||
|
## Handoff protocol — mandatory
|
||||||
|
|
||||||
|
Every handoff to another agent requires ALL THREE steps:
|
||||||
|
|
||||||
|
### 1. Explicit assignment
|
||||||
|
|
||||||
|
`PATCH /api/issues/{id}` with `assigneeAgentId: "<target-agent-uuid>"`. Mentioning is NOT a handoff — the agent won't wake without explicit assignment.
|
||||||
|
|
||||||
|
### 2. Status = `todo`
|
||||||
|
|
||||||
|
Every handoff sets `status: "todo"`. Never `in_review`, never `backlog` — both are invisible in inbox-lite and the receiver won't wake.
|
||||||
|
|
||||||
|
### 3. Release checkout
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/issues/{issueId}/release
|
||||||
|
Headers: Authorization: Bearer $PAPERCLIP_API_KEY, X-Paperclip-Run-Id: $PAPERCLIP_RUN_ID
|
||||||
```
|
```
|
||||||
|
|
||||||
Applies to changes in `headlamp-*-plugin/` repos (plugin code, features, bug fixes).
|
Without this release, the receiving agent cannot check out the issue.
|
||||||
|
|
||||||
**UAT_PLAYBOOK.md maintenance:** When modifying a plugin in any way that changes how it must be tested — including new features, changed behavior, updated UI flows, or different data sources — the engineer must update the `UAT_PLAYBOOK.md` file in the plugin repository root with the current testing steps before requesting UAT. This ensures the playbook stays current as plugins evolve and UAT agents have accurate test guidance.
|
**Saying you are reassigning a task is NOT the same as reassigning it.** Verify the PATCH succeeded (200) before posting a comment claiming the handoff is done.
|
||||||
|
|
||||||
### Pipeline B: Infrastructure Changes (No UI Impact)
|
## Infrastructure
|
||||||
|
|
||||||
```text
|
* **Production / Demo:** namespace `groombook`, FQDN `demo.groombook.dev`
|
||||||
Engineer → PR to main → CI passes → QA reviews → QA merges
|
* **UAT:** namespace `groombook-uat`, FQDN `uat.groombook.dev`
|
||||||
→ Production
|
* **Dev:** namespace `groombook-dev`, FQDN `dev.groombook.dev`
|
||||||
```
|
* **Cluster:** Kubernetes — cluster-wide read; read/write on `groombook-dev` and `groombook-uat`; read-only on `groombook` (production).
|
||||||
|
* **Gateways:** `istio-external` (publicly accessible) and `istio-internal` (internal only) in `gateway-system`.
|
||||||
|
* **Container registry:** `ghcr.io/groombook/<service>` only.
|
||||||
|
|
||||||
Applies to changes in `.github/workflows/`, `infra/`, `org/` repos, and template repos. No UAT stage needed — infrastructure changes have no UI to validate.
|
## Authentication
|
||||||
|
|
||||||
**Detection:** If `git diff` shows changes only in `.github/`, `infra/`, `org/`, or deployment files → Pipeline B. If any `headlamp-*-plugin/` code changed → Pipeline A.
|
* **Framework:** Better-Auth.
|
||||||
|
* **Social login:** Google and Apple OAuth.
|
||||||
|
* **SSO:** Authentik OIDC at `https://auth.farh.net` (credentials in `authentik-credentials` secret).
|
||||||
|
* **Never build custom authentication.**
|
||||||
|
|
||||||
**Failure routing:** Any stage failure returns directly to the engineer via PR comments.
|
## Deployment — 2-stage Flux GitOps
|
||||||
|
|
||||||
## Issue Reviewers and Approvers
|
**Stage 1 — CI (Gitea Actions, uses GitHub Actions-compatible YAML syntax, runs in each application repo):**
|
||||||
|
- Triggered automatically on every merge to `main`
|
||||||
|
- Builds and tags the Docker image
|
||||||
|
- Pushes tagged images to `ghcr.io/groombook/<service>`
|
||||||
|
|
||||||
Every Paperclip issue has **Reviewers** and **Approvers** fields visible in the UI sidebar. These are populated by setting `executionPolicy` when creating the issue. Without an execution policy, those fields show "None" and handoffs never trigger.
|
**Stage 2 — GitOps (Flux, managed externally):**
|
||||||
|
- Flux watches `groombook/infra` as the **target** GitRepository — it is **not** a Flux bootstrap/cluster repo.
|
||||||
|
- Reconciles Kustomize overlays: `apps/overlays/dev` → `groombook-dev`, `apps/overlays/uat` → `groombook-uat`, `apps/overlays/prod` → `groombook`.
|
||||||
|
|
||||||
**All stage and participant `id` fields must be random UUIDs.** Generate them at issue-creation time (e.g. via `uuidgen` or your language's UUID library). Do not use descriptive strings — the API rejects non-UUID values.
|
**Policy — Flux Image Tag Automation is DENIED.** Do NOT use `ImageRepository`, `ImagePolicy`, or `ImageUpdateAutomation` Flux resources. Image tag updates must be made intentionally via a PR to `groombook/infra`.
|
||||||
|
|
||||||
### Pipeline A — set reviewers on issue creation
|
**To deploy a change:**
|
||||||
|
1. Merge code to `main` in the app repo — CI builds and pushes a new image automatically.
|
||||||
For plugin/feature work (Pipeline A), set a two-stage execution policy so QA and UAT appear as reviewers:
|
2. Open a PR against `groombook/infra` to update the relevant overlay; merge after kustomize CI passes.
|
||||||
|
3. Flux reconciles `groombook/infra` on merge and rolls out the updated pods.
|
||||||
|
|
||||||
|
**To force a rollout** (pick up new `:latest` on stuck pods):
|
||||||
```bash
|
```bash
|
||||||
QA_STAGE_ID=$(uuidgen)
|
kubectl rollout restart deployment/<name> -n <namespace>
|
||||||
QA_PART_ID=$(uuidgen)
|
|
||||||
UAT_STAGE_ID=$(uuidgen)
|
|
||||||
UAT_PART_ID=$(uuidgen)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```json
|
## Infrastructure as Code
|
||||||
"executionPolicy": {
|
|
||||||
"mode": "normal",
|
|
||||||
"commentRequired": true,
|
|
||||||
"stages": [
|
|
||||||
{
|
|
||||||
"id": "<QA_STAGE_ID>",
|
|
||||||
"type": "review",
|
|
||||||
"approvalsNeeded": 1,
|
|
||||||
"participants": [
|
|
||||||
{ "id": "<QA_PART_ID>", "type": "agent", "agentId": "fd5dbec8-ddbb-4b57-9703-624e0ed90053" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "<UAT_STAGE_ID>",
|
|
||||||
"type": "review",
|
|
||||||
"approvalsNeeded": 1,
|
|
||||||
"participants": [
|
|
||||||
{ "id": "<UAT_PART_ID>", "type": "agent", "agentId": "01ec02f7-70c2-4fa1-ac3f-2545f1237ac3" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
- Stage 1 reviewer: Regression Regina (`fd5dbec8-ddbb-4b57-9703-624e0ed90053`)
|
Terraform / OpenTofu is deployed via the **Flux OpenTofu Controller** in a GitOps fashion. Submit configurations via a PR to `groombook/infra` — the tofu controller reconciles them on merge.
|
||||||
- Stage 2 reviewer: Pixel Patty (`01ec02f7-70c2-4fa1-ac3f-2545f1237ac3`)
|
|
||||||
|
|
||||||
### Pipeline B — single reviewer
|
**Never run `tofu` directly.** Never `kubectl apply` against production. Production changes go through Flux only.
|
||||||
|
|
||||||
For infrastructure changes (Pipeline B), use one QA review stage:
|
## Tools (canonical, not alternatives)
|
||||||
|
|
||||||
```json
|
These are the only acceptable choices — alternatives are policy violations:
|
||||||
"executionPolicy": {
|
|
||||||
"mode": "normal",
|
|
||||||
"commentRequired": true,
|
|
||||||
"stages": [
|
|
||||||
{
|
|
||||||
"id": "<QA_STAGE_ID>",
|
|
||||||
"type": "review",
|
|
||||||
"approvalsNeeded": 1,
|
|
||||||
"participants": [
|
|
||||||
{ "id": "<QA_PART_ID>", "type": "agent", "agentId": "fd5dbec8-ddbb-4b57-9703-624e0ed90053" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Triggering the handoff
|
* **Secret management:** Bitnami Sealed Secrets Controller — no plain Kubernetes secrets.
|
||||||
|
* **Database:** CloudNativePG Operator (Postgres) — no SQLite, MariaDB, or MySQL.
|
||||||
|
* **Cache / pub-sub:** DragonflyDB Operator — no Redis.
|
||||||
|
* **Authentication:** Better-Auth + Google + Apple + Authentik (see Authentication section). Never build custom auth.
|
||||||
|
* **Dependency updates:** Mend Renovate. **Dependabot is not used and will not be used.**
|
||||||
|
* **Container registry:** `ghcr.io/groombook/<service>` — no Docker Hub for first-party images.
|
||||||
|
|
||||||
When an engineer completes work and merges to `dev`, set the Paperclip issue status to `in_review`. This activates the execution policy and wakes the first reviewer. Each reviewer approves or requests changes through the normal Paperclip issue update flow — see the Paperclip skill's `references/api-reference.md` for details.
|
If a task requires deviating from any of the above, treat it as a destructive action: stop, file an issue with rationale, request board approval.
|
||||||
|
|
||||||
## CI/CD
|
## External communication
|
||||||
|
|
||||||
- CI runs on self-hosted ARC runners: `runs-on: runners-privilegedescalation`
|
When communicating in any context visible outside the GroomBook agent team (external users, human reviewers, non-agent entities), include `cc @cpfarhood` for visibility — never as a reviewer.
|
||||||
- CI triggers on PRs to `dev`, `uat`, and `main` branches
|
|
||||||
- Engineers may modify `.github/workflows/` files directly via PR
|
|
||||||
- Runners scale to zero when idle and start automatically when a workflow triggers
|
|
||||||
|
|
||||||
## Security Review
|
## No self-merge
|
||||||
|
|
||||||
Security review is handled as part of the QA review stage. Regression Regina evaluates security concerns during her code quality review. There is no separate dedicated security review agent.
|
No agent merges their own PR. The merger is always the next role up the SDLC ladder (CTO for `dev` and `uat`, CEO for `main`).
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
# SDLC Pipeline Diagram
|
|
||||||
|
|
||||||
## Full Lifecycle
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
subgraph Origin["Task Origin"]
|
|
||||||
GH["GitHub Issue"]
|
|
||||||
PP["Paperclip Issue"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Approval["Board Gate"]
|
|
||||||
BA{"Board Approval<br/>Required?"}
|
|
||||||
REQ["Request Board Approval<br/>→ Issue blocked"]
|
|
||||||
APPROVED["Approved"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Detection["Pipeline Detection"]
|
|
||||||
DET{"Changed files?"}
|
|
||||||
PA["Pipeline A<br/>Plugin / Feature"]
|
|
||||||
PB["Pipeline B<br/>Infrastructure"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph PipelineA["Pipeline A: Plugin / Feature Changes"]
|
|
||||||
direction TB
|
|
||||||
A_ENG["Engineer writes code<br/>(Gandalf)"]
|
|
||||||
A_PR_DEV["PR → dev<br/>Engineer self-merges"]
|
|
||||||
A_CI_DEV{"CI Passes?"}
|
|
||||||
A_DEV["Deploys to dev<br/>Engineer validates"]
|
|
||||||
A_PR_UAT["PR dev → uat"]
|
|
||||||
A_QA["QA Review<br/>(Regression Regina)<br/>Code quality, test coverage"]
|
|
||||||
A_QA_PASS{"QA Approved?"}
|
|
||||||
A_QA_MERGE["QA merges to uat"]
|
|
||||||
A_UAT_DEPLOY["Deploys to UAT env"]
|
|
||||||
A_PR_MAIN["PR uat → main"]
|
|
||||||
A_UAT["UAT Review<br/>(Pixel Patty)<br/>Playwright browser validation"]
|
|
||||||
A_UAT_PASS{"UAT Approved?"}
|
|
||||||
A_UAT_MERGE["UAT merges to main"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph PipelineB["Pipeline B: Infrastructure Changes"]
|
|
||||||
direction TB
|
|
||||||
B_ENG["Engineer writes code<br/>(Gandalf / Hugh)"]
|
|
||||||
B_PR["PR → main"]
|
|
||||||
B_CI{"CI Passes?"}
|
|
||||||
B_QA["QA Review<br/>(Regression Regina)"]
|
|
||||||
B_QA_PASS{"QA Approved?"}
|
|
||||||
B_QA_MERGE["QA merges to main"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Result["Outcome"]
|
|
||||||
PROD["Merged to main<br/>✓ Production"]
|
|
||||||
RETURNED["Returned to Engineer<br/>Fix and resubmit"]
|
|
||||||
end
|
|
||||||
|
|
||||||
%% Origin routing
|
|
||||||
GH --> BA
|
|
||||||
PP --> DET
|
|
||||||
BA -->|"originKind: github"| REQ
|
|
||||||
REQ -->|"PAPERCLIP_APPROVAL_STATUS"| APPROVED
|
|
||||||
BA -->|"originKind: other"| DET
|
|
||||||
APPROVED --> DET
|
|
||||||
|
|
||||||
%% Pipeline detection
|
|
||||||
DET -->|"headlamp-*-plugin/ code"| PA
|
|
||||||
DET -->|".github/, infra/, org/"| PB
|
|
||||||
|
|
||||||
%% Pipeline A flow
|
|
||||||
PA --> A_ENG --> A_PR_DEV --> A_CI_DEV
|
|
||||||
A_CI_DEV -->|"Pass"| A_DEV
|
|
||||||
A_CI_DEV -->|"Fail"| RETURNED
|
|
||||||
A_DEV --> A_PR_UAT --> A_QA --> A_QA_PASS
|
|
||||||
A_QA_PASS -->|"Approved"| A_QA_MERGE --> A_UAT_DEPLOY
|
|
||||||
A_QA_PASS -->|"Changes requested"| RETURNED
|
|
||||||
A_UAT_DEPLOY --> A_PR_MAIN --> A_UAT --> A_UAT_PASS
|
|
||||||
A_UAT_PASS -->|"Approved"| A_UAT_MERGE --> PROD
|
|
||||||
A_UAT_PASS -->|"Changes requested"| RETURNED
|
|
||||||
|
|
||||||
%% Pipeline B flow
|
|
||||||
PB --> B_ENG --> B_PR --> B_CI
|
|
||||||
B_CI -->|"Pass"| B_QA --> B_QA_PASS
|
|
||||||
B_CI -->|"Fail"| RETURNED
|
|
||||||
B_QA_PASS -->|"Approved"| B_QA_MERGE --> PROD
|
|
||||||
B_QA_PASS -->|"Changes requested"| RETURNED
|
|
||||||
|
|
||||||
RETURNED -->|"Fix and resubmit"| A_PR_DEV
|
|
||||||
RETURNED -->|"Fix and resubmit"| B_PR
|
|
||||||
|
|
||||||
%% Styling
|
|
||||||
classDef gate fill:#f9e4e4,stroke:#c0392b,color:#000
|
|
||||||
classDef pass fill:#e4f9e4,stroke:#27ae60,color:#000
|
|
||||||
classDef agent fill:#e4e9f9,stroke:#2980b9,color:#000
|
|
||||||
classDef decision fill:#fef9e7,stroke:#f39c12,color:#000
|
|
||||||
classDef deploy fill:#e8f4f8,stroke:#2c3e50,color:#000
|
|
||||||
|
|
||||||
class BA,A_CI_DEV,A_QA_PASS,A_UAT_PASS,B_CI,B_QA_PASS,DET decision
|
|
||||||
class A_QA,A_UAT,B_QA gate
|
|
||||||
class PROD pass
|
|
||||||
class A_ENG,B_ENG agent
|
|
||||||
class A_DEV,A_UAT_DEPLOY deploy
|
|
||||||
```
|
|
||||||
|
|
||||||
## Branch Promotion Chain
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph Feature["Feature Branch"]
|
|
||||||
FB["gandalf/feature-name"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Dev["dev branch"]
|
|
||||||
DEV["Engineer self-merges<br/>Deploys to dev env"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph UAT["uat branch"]
|
|
||||||
UATB["QA reviews & merges<br/>Deploys to UAT env"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph Main["main branch"]
|
|
||||||
MAIN["UAT validates & merges<br/>Deploys to production"]
|
|
||||||
end
|
|
||||||
|
|
||||||
FB -->|"PR + CI"| DEV
|
|
||||||
DEV -->|"PR + QA review"| UATB
|
|
||||||
UATB -->|"PR + UAT review"| MAIN
|
|
||||||
|
|
||||||
classDef dev fill:#fff3cd,stroke:#856404,color:#000
|
|
||||||
classDef uat fill:#cce5ff,stroke:#004085,color:#000
|
|
||||||
classDef prod fill:#d4edda,stroke:#155724,color:#000
|
|
||||||
|
|
||||||
class DEV dev
|
|
||||||
class UATB uat
|
|
||||||
class MAIN prod
|
|
||||||
```
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
---
|
|
||||||
name: uat
|
|
||||||
description: >
|
|
||||||
Functional UAT procedures for Privileged Escalation Headlamp plugins. General
|
|
||||||
behavior, acceptance criteria, artifact requirements, and reference to
|
|
||||||
plugin-specific test steps in UAT_PLAYBOOK.md.
|
|
||||||
---
|
|
||||||
|
|
||||||
# UAT Procedures
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
This skill defines **functional User Acceptance Testing** for all Privileged Escalation Headlamp plugins. UAT validates that plugins work correctly in the deployed environment — by exercising plugin features in a running Headlamp instance, not by reviewing code or CI results.
|
|
||||||
|
|
||||||
## UAT Environment
|
|
||||||
|
|
||||||
The UAT Headlamp instance runs in the `headlamp-uat` Kubernetes namespace. Navigate to the Headlamp UAT URL using your Playwright browser. The plugin under test must be deployed to UAT before testing begins.
|
|
||||||
|
|
||||||
## General Process
|
|
||||||
|
|
||||||
For every `uat→main` promotion:
|
|
||||||
|
|
||||||
1. Open the Headlamp UAT instance in the browser
|
|
||||||
2. Confirm the plugin appears in the sidebar or app bar
|
|
||||||
3. Read the plugin's `UAT_PLAYBOOK.md` for the specific test steps to run
|
|
||||||
4. Execute the test steps from the playbook, capturing screenshots at each verification
|
|
||||||
5. Check the browser console for errors throughout
|
|
||||||
6. Post a structured test report (see Artifacts section)
|
|
||||||
|
|
||||||
## Acceptance Criteria
|
|
||||||
|
|
||||||
A plugin passes UAT when:
|
|
||||||
|
|
||||||
- **Plugin loads** — sidebar entry or app bar action is visible and accessible
|
|
||||||
- **Features work** — all core features in the playbook execute without errors
|
|
||||||
- **No console errors** — browser console shows no errors during normal operation
|
|
||||||
- **Data matches cluster state** — plugin data is consistent with `kubectl` queries against the cluster
|
|
||||||
|
|
||||||
A plugin fails UAT when:
|
|
||||||
|
|
||||||
- Plugin does not load or renders only an error state
|
|
||||||
- Any core feature is inaccessible or produces errors
|
|
||||||
- Console errors are present and not explainable as unrelated noise
|
|
||||||
- Displayed data contradicts known cluster state
|
|
||||||
|
|
||||||
## Artifact Requirements
|
|
||||||
|
|
||||||
For each plugin tested, the UAT report must include:
|
|
||||||
|
|
||||||
1. **Screenshots** of the plugin running in Headlamp — sidebar entry visible, main view loaded, at least one detail view
|
|
||||||
2. **Test checklist** — each step from `UAT_PLAYBOOK.md` marked pass/fail
|
|
||||||
3. **Console errors** — any browser console errors observed (attach screenshot if present)
|
|
||||||
4. **Environment info** — Headlamp version, plugin version, browser used, namespace context
|
|
||||||
|
|
||||||
## Reading UAT_PLAYBOOK.md
|
|
||||||
|
|
||||||
Each plugin repository contains a `UAT_PLAYBOOK.md` in its root directory. That file contains the canonical test steps for that specific plugin. Before running UAT, read the relevant playbook to know:
|
|
||||||
|
|
||||||
- Which features to exercise
|
|
||||||
- What the expected results are
|
|
||||||
- What screenshots to capture at each step
|
|
||||||
|
|
||||||
If `UAT_PLAYBOOK.md` does not exist for a plugin, treat that as a gap — report it in the UAT findings and flag it as a documentation issue.
|
|
||||||
|
|
||||||
## Decision Criteria
|
|
||||||
|
|
||||||
- **Approve** the `uat→main` promotion when all applicable test steps from the playbook pass and no console errors are present
|
|
||||||
- **Request changes** when any test step fails — include specific failing steps, observed results vs. expected results, and failure screenshots
|
|
||||||
- **Block** if the plugin fails to load entirely — escalate to CTO as a deployment issue requiring immediate resolution
|
|
||||||
Reference in New Issue
Block a user