Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
43 changes: 27 additions & 16 deletions skills/deploy-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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 <namespace> 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 <name>` 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=<registry> \
--docker-username=<user> \
--docker-password=<token> \
-n <namespace>
indev pullsecret create --name <name> --registry <registry>:<username>:<token>
```

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 <full-cluster-name> <pull-secret-name>
```

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.
Expand Down Expand Up @@ -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." |
Expand Down
9 changes: 2 additions & 7 deletions skills/deploy-app/references/manifest-templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
47 changes: 26 additions & 21 deletions skills/deploy-app/references/usage-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,45 +26,48 @@ 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.
```

---

## 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]
```
Expand All @@ -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).
```

---
Expand All @@ -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
2 changes: 1 addition & 1 deletion skills/prepare-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ If they have `docker` locally, you can confirm the image is actually reachable:
docker manifest inspect <image-ref>
```

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.

Expand Down