Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f5d570ea60 | |||
| 1b4913c0fd | |||
| 983498765e | |||
| f901d622d1 | |||
| ae024551bb | |||
| 1f18a1d982 | |||
| d62d5da70d | |||
| 4c71fab41b | |||
| 7183381140 | |||
| 611334167b | |||
| a3bab704df | |||
| c48eccd70c | |||
| ea1f585722 | |||
| bedef6ab6a | |||
| 1fe4f900b0 | |||
| 44e528c373 | |||
| c041da4847 | |||
| fe3b4b90d7 | |||
| f9b3ea1882 | |||
| 40a8f3d773 | |||
| b5aa2b54a0 | |||
| bfe02545e5 | |||
| 0641848c4b | |||
| 40caf8cfee | |||
| da86aa7754 | |||
| b1e2000542 | |||
| d4a6141986 | |||
| d077c62bcb | |||
| 8840bd874d | |||
| 4c779823a0 | |||
| 496be01898 | |||
| 64269836f2 | |||
| a03256c231 | |||
| 1ebc0b0d89 | |||
| 6930b7a258 | |||
| d69f5e4bd4 | |||
| b7335c078e | |||
| 8b13f024e5 | |||
| 12ccf82454 |
@@ -0,0 +1,56 @@
|
|||||||
|
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"
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
@@ -1 +0,0 @@
|
|||||||
github: [privilegedescalation]
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
self-hosted-runner:
|
|
||||||
labels: []
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ci-health-check.sh — Scan all privilegedescalation repos for CI/CD health
|
|
||||||
# Run from: /paperclip/privilegedescalation/engineering/hugh
|
|
||||||
# Requires: GH_TOKEN set (use: export GH_TOKEN=$(bash ./get-github-token.sh))
|
|
||||||
#
|
|
||||||
# Plugin repo discovery
|
|
||||||
# ---------------------
|
|
||||||
# PLUGIN_REPOS is populated dynamically from the GitHub org so newly created
|
|
||||||
# plugin repos are picked up automatically. The filter is:
|
|
||||||
# - non-archived, public repos in the privilegedescalation org
|
|
||||||
# - name starts with "headlamp-"
|
|
||||||
# - excludes "headlamp-agent-skills" (skills bundle, not a Headlamp plugin)
|
|
||||||
# If discovery fails (network error, GH_TOKEN missing, API outage), we fall
|
|
||||||
# back to a hardcoded list so the health check still produces a useful report.
|
|
||||||
#
|
|
||||||
# Failure Categories:
|
|
||||||
# - code: test/lint/build/typecheck failures on main
|
|
||||||
# - infra: startup_failure, timed_out, runner issues
|
|
||||||
# - pending: action_required (awaiting review/approval) - informational only
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ORG="privilegedescalation"
|
|
||||||
|
|
||||||
# Hardcoded fallback — kept in sync manually as a safety net for discovery failures.
|
|
||||||
PLUGIN_REPOS_FALLBACK=(
|
|
||||||
headlamp-polaris-plugin
|
|
||||||
headlamp-rook-plugin
|
|
||||||
headlamp-sealed-secrets-plugin
|
|
||||||
headlamp-intel-gpu-plugin
|
|
||||||
headlamp-tns-csi-plugin
|
|
||||||
headlamp-kube-vip-plugin
|
|
||||||
headlamp-plugin-template
|
|
||||||
headlamp-argocd-plugin
|
|
||||||
)
|
|
||||||
|
|
||||||
mapfile -t PLUGIN_REPOS < <(
|
|
||||||
gh api --paginate "orgs/${ORG}/repos" \
|
|
||||||
--jq '.[] | select(.archived == false and .visibility == "public" and (.name | startswith("headlamp-")) and .name != "headlamp-agent-skills") | .name' \
|
|
||||||
2>/dev/null | sort
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ ${#PLUGIN_REPOS[@]} -eq 0 ]; then
|
|
||||||
echo "WARNING: dynamic repo discovery returned no results — using hardcoded fallback" >&2
|
|
||||||
PLUGIN_REPOS=("${PLUGIN_REPOS_FALLBACK[@]}")
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Private repos not visible to dynamic discovery
|
|
||||||
PLUGIN_REPOS+=("infra")
|
|
||||||
|
|
||||||
echo "=== CI/CD Health Check — $(date -u '+%Y-%m-%d %H:%M UTC') ==="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
failures=0
|
|
||||||
warnings=0
|
|
||||||
process_pending=0
|
|
||||||
|
|
||||||
for repo in "${PLUGIN_REPOS[@]}"; do
|
|
||||||
echo "--- ${repo} ---"
|
|
||||||
|
|
||||||
# Get last 10 runs (wider window to catch intermittent failures)
|
|
||||||
runs=$(gh run list --repo "${ORG}/${repo}" --limit 10 --json name,conclusion,headBranch,updatedAt 2>/dev/null || echo "[]")
|
|
||||||
|
|
||||||
if [ "$runs" = "[]" ]; then
|
|
||||||
echo " WARNING: No workflow runs found"
|
|
||||||
((warnings++)) || true
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
total=$(echo "$runs" | jq 'length')
|
|
||||||
|
|
||||||
# Categorize failures:
|
|
||||||
# - code failures: test/lint/build on main
|
|
||||||
# - infra failures: startup_failure, timed_out
|
|
||||||
# - process pending: action_required
|
|
||||||
|
|
||||||
code_failures=$(echo "$runs" | jq '[.[] | select(.headBranch=="main" and .conclusion=="failure" and .name!="Release" and .name!="E2E Tests")] | length')
|
|
||||||
infra_failures=$(echo "$runs" | jq '[.[] | select(.conclusion=="startup_failure" or .conclusion=="timed_out")] | length')
|
|
||||||
action_required=$(echo "$runs" | jq '[.[] | select(.conclusion=="action_required")] | length')
|
|
||||||
|
|
||||||
if [ "$code_failures" -gt 0 ]; then
|
|
||||||
echo " FAIL (code): ${code_failures} CI failure(s) in last ${total} runs on main:"
|
|
||||||
echo "$runs" | jq -r '.[] | select(.headBranch=="main" and .conclusion=="failure" and .name!="Release" and .name!="E2E Tests") | " - \(.name) (\(.updatedAt))"'
|
|
||||||
((failures++)) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$infra_failures" -gt 0 ]; then
|
|
||||||
echo " FAIL (infra): ${infra_failures} infrastructure failure(s):"
|
|
||||||
echo "$runs" | jq -r '.[] | select(.conclusion=="startup_failure" or .conclusion=="timed_out") | " - \(.name): \(.conclusion) (\(.updatedAt))"'
|
|
||||||
((failures++)) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$code_failures" -eq 0 ] && [ "$infra_failures" -eq 0 ]; then
|
|
||||||
echo " OK: CI passing on main"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Process pending — informational only (awaiting review/approval)
|
|
||||||
if [ "$action_required" -gt 0 ]; then
|
|
||||||
echo " INFO: ${action_required} workflow run(s) awaiting action (dual approval, review, etc.):"
|
|
||||||
echo "$runs" | jq -r '.[] | select(.conclusion=="action_required") | " - \(.name) on \(.headBranch) (\(.updatedAt))"'
|
|
||||||
((process_pending++)) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Surface E2E test failures as warnings (infra blocker: RBAC not yet applied — PRI-494)
|
|
||||||
e2e_failures=$(echo "$runs" | jq '[.[] | select(.headBranch=="main" and .name=="E2E Tests" and .conclusion=="failure")] | length')
|
|
||||||
if [ "$e2e_failures" -gt 0 ]; then
|
|
||||||
echo " WARN: E2E Tests failing on main (${e2e_failures} failure(s)) — RBAC bootstrap pending (PRI-494)"
|
|
||||||
((warnings++)) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Surface Release failures as warnings — with graceful skip in place, these indicate real errors
|
|
||||||
release_failures=$(echo "$runs" | jq '[.[] | select(.name=="Release" and .conclusion=="failure")] | length')
|
|
||||||
if [ "$release_failures" -gt 0 ]; then
|
|
||||||
echo " WARN: Release workflow has ${release_failures} failure(s) — investigate (PRI-380 secrets still pending)"
|
|
||||||
((warnings++)) || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check latest release
|
|
||||||
latest_release=$(gh api "repos/${ORG}/${repo}/releases" --jq '.[0].tag_name // "none"' 2>/dev/null || echo "error")
|
|
||||||
echo " Latest release: ${latest_release}"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "=== Summary ==="
|
|
||||||
echo "Repos scanned: ${#PLUGIN_REPOS[@]}"
|
|
||||||
echo "With failures: ${failures}"
|
|
||||||
echo "With warnings: ${warnings}"
|
|
||||||
echo "With pending approval: ${process_pending}"
|
|
||||||
|
|
||||||
if [ "$failures" -gt 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
# GitHub Actions Workflows
|
|
||||||
|
|
||||||
This directory contains reusable and repo-specific GitHub Actions workflows for the privilegedescalation organization.
|
|
||||||
|
|
||||||
## Available Tools on Runners
|
|
||||||
|
|
||||||
### Always Available
|
|
||||||
- `curl` - HTTP client (use this instead of `gh` CLI for API calls)
|
|
||||||
- `jq` - JSON processor
|
|
||||||
- `bash` - Shell
|
|
||||||
- `git` - Version control
|
|
||||||
- `docker` / `podman` - Container runtime (depending on runner)
|
|
||||||
|
|
||||||
### NOT Available (must install if needed)
|
|
||||||
- `gh` CLI - GitHub CLI is **not** pre-installed on runners. Use `curl` with the GitHub API instead.
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
### GitHub API Calls
|
|
||||||
Instead of using `gh` CLI (which is not installed), use `curl` with the GitHub API:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Set PR label
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
run: |
|
|
||||||
curl -sf \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
|
||||||
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \
|
|
||||||
-d '{"labels":["label-name"]}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workflow Validation
|
|
||||||
Run actionlint locally before pushing:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
actionlint -color .github/workflows/*.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reusable Workflows
|
|
||||||
- `plugin-ci.yaml` - Standard CI for Headlamp plugins
|
|
||||||
- `plugin-e2e.yaml` - E2E testing for Headlamp plugins
|
|
||||||
- `dual-approval-check.yaml` - Checks for CTO and QA approval
|
|
||||||
- `detect-pr-pipeline.yaml` - Detects Pipeline A vs Pipeline B based on changed files
|
|
||||||
|
|
||||||
## Workflow Naming Convention
|
|
||||||
|
|
||||||
- Use kebab-case: `my-workflow.yaml`
|
|
||||||
- Be descriptive: `plugin-ci.yaml` not `ci.yaml`
|
|
||||||
- For reusable workflows, keep the name clear about its purpose
|
|
||||||
|
|
||||||
## Required Gates
|
|
||||||
|
|
||||||
All PRs must pass:
|
|
||||||
1. `actionlint` validation (workflow YAML syntax)
|
|
||||||
2. Shell script validation (if scripts are used)
|
|
||||||
3. Any repo-specific CI checks
|
|
||||||
|
|
||||||
## Common Patterns
|
|
||||||
|
|
||||||
### Getting Changed Files
|
|
||||||
Use `tj-actions/changed-files`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- name: Get changed files
|
|
||||||
id: changed-files
|
|
||||||
uses: tj-actions/changed-files@v47
|
|
||||||
with:
|
|
||||||
files_separator: '\n'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Setting Job Outputs
|
|
||||||
```yaml
|
|
||||||
- name: Set output
|
|
||||||
id: detect
|
|
||||||
run: |
|
|
||||||
echo "pipeline-type=pipeline-a" >> $GITHUB_OUTPUT
|
|
||||||
```
|
|
||||||
|
|
||||||
Access in downstream jobs: `${{ jobs.job-name.outputs.pipeline-type }}`
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
name: CI/CD Health Check
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 8 * * 1-5' # Every weekday at 8 AM UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
health-check:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Run CI/CD health check
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
||||||
run: |
|
|
||||||
if [ -z "$GITEA_TOKEN" ]; then
|
|
||||||
echo "::warning::GITEA_TOKEN not configured — health check may have limited data."
|
|
||||||
fi
|
|
||||||
./.github/scripts/ci-health-check.sh
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
name: Detect PR Pipeline Type
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main, dev, uat]
|
|
||||||
workflow_call:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test-detection-logic:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 2
|
|
||||||
|
|
||||||
env:
|
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
|
||||||
BASE_REF: ${{ github.base_ref }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
|
|
||||||
git fetch origin "$BASE_REF" --depth=1
|
|
||||||
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
|
|
||||||
git checkout "${{ github.sha }}"
|
|
||||||
|
|
||||||
- name: Run detection tests
|
|
||||||
run: bash scripts/test-detect-pipeline.sh
|
|
||||||
|
|
||||||
detect-pipeline:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
env:
|
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
|
||||||
BASE_REF: ${{ github.base_ref }}
|
|
||||||
|
|
||||||
outputs:
|
|
||||||
pipeline-type: ${{ steps.detect.outputs.pipeline-type }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
run: |
|
|
||||||
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
|
|
||||||
git fetch origin "$BASE_REF" --depth=1
|
|
||||||
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
|
|
||||||
git checkout "${{ github.sha }}"
|
|
||||||
|
|
||||||
- name: Get changed files
|
|
||||||
run: |
|
|
||||||
mkdir -p /tmp/pr-detect
|
|
||||||
git fetch origin "$BASE_REF" --depth=1 2>/dev/null
|
|
||||||
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1 2>/dev/null
|
|
||||||
git diff --name-only "origin/$BASE_REF" HEAD > /tmp/pr-detect/changed_files.txt
|
|
||||||
echo "Files found: $(wc -l < /tmp/pr-detect/changed_files.txt)"
|
|
||||||
cat /tmp/pr-detect/changed_files.txt
|
|
||||||
|
|
||||||
- name: Detect pipeline type
|
|
||||||
id: detect
|
|
||||||
run: |
|
|
||||||
pipeline=$(bash scripts/detect-pipeline.sh < /tmp/pr-detect/changed_files.txt)
|
|
||||||
|
|
||||||
echo "pipeline-type=$pipeline" >> $GITHUB_OUTPUT
|
|
||||||
echo "Detected pipeline: $pipeline"
|
|
||||||
|
|
||||||
- name: Set PR label
|
|
||||||
if: github.event_name == 'pull_request'
|
|
||||||
continue-on-error: true
|
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
REPO: ${{ github.repository }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
PIPELINE_TYPE: ${{ steps.detect.outputs.pipeline-type }}
|
|
||||||
run: |
|
|
||||||
curl -sf \
|
|
||||||
-X POST \
|
|
||||||
-H "Authorization: Bearer ${GH_TOKEN}" \
|
|
||||||
-H "Accept: application/vnd.github.v3+json" \
|
|
||||||
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/labels" \
|
|
||||||
-d "{\"labels\":[\"${PIPELINE_TYPE}\"]}"
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
name: PR Validation
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
env:
|
|
||||||
HEAD_REF: ${{ github.head_ref }}
|
|
||||||
BASE_REF: ${{ github.base_ref }}
|
|
||||||
run: |
|
|
||||||
git clone --depth=1 "https://x-access-token:${{ secrets.GITEA_TOKEN }}@git.farh.net/${{ github.repository }}.git" .
|
|
||||||
git fetch origin "$BASE_REF" --depth=1
|
|
||||||
git fetch origin +refs/pull/*/head:refs/pull/*/head --depth=1
|
|
||||||
git checkout "${{ github.sha }}"
|
|
||||||
|
|
||||||
- name: Install actionlint
|
|
||||||
run: |
|
|
||||||
ACTIONLINT_VERSION="1.7.7"
|
|
||||||
mkdir -p "$HOME/.local/bin"
|
|
||||||
apt-get install -y wget -qq >/dev/null 2>&1 || true
|
|
||||||
wget -qO- "https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" \
|
|
||||||
| tar -xz -C "$HOME/.local/bin" actionlint
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Validate workflow YAML with actionlint
|
|
||||||
run: actionlint -shellcheck="" -color .github/workflows/*.yaml
|
|
||||||
|
|
||||||
- name: Install shellcheck
|
|
||||||
run: |
|
|
||||||
SC_VERSION="v0.10.0"
|
|
||||||
mkdir -p "$HOME/.local/bin"
|
|
||||||
wget -qO- "https://github.com/koalaman/shellcheck/releases/download/${SC_VERSION}/shellcheck-${SC_VERSION}.linux.x86_64.tar.xz" \
|
|
||||||
| tar -xJ --strip-components=1 -C "$HOME/.local/bin" "shellcheck-${SC_VERSION}/shellcheck"
|
|
||||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
|
||||||
|
|
||||||
- name: Shellcheck scripts
|
|
||||||
run: |
|
|
||||||
if ls .github/scripts/*.sh 1>/dev/null 2>&1; then
|
|
||||||
for script in .github/scripts/*.sh; do
|
|
||||||
echo "Checking ${script}..."
|
|
||||||
shellcheck --severity=warning "$script"
|
|
||||||
done
|
|
||||||
else
|
|
||||||
echo "No shell scripts to check"
|
|
||||||
fi
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
name: Renovate
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 2 * * 6' # Saturday 2:00 UTC — aligns with "every weekend" in renovate-config.json
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
renovate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
|
|
||||||
- name: Run Renovate
|
|
||||||
env:
|
|
||||||
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
|
||||||
RENOVATE_PLATFORM: gitea
|
|
||||||
RENOVATE_ENDPOINT: https://git.farh.net
|
|
||||||
RENOVATE_AUTODISCOVER: "true"
|
|
||||||
LOG_LEVEL: debug
|
|
||||||
run: |
|
|
||||||
npx renovate \
|
|
||||||
--token="${RENOVATE_TOKEN}" \
|
|
||||||
--platform=gitea \
|
|
||||||
--endpoint=https://git.farh.net \
|
|
||||||
--configurationFile=renovate-config.json
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
name: Stale Release Branch Cleanup
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 9 * * 1' # Weekly every Monday at 09:00 UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
dry_run:
|
|
||||||
description: 'Dry run (no changes made)'
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cleanup-stale-branches:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
sparse-checkout: |
|
|
||||||
.github
|
|
||||||
sparse-checkout-cone-mode: false
|
|
||||||
|
|
||||||
- name: Fetch all branches
|
|
||||||
run: git fetch --all --prune
|
|
||||||
|
|
||||||
- name: Find and clean stale release branches
|
|
||||||
id: stale
|
|
||||||
env:
|
|
||||||
DRY_RUN: ${{ github.event.inputs.dry_run || false }}
|
|
||||||
run: |
|
|
||||||
DAYS=14
|
|
||||||
|
|
||||||
# Find release branches older than 14 days not on main
|
|
||||||
for branch in $(git for-each-ref --format '%(refname:strip=3)' 'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*'); do
|
|
||||||
ts=$(git log -1 --format='%ct' "refs/remotes/origin/$branch")
|
|
||||||
if [ -z "$ts" ]; then
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
age_days=$(( ($(date +%s) - ts) / 86400 ))
|
|
||||||
|
|
||||||
if [ "$age_days" -gt "$DAYS" ]; then
|
|
||||||
# Check if branch has been merged into main
|
|
||||||
if git merge-base --is-ancestor "refs/remotes/origin/$branch" main 2>/dev/null; then
|
|
||||||
echo "Merged branch found: $branch (age: ${age_days}d)"
|
|
||||||
if [ "$DRY_RUN" == "true" ]; then
|
|
||||||
echo "Would delete merged branch: $branch"
|
|
||||||
else
|
|
||||||
echo "Deleting merged branch: $branch"
|
|
||||||
if ! git push origin --delete "$branch" 2>&1; then
|
|
||||||
echo "::warning::Failed to delete branch: $branch"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Report dry run results
|
|
||||||
if: github.event.inputs.dry_run == 'true'
|
|
||||||
run: |
|
|
||||||
echo "Dry run complete. No branches were deleted."
|
|
||||||
echo ""
|
|
||||||
echo "Release branches found:"
|
|
||||||
git for-each-ref --format '%(refname:strip=3) - %(committerdate:relative)' \
|
|
||||||
'refs/remotes/origin/release/*' 'refs/remotes/origin/v[0-9]*' 2>/dev/null || echo "None found"
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# 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
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
extends: default
|
||||||
|
|
||||||
|
rules:
|
||||||
|
line-length: disable
|
||||||
|
document-start: disable
|
||||||
|
truthy:
|
||||||
|
check-keys: false
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# 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.
|
||||||
@@ -1 +0,0 @@
|
|||||||
github: [privilegedescalation]
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<img src="privilegedescalation-logo.jpg" alt="Privileged Escalation" width="300" />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 align="center">Headlamp plugins for the infrastructure you actually run.</h3>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://artifacthub.io/packages/search?org=privilegedescalation&kind=21">Artifact Hub</a>
|
|
||||||
·
|
|
||||||
<a href="https://headlamp.dev">Headlamp</a>
|
|
||||||
·
|
|
||||||
<a href="https://github.com/sponsors/privilegedescalation">Sponsor</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
We build open source [Headlamp](https://headlamp.dev) plugins that bring deep visibility into Kubernetes storage, networking, GPU, and security subsystems — right inside your cluster dashboard.
|
|
||||||
|
|
||||||
## Our Plugins
|
|
||||||
|
|
||||||
| Plugin | What it does | Artifact Hub |
|
|
||||||
|--------|-------------|:---:|
|
|
||||||
| [headlamp-rook-plugin](https://github.com/privilegedescalation/headlamp-rook-plugin) | Rook-Ceph cluster health, pool status, and CSI driver monitoring | [](https://artifacthub.io/packages/headlamp/headlamp-rook-plugin/headlamp-rook-plugin) |
|
|
||||||
| [headlamp-tns-csi-plugin](https://github.com/privilegedescalation/headlamp-tns-csi-plugin) | TrueNAS CSI driver visibility and kbench storage benchmarking | [](https://artifacthub.io/packages/headlamp/headlamp-tns-csi-plugin/headlamp-tns-csi-plugin) |
|
|
||||||
| [headlamp-sealed-secrets-plugin](https://github.com/privilegedescalation/headlamp-sealed-secrets-plugin) | Manage Bitnami Sealed Secrets with client-side encryption | [](https://artifacthub.io/packages/headlamp/headlamp-sealed-secrets-plugin/headlamp-sealed-secrets-plugin) |
|
|
||||||
| [headlamp-polaris-plugin](https://github.com/privilegedescalation/headlamp-polaris-plugin) | Fairwinds Polaris security and best-practices auditing | [](https://artifacthub.io/packages/headlamp/headlamp-polaris-plugin/headlamp-polaris-plugin) |
|
|
||||||
| [headlamp-intel-gpu-plugin](https://github.com/privilegedescalation/headlamp-intel-gpu-plugin) | Intel GPU device visibility and resource monitoring | [](https://artifacthub.io/packages/headlamp/headlamp-intel-gpu-plugin/headlamp-intel-gpu-plugin) |
|
|
||||||
| [headlamp-kube-vip-plugin](https://github.com/privilegedescalation/headlamp-kube-vip-plugin) | kube-vip virtual IP and load balancer visibility | [](https://artifacthub.io/packages/headlamp/headlamp-kube-vip/headlamp-kube-vip) |
|
|
||||||
|
|
||||||
## Why Headlamp?
|
|
||||||
|
|
||||||
Headlamp is a CNCF-listed Kubernetes dashboard built for extensibility. Our plugins slot in natively — no separate UIs, no context switching. If you run Headlamp, you can add any of our plugins with a single command.
|
|
||||||
|
|
||||||
## Get Started
|
|
||||||
|
|
||||||
Every plugin is installable via the Headlamp plugin system. See individual repos for install instructions.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
We welcome contributions, bug reports, and feature requests. Open an issue on any repo or start a discussion. All projects are licensed under Apache 2.0.
|
|
||||||
|
|
||||||
## Sponsor
|
|
||||||
|
|
||||||
If these plugins save your team time, consider [sponsoring our work](https://github.com/sponsors/privilegedescalation). Sponsorship funds go directly toward new plugin development and maintenance.
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 63 KiB |
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
|
||||||
"gitAuthor": "Renovate Bot <bot@renovateapp.com>",
|
|
||||||
"extends": ["config:recommended"],
|
|
||||||
"baseBranches": ["main"],
|
|
||||||
"schedule": ["every weekend"],
|
|
||||||
"prConcurrentLimit": 5,
|
|
||||||
"pinDigests": true,
|
|
||||||
"packageRules": [
|
|
||||||
{
|
|
||||||
"matchManagers": ["npm"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
|
||||||
"groupName": "npm minor and patch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchManagers": ["npm"],
|
|
||||||
"matchUpdateTypes": ["major"],
|
|
||||||
"groupName": "npm major updates",
|
|
||||||
"automerge": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchManagers": ["github-actions"],
|
|
||||||
"matchUpdateTypes": ["minor", "patch"],
|
|
||||||
"groupName": "github-actions minor and patch"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"matchManagers": ["github-actions"],
|
|
||||||
"matchUpdateTypes": ["major"],
|
|
||||||
"groupName": "github-actions major updates",
|
|
||||||
"automerge": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"local>privilegedescalation/.github:renovate-config"
|
||||||
|
]
|
||||||
|
}
|
||||||
Executable
+106
@@ -0,0 +1,106 @@
|
|||||||
|
#!/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,49 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Reads a newline-separated list of changed files from stdin.
|
|
||||||
# Outputs "pipeline-a" or "pipeline-b" to stdout.
|
|
||||||
# Pipeline B: all files are infra-only (config, docs, workflows).
|
|
||||||
# Pipeline A: any non-infra file present.
|
|
||||||
|
|
||||||
detect_pipeline() {
|
|
||||||
local all_infra=true
|
|
||||||
|
|
||||||
while IFS= read -r file; do
|
|
||||||
[ -z "$file" ] && continue
|
|
||||||
|
|
||||||
local filename
|
|
||||||
local dir
|
|
||||||
filename=$(basename "$file")
|
|
||||||
dir=$(dirname "$file")
|
|
||||||
|
|
||||||
if [[ "$dir" == ".github" || "$dir" == .github/* ]] || \
|
|
||||||
[[ "$dir" == "infra" || "$dir" == infra/* ]] || \
|
|
||||||
[[ "$dir" == "org" || "$dir" == org/* ]] || \
|
|
||||||
[[ "$filename" == *.md ]] || \
|
|
||||||
[[ "$filename" == .eslintrc* ]] || \
|
|
||||||
[[ "$filename" == .prettierrc* ]] || \
|
|
||||||
[[ "$filename" == renovate.json* ]] || \
|
|
||||||
[[ "$filename" == .gitignore ]] || \
|
|
||||||
[[ "$filename" == .editorconfig ]] || \
|
|
||||||
[[ "$filename" == LICENSE ]] || \
|
|
||||||
[[ "$filename" == Dockerfile ]] || \
|
|
||||||
[[ "$filename" == docker-compose* ]] || \
|
|
||||||
[[ "$filename" == Makefile ]]; then
|
|
||||||
continue
|
|
||||||
else
|
|
||||||
all_infra=false
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$all_infra" = true ]; then
|
|
||||||
echo "pipeline-b"
|
|
||||||
else
|
|
||||||
echo "pipeline-a"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
||||||
detect_pipeline
|
|
||||||
fi
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
source "$SCRIPT_DIR/detect-pipeline.sh"
|
|
||||||
|
|
||||||
PASS=0
|
|
||||||
FAIL=0
|
|
||||||
|
|
||||||
assert_eq() {
|
|
||||||
local test_name="$1" expected="$2" actual="$3"
|
|
||||||
if [ "$expected" = "$actual" ]; then
|
|
||||||
echo "PASS: $test_name"
|
|
||||||
PASS=$((PASS + 1))
|
|
||||||
else
|
|
||||||
echo "FAIL: $test_name (expected=$expected, actual=$actual)"
|
|
||||||
FAIL=$((FAIL + 1))
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
run_detect() {
|
|
||||||
echo "$1" | detect_pipeline
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- Pipeline B cases (infra-only) ---
|
|
||||||
|
|
||||||
assert_eq "single .github root file" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/dependabot.yml")"
|
|
||||||
|
|
||||||
assert_eq ".github/workflows subdirectory" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/workflows/ci.yaml")"
|
|
||||||
|
|
||||||
assert_eq "deeply nested .github path" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/workflows/reusable/build.yaml")"
|
|
||||||
|
|
||||||
assert_eq "markdown file at root" "pipeline-b" \
|
|
||||||
"$(run_detect "README.md")"
|
|
||||||
|
|
||||||
assert_eq "markdown in subdirectory" "pipeline-b" \
|
|
||||||
"$(run_detect "docs/CONTRIBUTING.md")"
|
|
||||||
|
|
||||||
assert_eq "eslintrc config" "pipeline-b" \
|
|
||||||
"$(run_detect ".eslintrc.json")"
|
|
||||||
|
|
||||||
assert_eq "prettierrc config" "pipeline-b" \
|
|
||||||
"$(run_detect ".prettierrc.yaml")"
|
|
||||||
|
|
||||||
assert_eq "renovate config" "pipeline-b" \
|
|
||||||
"$(run_detect "renovate.json")"
|
|
||||||
|
|
||||||
assert_eq "renovate config5" "pipeline-b" \
|
|
||||||
"$(run_detect "renovate.json5")"
|
|
||||||
|
|
||||||
assert_eq "gitignore" "pipeline-b" \
|
|
||||||
"$(run_detect ".gitignore")"
|
|
||||||
|
|
||||||
assert_eq "editorconfig" "pipeline-b" \
|
|
||||||
"$(run_detect ".editorconfig")"
|
|
||||||
|
|
||||||
assert_eq "LICENSE" "pipeline-b" \
|
|
||||||
"$(run_detect "LICENSE")"
|
|
||||||
|
|
||||||
assert_eq "mixed infra files" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/workflows/ci.yaml
|
|
||||||
README.md
|
|
||||||
.eslintrc.json
|
|
||||||
LICENSE")"
|
|
||||||
|
|
||||||
assert_eq "workflow + markdown combo" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/workflows/detect-pr-pipeline.yaml
|
|
||||||
.github/workflows/README.md")"
|
|
||||||
|
|
||||||
assert_eq "infra root file" "pipeline-b" \
|
|
||||||
"$(run_detect "infra/helmrelease.yaml")"
|
|
||||||
|
|
||||||
assert_eq "infra nested file" "pipeline-b" \
|
|
||||||
"$(run_detect "infra/clusters/prod/kustomization.yaml")"
|
|
||||||
|
|
||||||
assert_eq "org root file" "pipeline-b" \
|
|
||||||
"$(run_detect "org/CODEOWNERS")"
|
|
||||||
|
|
||||||
assert_eq "org nested file" "pipeline-b" \
|
|
||||||
"$(run_detect "org/policies/branch-protection.json")"
|
|
||||||
|
|
||||||
assert_eq "Dockerfile" "pipeline-b" \
|
|
||||||
"$(run_detect "Dockerfile")"
|
|
||||||
|
|
||||||
assert_eq "docker-compose.yaml" "pipeline-b" \
|
|
||||||
"$(run_detect "docker-compose.yaml")"
|
|
||||||
|
|
||||||
assert_eq "docker-compose.override.yml" "pipeline-b" \
|
|
||||||
"$(run_detect "docker-compose.override.yml")"
|
|
||||||
|
|
||||||
assert_eq "Makefile" "pipeline-b" \
|
|
||||||
"$(run_detect "Makefile")"
|
|
||||||
|
|
||||||
assert_eq "mixed infra + org + workflow" "pipeline-b" \
|
|
||||||
"$(run_detect ".github/workflows/ci.yaml
|
|
||||||
infra/helmrelease.yaml
|
|
||||||
org/CODEOWNERS
|
|
||||||
README.md")"
|
|
||||||
|
|
||||||
# --- Pipeline A cases (has non-infra files) ---
|
|
||||||
|
|
||||||
assert_eq "plugin source file" "pipeline-a" \
|
|
||||||
"$(run_detect "headlamp-polaris-plugin/src/index.tsx")"
|
|
||||||
|
|
||||||
assert_eq "plugin package.json" "pipeline-a" \
|
|
||||||
"$(run_detect "headlamp-polaris-plugin/package.json")"
|
|
||||||
|
|
||||||
assert_eq "root source file" "pipeline-a" \
|
|
||||||
"$(run_detect "src/main.ts")"
|
|
||||||
|
|
||||||
assert_eq "mixed infra + code" "pipeline-a" \
|
|
||||||
"$(run_detect ".github/workflows/ci.yaml
|
|
||||||
headlamp-polaris-plugin/src/index.tsx
|
|
||||||
README.md")"
|
|
||||||
|
|
||||||
assert_eq "single non-infra file" "pipeline-a" \
|
|
||||||
"$(run_detect "server.js")"
|
|
||||||
|
|
||||||
assert_eq "plugin code + infra files" "pipeline-a" \
|
|
||||||
"$(run_detect "infra/helmrelease.yaml
|
|
||||||
org/CODEOWNERS
|
|
||||||
headlamp-polaris-plugin/src/index.tsx")"
|
|
||||||
|
|
||||||
# --- Edge cases ---
|
|
||||||
|
|
||||||
assert_eq "empty input" "pipeline-b" \
|
|
||||||
"$(run_detect "")"
|
|
||||||
|
|
||||||
assert_eq "root dot file (not in infra list)" "pipeline-a" \
|
|
||||||
"$(run_detect ".env")"
|
|
||||||
|
|
||||||
assert_eq ".github-like but not .github dir" "pipeline-a" \
|
|
||||||
"$(run_detect ".github-backup/config.yaml")"
|
|
||||||
|
|
||||||
# --- Summary ---
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "Results: $PASS passed, $FAIL failed"
|
|
||||||
|
|
||||||
if [ "$FAIL" -gt 0 ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
name: coding-standards
|
||||||
|
description: >
|
||||||
|
Coding standards for Privileged Escalation. Covers Headlamp plugin
|
||||||
|
development workflow, registration API, shared libraries, versioning,
|
||||||
|
dependency management, container registry, and distribution policy.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Coding Standards
|
||||||
|
|
||||||
|
## Headlamp Plugins
|
||||||
|
|
||||||
|
All plugins extend [Headlamp](https://headlamp.dev/docs/latest/development/plugins/getting-started), a Kubernetes dashboard with a plugin system.
|
||||||
|
|
||||||
|
- **Language:** TypeScript + React 18, MUI v5
|
||||||
|
- **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
|
||||||
|
|
||||||
|
Run from the plugin directory:
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `npm run start` | Dev mode with hot reload |
|
||||||
|
| `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
|
||||||
|
|
||||||
|
Import from `@kinvolk/headlamp-plugin/lib`:
|
||||||
|
|
||||||
|
- `registerAppBarAction()` — add components to the nav bar
|
||||||
|
- `registerRoute()` — create new pages
|
||||||
|
- `registerSidebarEntry()` — add sidebar items
|
||||||
|
- `registerDetailsViewSection()` — extend resource detail views
|
||||||
|
- `registerPluginSettings()` — add plugin configuration UI
|
||||||
|
|
||||||
|
### K8s API Access
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { K8s } from '@kinvolk/headlamp-plugin/lib';
|
||||||
|
const [pods, error] = K8s.ResourceClasses.Pod.useList();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shared Libraries
|
||||||
|
|
||||||
|
These are provided by Headlamp at runtime — **do not bundle them**:
|
||||||
|
React, React Router, Redux, MUI, Lodash, Monaco Editor, Notistack, Iconify.
|
||||||
|
|
||||||
|
## Versioning & Distribution
|
||||||
|
|
||||||
|
- **All releases use SemVer.** ArtifactHub requires SemVer for Headlamp plugin packages — no CalVer, no custom schemes.
|
||||||
|
- **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
|
||||||
|
|
||||||
|
- **Dependency updates are owned by Mend Renovate.** Never enable Dependabot, never create `.github/dependabot.yml`, never reference Dependabot in workflows or docs.
|
||||||
|
- **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.
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
---
|
||||||
|
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.
|
||||||
|
```
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
---
|
||||||
|
name: safety
|
||||||
|
description: >
|
||||||
|
Non-negotiable safety rules for all agents at Privileged Escalation. Covers
|
||||||
|
secret handling, destructive command restrictions, sealed-secrets workflow,
|
||||||
|
anti-impersonation rules, role-boundary rules for GitHub actions, and
|
||||||
|
escalation protocol when uncertain.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Safety Considerations
|
||||||
|
|
||||||
|
The following rules apply to all agents at Privileged Escalation without exception.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
* **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.
|
||||||
|
|
||||||
|
* **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.
|
||||||
|
|
||||||
|
* **Do not use `kubectl create` in production.**
|
||||||
|
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.
|
||||||
|
|
||||||
|
* **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:
|
||||||
|
- **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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
---
|
||||||
|
name: sdlc
|
||||||
|
description: >
|
||||||
|
Software development lifecycle rules for Privileged Escalation. Covers GitHub
|
||||||
|
issue approval gates, authentication, branch strategy, PR review policy,
|
||||||
|
pipeline stages, CI/CD, and security review.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Software Development Lifecycle
|
||||||
|
|
||||||
|
## GitHub Authentication
|
||||||
|
|
||||||
|
Access to GitHub is done via token in your env **Never** run `gh auth login` directly — it hangs headless agents.
|
||||||
|
|
||||||
|
## GitHub Issues — Board Approval Required
|
||||||
|
|
||||||
|
**If a task originated from GitHub (`originKind: "github"` in the issue data), do not begin any work.** Immediately create a `request_board_approval`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
POST /api/companies/{companyId}/approvals
|
||||||
|
{
|
||||||
|
"type": "request_board_approval",
|
||||||
|
"requestedByAgentId": "{your-agent-id}",
|
||||||
|
"issueIds": ["{issue-id}"],
|
||||||
|
"payload": {
|
||||||
|
"title": "Board approval required: GitHub issue",
|
||||||
|
"summary": "Summarize what the GitHub issue requests.",
|
||||||
|
"recommendedAction": "Approve to begin work.",
|
||||||
|
"risks": ["Work begins without board review if approved."]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the issue to `blocked` until `PAPERCLIP_APPROVAL_STATUS` confirms approval. Only proceed once approved.
|
||||||
|
|
||||||
|
## Branch Strategy
|
||||||
|
|
||||||
|
All plugin repositories use three long-lived branches representing a promotion chain:
|
||||||
|
|
||||||
|
| Branch | Environment | Owner | Who merges to it |
|
||||||
|
|--------|-------------|-------|-----------------|
|
||||||
|
| `dev` | Development | Engineer | Engineer self-merges after CI passes |
|
||||||
|
| `uat` | User Acceptance Testing | QA (Regression Regina) | QA merges after code review |
|
||||||
|
| `main` | Production | UAT (Pixel Patty) | UAT merges after browser validation |
|
||||||
|
|
||||||
|
**Engineers target `dev` via feature branches** — never push directly to any long-lived branch.
|
||||||
|
|
||||||
|
Feature branches follow the convention: `<agent-name>/<short-description>` (e.g., `gandalf/add-sealed-secrets-list`).
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
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
|
||||||
|
gh pr create --title "..." --body "... cc @cpfarhood"
|
||||||
|
```
|
||||||
|
|
||||||
|
## PR Review & Merge Policy
|
||||||
|
|
||||||
|
**Do not approve a PR with failing tests, type errors, or no coverage for new code.**
|
||||||
|
|
||||||
|
### Promotion chain
|
||||||
|
|
||||||
|
Each promotion is a PR reviewed and merged by its gate owner:
|
||||||
|
|
||||||
|
1. **feature → dev** — Engineer self-merges after CI passes. No review required. Dev is for validation, not quality gates.
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Pipeline
|
||||||
|
|
||||||
|
### Pipeline A: Plugin/Feature Changes
|
||||||
|
|
||||||
|
```text
|
||||||
|
Engineer → PR to dev → self-merge → deploys to dev
|
||||||
|
→ Engineer validates on dev
|
||||||
|
→ PR from dev → uat → QA reviews → QA merges
|
||||||
|
→ Deploys to UAT environment
|
||||||
|
→ PR from uat → main → UAT validates → UAT merges
|
||||||
|
→ Production
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies to changes in `headlamp-*-plugin/` repos (plugin code, features, bug fixes).
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Pipeline B: Infrastructure Changes (No UI Impact)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Engineer → PR to main → CI passes → QA reviews → QA merges
|
||||||
|
→ Production
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies to changes in `.github/workflows/`, `infra/`, `org/` repos, and template repos. No UAT stage needed — infrastructure changes have no UI to validate.
|
||||||
|
|
||||||
|
**Detection:** If `git diff` shows changes only in `.github/`, `infra/`, `org/`, or deployment files → Pipeline B. If any `headlamp-*-plugin/` code changed → Pipeline A.
|
||||||
|
|
||||||
|
**Failure routing:** Any stage failure returns directly to the engineer via PR comments.
|
||||||
|
|
||||||
|
## Issue Reviewers and Approvers
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Pipeline A — set reviewers on issue creation
|
||||||
|
|
||||||
|
For plugin/feature work (Pipeline A), set a two-stage execution policy so QA and UAT appear as reviewers:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
QA_STAGE_ID=$(uuidgen)
|
||||||
|
QA_PART_ID=$(uuidgen)
|
||||||
|
UAT_STAGE_ID=$(uuidgen)
|
||||||
|
UAT_PART_ID=$(uuidgen)
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
"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`)
|
||||||
|
- Stage 2 reviewer: Pixel Patty (`01ec02f7-70c2-4fa1-ac3f-2545f1237ac3`)
|
||||||
|
|
||||||
|
### Pipeline B — single reviewer
|
||||||
|
|
||||||
|
For infrastructure changes (Pipeline B), use one QA review stage:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"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
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## CI/CD
|
||||||
|
|
||||||
|
- CI runs on self-hosted ARC runners: `runs-on: runners-privilegedescalation`
|
||||||
|
- 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
|
||||||
|
|
||||||
|
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.
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
# 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
|
||||||
|
```
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
---
|
||||||
|
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