diff --git a/clientdocs/README.md b/clientdocs/README.md index f030dcb..50a163e 100644 --- a/clientdocs/README.md +++ b/clientdocs/README.md @@ -22,16 +22,47 @@ TEMPLATE NOTE banner). - `ci-integration-guide.md` -- with the handover pack for any client that connects CI/automation (Jenkins etc.); same instantiation rule. -- **Templates.** `handover-pack.md`, `ci-integration-guide.md`, and - `acceptance-checklist.md` are reusable templates: `{{PLACEHOLDER}}` fields - are operator-filled at instantiation; a delivered copy contains none. + - `scripts/` (the tenant starter kit) -- WITH the handover pack: + `tenancy-audit.sh`, `smoke-test.sh`, `acceptance-run.sh`, and + `ci-cleanup-sweep.sh` go to every client; `clouds.yaml.template` and + `Jenkinsfile.example` go only to clients that connect CI/automation + (with the CI guide). The .sh files are generic (auth via the client's + own OS_* environment, nothing to instantiate); `clouds.yaml.template` + follows the `{{PLACEHOLDER}}` instantiation rule below. + - `tenant-skill/` + `ai-assistant-guide.md` -- AT handover, with the + pack, for any client whose team uses an AI assistant (Claude Code / + claude.ai). `tenant-skill/` is a Claude Agent Skill (SKILL.md + + references/): instantiate every `{{PLACEHOLDER}}` across all its + files, remove the TEMPLATE NOTE banner, deliver the directory as a + copy. `ai-assistant-guide.md` is the client-facing install/usage + page for it (no placeholders). Install on the client side: copy the + directory into their project as `.claude/skills/omega-cloud-tenant/` + (Claude Code), or add the files to their claude.ai project; the + installed directory name must match the skill name + `omega-cloud-tenant`. The skill's routing table references the + starter-kit scripts by name; keep them in sync. +- **Templates.** `handover-pack.md`, `ci-integration-guide.md`, + `acceptance-checklist.md`, `scripts/clouds.yaml.template`, and every file + under `tenant-skill/` are reusable templates: `{{PLACEHOLDER}}` fields are + operator-filled at instantiation; a delivered copy contains none. The + tenant-skill placeholder set is exactly: `{{TENANT_SHORT_NAME}}`, + `{{AUTH_URL}}`, `{{DASHBOARD_URL}}`, `{{REGION}}`, `{{ACCOUNT_CONTACT}}` + -- enforced by `tests/clientdocs-skill/run-tests.sh`, which also gates + internal-term leakage and routing-table reachability for the skill. - **ASCII + LF**, like everything committed. - **Sweep-on-change is enforced.** `sweep-receipt.txt` (INTERNAL, never - shipped) pins the reviewed state of every tenant-facing file; repo lint - fails when any of them drifts, until this directory has been reviewed for - staleness and the receipt is re-recorded: + shipped) pins the reviewed state of every tenant-facing file (including + everything under `scripts/`, which is auto-covered like the *.md files); + repo lint fails when any of them drifts, until this directory has been + reviewed for staleness and the receipt is re-recorded: `bash scripts/repo-lint.sh --record-clientdocs-sweep`. +- **Starter-kit scripts are tested offline.** Any change to `scripts/*.sh` + ships with `tests/clientdocs-scripts/run-tests.sh` green (mock CLI, no + cloud access). Contents: `intake-form.md`, `welcome.md`, `self-service-guide.md`, -`handover-pack.md`, `ci-integration-guide.md`, `acceptance-checklist.md`; -internal-only: `sweep-receipt.txt`. +`handover-pack.md`, `ci-integration-guide.md`, `acceptance-checklist.md`, +`ai-assistant-guide.md`, `tenant-skill/` (SKILL.md + references/), +`scripts/` (starter kit: `tenancy-audit.sh`, `smoke-test.sh`, +`acceptance-run.sh`, `ci-cleanup-sweep.sh`, `clouds.yaml.template`, +`Jenkinsfile.example`); internal-only: `sweep-receipt.txt`. diff --git a/clientdocs/ai-assistant-guide.md b/clientdocs/ai-assistant-guide.md new file mode 100644 index 0000000..67e6a5d --- /dev/null +++ b/clientdocs/ai-assistant-guide.md @@ -0,0 +1,82 @@ +# Omega Cloud -- Using Your AI Assistant Skill (DRAFT) + +With your handover pack you received `tenant-skill/`, a skill package for +Claude (Anthropic's AI assistant). Installed into your own project, it +teaches your assistant how to operate your Omega Cloud environment +correctly: which of your three accounts to use for which job, which +permission errors are deliberate platform boundaries, and how to behave +safely on a live environment. This page explains how to install it and +how to get the most out of it. + +## What the skill is + +A folder of plain-text instructions your AI assistant loads when you ask +it to do cloud work: + +- `SKILL.md` -- the core: account selection, the boundary model, + authentication, safety rules, and a routing table. +- `references/` -- detailed, on-demand pages the assistant loads per + topic: day-2 operations (networks, VMs, volumes, load balancers), + Kubernetes, CI/automation, and troubleshooting. + +Your copy was instantiated for you at handover: it already contains your +short name, endpoints, and region. It contains NO credentials -- it tells +the assistant how to authenticate, never what your secrets are. + +## Installing it + +- **Claude Code** (terminal / IDE): copy the delivered `tenant-skill/` + directory into your project as `.claude/skills/omega-cloud-tenant/` + (the directory name must be exactly `omega-cloud-tenant`). The skill + is picked up automatically; asking "what skills are available?" should + list it. +- **claude.ai** (web): add the skill files to your project so they are + part of its knowledge, or use your organization's skill-upload feature + where available. Claude Code with terminal access is the better fit + for hands-on cloud work, since the assistant can run the OpenStack + CLI itself. + +Give the assistant a working CLI environment: the OpenStack client +installed, your `clouds.yaml` configured per the CI/Automation +Integration Guide, and the delivered CA bundle on disk. Credential +secrets belong in your environment (or your secret store), never pasted +into the chat. + +## Getting the best out of it + +1. **Just describe the task.** "Create a VM reachable from our office + network", "why did the pipeline get a permission error", "stand up a + Kubernetes cluster for staging". The skill triggers on cloud work by + itself; you do not need to name it. +2. **Let it read before it writes.** The skill instructs the assistant + to audit current state with list/show commands before changing + anything, and to ask your confirmation before anything destructive. + Expect (and welcome) those confirmation questions. +3. **Expect it to respect boundaries.** If you ask for something the + platform deliberately refuses -- raising your own quota, creating a + cluster from an automation credential, granting `admin` -- a + correctly working assistant will decline the workaround and point + you at the request path instead. That is the skill doing its job. +4. **Never paste secrets into the conversation.** Passwords, credential + secrets, kubeconfig contents, and private keys stay out of the chat. + The assistant is instructed never to print them; help it by never + supplying them in-line. When a password login is needed, it will + have you enter it at a prompt. +5. **Keep the companions nearby.** The Handover Pack, Self-Service + Guide, CI/Automation Integration Guide, and Acceptance Checklist are + the authoritative long-form documents; the skill is the operating + layer over them. If the assistant's answer and a delivered document + ever disagree, the document wins -- and we would like to hear about + it via your account contact. +6. **A good first test.** Ask the assistant to run the delivered + read-only audit script (`bash scripts/tenancy-audit.sh` from your + document pack). It proves authentication, the CA bundle, endpoint + discovery, and the skill itself in one pass, and changes nothing. + +## Keeping it current + +The skill is versioned with your document pack. When we ship an updated +pack, replace the installed skill directory with the new copy -- do not +hand-edit it, or your edits will be lost at the next update. Requests to +change what the skill teaches go through your account contact like any +other document feedback. diff --git a/clientdocs/scripts/Jenkinsfile.example b/clientdocs/scripts/Jenkinsfile.example new file mode 100644 index 0000000..2169933 --- /dev/null +++ b/clientdocs/scripts/Jenkinsfile.example @@ -0,0 +1,69 @@ +// Jenkinsfile.example -- declarative pipeline skeleton for your Omega Cloud +// environment, following the CI/Automation Integration Guide. +// +// What: smoke test -> the guide's worked sequence (via acceptance-run.sh) +// -> an always-runs cleanup, all authenticated with your application +// credential injected from the Jenkins credential store. +// Setup (once, in Manage Jenkins > Credentials): add a "Username with +// password" credential with id 'omega-cloud-app-credential', where +// username = the application credential id and password = its secret, +// both from the credential file delivered to your custodians. Never +// put them in this file or any repository. +// Notes: adjust the script paths to wherever you keep the delivered starter +// kit scripts in your repository. Keep the delivered CA bundle in your +// repository or agent image and point OS_CACERT at it -- do not +// disable certificate verification instead. + +pipeline { + agent any + + options { + timestamps() + // Load balancers take minutes; the acceptance sequence is not fast. + timeout(time: 60, unit: 'MINUTES') + } + + environment { + // credentials() injects OMEGA_USR (the id) and OMEGA_PSW (the + // secret) and masks them in the build log. + OMEGA = credentials('omega-cloud-app-credential') + + // Environment-variable auth form from the integration guide. + OS_AUTH_TYPE = 'v3applicationcredential' + OS_AUTH_URL = 'FILL-ME: auth url from your Handover Pack' + OS_IDENTITY_API_VERSION = '3' + OS_REGION_NAME = 'FILL-ME: region from your Handover Pack' + OS_CACERT = "${WORKSPACE}/FILL-ME-delivered-ca-bundle.pem" + OS_APPLICATION_CREDENTIAL_ID = "${OMEGA_USR}" + OS_APPLICATION_CREDENTIAL_SECRET = "${OMEGA_PSW}" + } + + stages { + stage('Smoke test') { + steps { + // Token, catalog, quota -- fail fast before building anything. + sh 'bash clientdocs-starter-kit/smoke-test.sh' + } + } + + stage('Worked sequence') { + steps { + // The guide section-7 sequence with automatic teardown. + // Replace this with your own deployment steps once you have + // them; keep a trimmed version as a recurring smoke test. + sh 'bash clientdocs-starter-kit/acceptance-run.sh' + } + } + } + + post { + // Cleanup runs on success, failure, and abort alike, so a broken + // build cannot leak quota. This deletes THIS kit's leftovers + // (ci-accept-*) of any age; run the sweep with its defaults + // (--prefix ci- --hours 24) as a separate scheduled job to catch + // everything else your pipelines leak. + always { + sh 'bash clientdocs-starter-kit/ci-cleanup-sweep.sh --prefix ci-accept- --hours 0 --apply' + } + } +} diff --git a/clientdocs/scripts/acceptance-run.sh b/clientdocs/scripts/acceptance-run.sh new file mode 100644 index 0000000..d20f50e --- /dev/null +++ b/clientdocs/scripts/acceptance-run.sh @@ -0,0 +1,485 @@ +#!/usr/bin/env bash +# acceptance-run.sh -- automated run of the self-service Acceptance Checklist rows +# that your automation (application) credential can prove on its own. +# +# What: builds the checklist's worked sequence with every resource named +# ci-accept-*, checks each row, then tears everything down again in +# reverse order -- teardown is guaranteed by an exit trap, so it runs +# even when a row fails or the script is interrupted. +# +# Rows run here: A1 A4 A5 A6(expected refusal) C1 C2 C3 C4 +# D1 D2 E1 E2 F1 F2 G1 +# Rows skipped: A2 A3 B1 B2 (need a password login) +# D3 (needs interactive SSH) +# H1 H2 H3 (need the -cluster password login) +# I1 (object storage -- confirm with your account contact) +# J1 J2 (request drills through your account contact) +# Manual instructions for the skipped rows are printed at the end. +# +# Why: run once at onboarding to sign off your environment, and re-run any +# time you suspect a problem. "Expected refusal" rows MUST fail -- the +# script counts a refusal as PASS and an unexpected success as FAIL, +# because those refusals are the platform protecting you. +# Safety: creates only resources named ci-accept-* inside your own project, +# and deletes exactly what it created. Waits use the CLI --wait flags +# or 10-second polling, per the integration guide's pacing rules. +# +# Two deliberate adaptations of the checklist text (noted on their rows): +# - D2 boots with a throwaway keypair ci-accept-key created by this run, +# because your handover keypair belongs to the -cluster account and +# keypairs are per-account. The keypair is deleted at teardown. +# - C2/D2 build a dedicated network ci-accept-net (default range +# 192.168.199.0/24, override with CI_ACCEPT_CIDR= if that would +# collide with something you plan to route). It is not attached to any +# router, so it cannot conflict with your handover network. +# +# Auth: your normal OpenStack client environment (OS_CLOUD or the OS_* +# variables), as the application credential. No arguments needed. +# Optional: CI_ACCEPT_IMAGE / CI_ACCEPT_FLAVOR override the automatic +# pick of a shared base image and the smallest machine size. +# Exit: 0 all automated rows passed and teardown is clean | 1 otherwise +# | 2 environment not set up. +set -uo pipefail + +command -v openstack >/dev/null 2>&1 || { + echo "SETUP: the 'openstack' client is not installed (pip install python-openstackclient)"; exit 2; } +command -v python3 >/dev/null 2>&1 || { echo "SETUP: python3 is required"; exit 2; } +if [ -z "${OS_CLOUD:-}" ] && [ -z "${OS_AUTH_URL:-}" ]; then + echo "SETUP: no OpenStack environment found." + echo " Either: export OS_CLOUD=" + echo " Or: export the OS_* variables from the CI/Automation Integration Guide." + exit 2 +fi + +CIDR="${CI_ACCEPT_CIDR:-192.168.199.0/24}" +IMAGE="${CI_ACCEPT_IMAGE:-}" +FLAVOR="${CI_ACCEPT_FLAVOR:-}" +POLL_TRIES=30 # x10s = up to 5 minutes per wait loop + +WORK="$(mktemp -d)" +OS_ERR="$WORK/stderr.txt" +FAILED=0; AUTH_OK=0 +ROWS_ID=(); ROWS_ST=(); ROWS_NOTE=() + +# Structured captures take stdout ONLY; stderr goes to a file so client +# warnings and notices can never corrupt the parsed output. +cap() { # cap + local __v="$1"; shift + local out rc + out=$(openstack "$@" "$OS_ERR"); rc=$? + printf -v "$__v" '%s' "$out" + return "$rc" +} +oerr() { head -1 "$OS_ERR" 2>/dev/null || true; } +jget() { # jget [subkey] -> value (empty + rc 1 when absent) + printf '%s' "$1" | python3 -c ' +import json,sys +try: + v=json.load(sys.stdin) + for k in sys.argv[1:]: v=v[k] + if v is None: raise KeyError + print(v) +except Exception: + sys.exit(1)' "${@:2}" +} +row() { # row + ROWS_ID+=("$1"); ROWS_ST+=("$2"); ROWS_NOTE+=("$3") + [ "$2" = "FAIL" ] && FAILED=1 + printf ' [%s] %-3s %s\n' "$2" "$1" "$3" +} + +# ---- teardown: guaranteed, reverse order, only what this run created ------- +MADE_SECRET=""; SECRET_DONE=0 +MADE_LB=0; MADE_FIP=""; MADE_VM=0; MADE_KEY=0 +MADE_VOL=0; MADE_SG=0; MADE_SUBNET=0; MADE_NET=0; LEAKED_USER=0 +TD_FAIL=0; FINISHED=0 + +td() { # td