diff --git a/README.md b/README.md index 8eb611c..788c8b4 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ A few intentional choices that shape how the plugin behaves: A few things that aren't obvious up front: - **First run = clicking "Allow" a lot.** Claude Code asks permission the first time it runs each command. Allow them once and future sessions are quiet. +- **Private images need indev v1.4+.** Pull credentials are set up once per cluster (`indev pullsecret`), not per app — `brew upgrade intility/tap/indev` if yours is older. - **Tokens expire.** Both `indev` and `oc` log out after a few hours. If something fails with "Unauthorized", just say *"log me back in"*. - **Different repo, different view.** Claude only sees the manifests in the directory you're working from. Switching repos means switching scope. - **`update-image` keeps the local file in sync** — but only if you run it from the repo that contains the manifests. Otherwise, the YAML on disk will drift from the cluster. diff --git a/skills/deploy-app/SKILL.md b/skills/deploy-app/SKILL.md index a031862..1a342cd 100644 --- a/skills/deploy-app/SKILL.md +++ b/skills/deploy-app/SKILL.md @@ -6,10 +6,13 @@ allowed-tools: - Bash(oc whoami*) - Bash(oc get*) - Bash(oc apply*) - - Bash(oc create*) - Bash(oc rollout*) - Bash(oc describe*) - Bash(oc logs*) + - Bash(indev pullsecret*) + - Bash(indev cluster pullsecret*) + - Bash(indev cluster list*) + - Bash(indev login) - Bash(mkdir *) - Read - Write @@ -34,6 +37,8 @@ If it errors, run the `login` skill first. Don't proceed without auth. The `oc` token has expired. Don't retry the failing command. Stop where you are, route to the `login` skill, then resume from the failed step. +If an `indev` command says "Unauthorized" instead, run `indev login` and resume. + ## Step 1 — Collect what you need If you came from `prepare-app`, you already have: @@ -81,32 +86,38 @@ If the directory doesn't exist, create it. Don't overwrite existing files withou ### Private image registry -If `prepare-app` flagged the registry as private, you need an image pull secret in the namespace. +If `prepare-app` flagged the registry as private, the cluster needs pull credentials. The platform handles this **once per cluster, not per app**, via `indev` (requires indev v1.4+). No `imagePullSecrets` in the manifests, no per-namespace secrets. -**First, check whether one already exists** (idempotency — `oc create secret` errors out if the secret is already there): +**1. Is there already a pull secret that covers this registry?** ```bash -oc get secret pull-secret -n 2>/dev/null +indev pullsecret list ``` -- **Exists** → skip ahead, just reference it in the deployment. -- **Doesn't exist** → ask the user for the registry, username, and password/token (one `AskUserQuestion` with three free-text Other options), then create it: +- One exists and includes the registry → confirm it's assigned to this cluster (`indev pullsecret get ` shows details). If so, nothing to do — continue with the deploy. +- One exists but for a different registry → add this one to it: `indev pullsecret registry add` (check `--help` for the format), then continue. +- None → create one (next step). + +**2. Create it** + +Ask the user for registry, username, and password/token (one `AskUserQuestion` with free-text options). + +> **ghcr.io tip:** username = their GitHub username, token = a **classic** personal access token with the `read:packages` scope (fine-grained tokens don't work with ghcr), created at https://github.com/settings/tokens. Don't let them paste their GitHub password — it won't work. ```bash -oc create secret docker-registry pull-secret \ - --docker-server= \ - --docker-username= \ - --docker-password= \ - -n +indev pullsecret create --name --registry :: ``` -Add to the deployment's pod spec: +The registry's short name (e.g. `ghcr`) is a fine secret name. -```yaml -imagePullSecrets: - - name: pull-secret +**3. Assign it to the cluster** + +```bash +indev cluster pullsecret set ``` +That's it — the whole cluster can now pull from that registry. Every future private app on this cluster just works, no questions asked. + ## Step 3 — Apply Apply in order — namespace first so the others have somewhere to land. @@ -145,7 +156,7 @@ Common things and the one-line summary to give the user: | What you see | Tell the user | |---|---| -| `ImagePullBackOff` / `ErrImagePull` | "The cluster can't pull your image. Is the tag correct, and is the registry public (or did we set up a pull secret)?" | +| `ImagePullBackOff` / `ErrImagePull` | "The cluster can't pull your image. Is the tag correct, and is the registry public? If it's private: does the cluster's pull secret cover it (`indev pullsecret list`), and is the token in it still valid? (PATs expire — recreate the pull secret and reassign it if so.)" | | `CrashLoopBackOff` with logs showing app errors | Share the relevant log line. "Your app is crashing on startup — here's what it said: …" | | `CreateContainerConfigError` | "Something in the manifest is invalid — usually a missing secret or bad env var." | | Pending forever | "Cluster is out of room. Check `oc get nodes` and consider resizing in the portal." | diff --git a/skills/deploy-app/references/manifest-templates.md b/skills/deploy-app/references/manifest-templates.md index 8ffd979..9d71a6e 100644 --- a/skills/deploy-app/references/manifest-templates.md +++ b/skills/deploy-app/references/manifest-templates.md @@ -56,14 +56,9 @@ spec: name: http ``` -### With a private-registry pull secret +### Private images -If the image is in a private registry and you've created a `pull-secret` in the namespace, add this under `spec.template.spec`: - -```yaml - imagePullSecrets: - - name: pull-secret -``` +Don't add `imagePullSecrets` here. Private registries are handled cluster-wide by the platform — `deploy-app` sets it up once with `indev pullsecret create` + `indev cluster pullsecret set`, and every namespace can pull from then on. The manifests stay identical whether the image is public or private. ### With environment variables (only if the user asks) diff --git a/skills/deploy-app/references/usage-examples.md b/skills/deploy-app/references/usage-examples.md index a8ab6ca..6a84559 100644 --- a/skills/deploy-app/references/usage-examples.md +++ b/skills/deploy-app/references/usage-examples.md @@ -26,29 +26,31 @@ You: 1 --- -## Example 2 — Deploy with a private registry +## Example 2 — Deploy with a private registry (ghcr) ``` -You: Deploy private.registry.example.com/team/api:v2, port 8000. +You: Deploy ghcr.io/me/api:v2, port 8000. The image is private. Claude: [oc whoami → ok] + [indev pullsecret list → empty] - What do you want to call this app? -You: api + Your image is private, so the cluster needs pull credentials — I set + this up once and it covers every app on the cluster from now on. -Claude: How many copies should run? -You: 2 + Q: Registry, username, and access token? + (For ghcr.io: your GitHub username + a classic personal access token + with the read:packages scope — github.com/settings/tokens) +You: ghcr.io / me / ghp_abc123 - [mkdir -p k8s/api] - [oc get secret pull-secret -n api → not found] + [indev pullsecret create --name ghcr --registry ghcr.io:me:ghp_abc123] + [indev cluster pullsecret set shop-a3k9x2 ghcr] - Your image is in a private registry. I need credentials for it. + Done — the cluster can now pull from ghcr.io. - Q: Registry, username, and access token? -You: private.registry.example.com / svc-deploy / abc123token + What do you want to call this app? +You: api - [oc create secret docker-registry pull-secret ...] - [writes manifests, includes imagePullSecrets in deployment] + [writes manifests — no imagePullSecrets needed] [oc apply x3, rollout status → success] api is running. @@ -56,15 +58,16 @@ You: private.registry.example.com / svc-deploy / abc123token --- -## Example 3 — Pull secret already exists +## Example 3 — Pull secret already covers the registry ``` -You: Deploy private.registry.example.com/team/another-app:v1. +You: Deploy ghcr.io/me/another-app:v1. Also private. -Claude: [oc get secret pull-secret -n another-app → exists] +Claude: [indev pullsecret list → ghcr (ghcr.io)] + [indev pullsecret get ghcr → assigned to shop-a3k9x2] - Pull secret is already in this namespace — reusing it. No need to ask - for credentials again. + The cluster already has pull access to ghcr.io — no credentials + needed. [writes manifests, applies, rolls out] ``` @@ -83,7 +86,8 @@ Claude: [oc rollout status → progressing] Can you double-check that `docker pull ghcr.io/me/shop:v1` works from your machine? If yes but the cluster still can't pull, the registry is - probably private and we need to add a pull secret. + probably private — I'll set up cluster-wide pull access with + `indev pullsecret` (one question, once, covers all future apps). ``` --- @@ -107,8 +111,9 @@ Claude: [pod status: CrashLoopBackOff] ## What this skill won't do -- Add ConfigMaps, Secrets (other than the optional pull secret), HPAs, - PodDisruptionBudgets, or NetworkPolicies unless you ask +- Add ConfigMaps, Secrets, HPAs, PodDisruptionBudgets, or NetworkPolicies + unless you ask (pull credentials live on the platform via `indev pullsecret`, + not in your manifests) - Set resource requests/limits (cluster has reasonable defaults) - Generate Helm or Kustomize files — plain YAML only - Expose the app on a URL — that's `expose-app`'s job diff --git a/skills/prepare-app/SKILL.md b/skills/prepare-app/SKILL.md index 91fea8c..a7a00f7 100644 --- a/skills/prepare-app/SKILL.md +++ b/skills/prepare-app/SKILL.md @@ -65,7 +65,7 @@ If they have `docker` locally, you can confirm the image is actually reachable: docker manifest inspect ``` -A successful response means the cluster can probably pull it too. If it fails with `unauthorized`, the image is private — flag this and tell the user the cluster will also need credentials (an `imagePullSecret`). Note it and continue; `deploy-app` will handle the secret. +A successful response means the cluster can probably pull it too. If it fails with `unauthorized`, the image is private (or the name has a typo — check that first) — flag it and continue; `deploy-app` sets up pull credentials once for the whole cluster with `indev pullsecret`, so this costs the user one extra question, once. Skip this step if `docker` isn't installed locally — it's a nice-to-have, not a blocker.