- name
- gmp-migrate
- description
- Run the gmp-migrate CLI tool to convert Prometheus Operator manifests (PodMonitor, ServiceMonitor) to Google Managed Prometheus (GMP) CRDs (PodMonitoring, ClusterPodMonitoring), and reconcile generated TODOs, port placeholders, and errors.
# GMP Migration & Reconciliation Skill
This skill guides agents through executing the migration CLI tool, interpreting migration reports, diagnosing `Stderr` logs, resolving safety guardrails and `gmp.googleapis.com/todo-*` annotations, and safely validating converted GMP manifests.
---
## 1. Tool Execution Interface
> [!NOTE]
> All commands in this skill invoke the `gmp-migrate` binary. If the underlying CLI binary name or command syntax changes in the future, only this invocation section needs to be updated.
### Core CLI Invocations
1. **Default Mode (Ready-only)**: Emits strictly 100% ready manifests to `Stdout`. Omits any best-effort drafts requiring human/agent review.
```bash
gmp-migrate -f <input-path> > ready_manifests.yaml 2> migration.log
```
2. **Full Review Mode (`--all` / `-a`)**: Emits all converted manifests to `Stdout`, including best-effort draft configurations with inline `gmp.googleapis.com/todo-*` annotations for human / agent reconciliation.
```bash
gmp-migrate --all -f <input-path> > all_manifests.yaml 2> migration.log
```
3. **Multiple Input Sources (Monitors + Backing Services/Secrets)**:
```bash
gmp-migrate --all -f path/to/monitors/ -f path/to/services/ > all_manifests.yaml 2> migration.log
```
4. **Live Cluster Ingestion via Stdin**:
```bash
kubectl get podmonitors,servicemonitors,services,configmaps,secrets -A -o yaml | gmp-migrate --all -f - > all_manifests.yaml 2> migration.log
```
---
## 2. Input Discovery Strategy
1. **Local Files**:
- Check the workspace for Prometheus Operator YAML manifests (`kind: PodMonitor` or `kind: ServiceMonitor`).
- When migrating local files, also include directories containing backing `kind: Service`, `kind: Secret`, and `kind: ConfigMap` resources so the tool can perform cross-resource port and auth resolution.
2. **Live Cluster Context**:
- If no local manifests exist, or if the user asks to migrate an active cluster, stream cluster resources directly via `kubectl ... | gmp-migrate --all -f -`.
3. **Ambiguous Source**:
- If both local files and an active `kubectl` context exist, ask the user whether they want to migrate local manifests or pull live resources from the cluster.
---
## 3. Exit Code, Stderr Logs & Diagnostic Handling
The CLI strictly separates streams: converted YAML manifests are written to `Stdout`, while diagnostics and summary tables are written to `Stderr`. Workload selectors (`spec.selector.matchLabels` and `spec.selector.matchExpressions`) remain **100% pure and untouched**.
```
=========================================
Migration Complete Summary:
Successfully Migrated: X
Migrated with Warnings: Y
Migrated with Action Items: Z
Skipped (Unsupported): W
Failed: V
=========================================
```
| Exit Code / Report | Status | Stream Behavior & Recommended Agent Action |
|:--------------------------------------------------------------|:-------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **Exit Code 0** (`ActionItemsCount == 0`, `FailedCount == 0`) | Clean Parity | All manifests are 100% ready. Written to `Stdout`. Proceed directly to dry-run verification and diff presentation. |
| **Exit Code 1** (`ActionItemsCount > 0`, `FailedCount == 0`) | Action Items Present | • Default mode: emits only ready manifests to `Stdout` and notes omitted drafts on `Stderr`.<br>• `--all` mode: emits all manifests (including drafts with inline `gmp.googleapis.com/todo-*` annotations) to `Stdout`.<br>Follow Section 5 to reconcile drafts. |
| **Exit Code 1** (`FailedCount > 0`) | Fatal Conversion Failure | Zero manifests were written to `Stdout` (0 bytes). Inspect `Stderr` logs to identify missing dependencies or malformed YAML inputs, resolve them, and re-run. |
### Inspecting CLI Diagnostics in `Stderr` Logs:
When investigating why a TODO or warning was added, inspect the `Stderr` log (or `migration.log`) for pre-scoped resource logs:
```bash
# Filter logs for a specific resource
grep "PodMonitor:default/my-app" migration.log
# Filter all warnings and fatal errors
grep -E "\[WARNING\]|\[ERROR\]" migration.log
```
---
## 4. Secret Creation & Namespacing Rules
* **`PodMonitoring` (Namespaced CRD)**: Kubernetes forbids cross-namespace Secret references. If a Secret or converted ConfigMap is referenced, **it must exist in the same namespace as the `PodMonitoring`**. `gmp-migrate` automatically generates a companion `Secret` clone in every target namespace where the `PodMonitoring` is created.
* **`ClusterPodMonitoring` (Cluster-Scoped CRD)**: Supports an explicit `namespace` field in `SecretKeySelector`. Companion Secrets live in a central namespace (e.g. `gmp-system`), and `secret.namespace` is explicitly populated.
---
## 5. Action Plan Catalog for TODO Reconciliation
> [!CAUTION]
> **Strict Read-Only Investigation Policy**:
> All `kubectl` commands executed during investigation (Recipes 1–6) MUST be strictly READ-ONLY (`get`, `describe`, `logs`, `--dry-run=client`).
> Under NO circumstances should an agent run state-changing commands (`kubectl apply`, `kubectl create`, `kubectl patch`, `kubectl delete`) against a live cluster without reaching Section 7 and receiving explicit user approval.
All TODO annotations follow the format: `gmp.googleapis.com/todo-N: "[WARNING|ERROR] <reason> ACTION: <action>"`. Match the placeholder or annotation message against the recipes below:
---
### Recipe 1: Port & Pod Selector Resolution
#### 1.1 Unresolved Named Port (`TODO_RESOLVE_PORT_<NAME>`)
* **Annotation**: `[WARNING] Named port "<name>" was not found on Service "<svc>"... ACTION: Replace TODO_RESOLVE_PORT_<NAME> with the target container port name or number.`
* **Commands**:
```bash
kubectl get svc <service-name> -n <namespace> -o jsonpath='{.spec.ports}' | jq .
kubectl get pod -n <namespace> -l <selector> -o jsonpath='{.items[0].spec.containers[*].ports}' | jq .
```
* **Resolution**:
1. If Service `targetPort` is an integer (e.g. `8080`), replace placeholder with integer `8080`.
2. If Service `targetPort` is a named string (e.g. `http-metrics`), set `port: http-metrics`.
3. If Service `targetPort` is omitted, replace with the Service's integer `port`.
* **Diff**:
```diff
spec:
endpoints:
- - port: TODO_RESOLVE_PORT_METRICS
+ - port: 8080
interval: 30s
```
#### 1.2 Missing Backing Service (`TODO_RESOLVE_PORT` & `TODO_SET_POD_LABELS`)
* **Context**: `gmp-migrate` emits `TODO_SET_POD_LABELS: TODO_SET_POD_LABELS` as a safe guardrail placeholder. Replace this placeholder with the complete set of pod template labels for the target workload.
* **Annotation**: `[ERROR] Corresponding Kubernetes Service was not found... ACTION: Define target pod selector in 'spec.selector.matchLabels' and verify endpoint ports.`
* **Commands**:
```bash
kubectl get svc -n <namespace> -o wide
kubectl get deployment,statefulset,daemonset -n <namespace> -o wide
```
* **Agent Interactive Step**:
- If a matching Service or workload is identified, copy its pod label selector into `spec.selector.matchLabels` and resolve endpoint ports.
- If multiple ambiguous workloads match, prompt the user:
> *"Could not find a backing Service for `<name>`. Discovered candidate workloads: `[app=payment-processor, app=payment-worker]`. Please confirm the target pod labels."*
* **Diff**:
```diff
metadata:
- annotations:
- gmp.googleapis.com/todo-1: "[ERROR] Corresponding Kubernetes Service was not found..."
spec:
selector:
matchLabels:
- TODO_SET_POD_LABELS: TODO_SET_POD_LABELS
+ app.kubernetes.io/name: payment-processor
+ app.kubernetes.io/component: backend
endpoints:
- - port: TODO_RESOLVE_PORT
+ - port: 9102
```
#### 1.3 Missing Endpoint Port (`TODO_SET_PORT`)
* **Annotation**: `[ERROR] Endpoint [N] does not specify a 'port' or 'targetPort'...`
* **Commands**:
```bash
kubectl get pod -n <namespace> -l <selector> -o jsonpath='{.items[0].spec.containers[*].ports}' | jq .
```
* **Agent Interactive Step**:
- If a single container metrics port is found, resolve to that port.
- If the pod has multiple containers or multiple ports (e.g. `8080` vs `15090`), **do not guess**—prompt the user to choose the intended metrics port.
* **Diff**:
```diff
spec:
endpoints:
- - port: TODO_SET_PORT
+ - port: metrics
```
---
### Recipe 2: Missing or Incomplete Secrets & ConfigMaps
#### Security Policy: Zero Secret Ingestion & Human-In-The-Loop
* **Do Not Read Secrets via CLI**: Agents must never run `kubectl get secret` to inspect secret payloads. Reading cluster secrets risks leaking sensitive credentials (passwords, tokens, private keys) into agent context logs and transcripts.
* **Dual-Path User Handoff**: When a Secret placeholder (`TODO_SET_USERNAME_FROM_SECRET_*` or `TODO_SET_CLIENT-ID_FROM_SECRET_*`) is encountered, the agent must output the targeted `jsonpath` command for the user to run in their terminal, along with the exact resource identifier, file link, field path, and resolution options:
1. **Option A (Interactive)**: The user replies in chat with the non-sensitive string (e.g. `username: <output>`) and the agent updates the manifest and removes the TODO annotation.
2. **Option B (Direct Edit)**: The user updates the manifest directly using the provided file link, target field path, and diff snippet.
#### 2.1 Missing Source Secret / ConfigMap (`TODO_SET_<KEY>_FROM_<KIND>_<NAME>`)
* **Annotation**: `[ERROR] Referenced SECRET "<name>" for key "<key>" was not found in migration inputs...`
* **Agent Handoff Output Format**:
In GMP, `basicAuth.username` is configured directly as a string, while `password` remains securely referenced from Secret `<secret-name>`.
### 1. Retrieve the username in your terminal:
```bash
kubectl get secret <secret-name> -n <namespace> -o jsonpath='{.data.<key>}' | base64 -d
```
### 2. Resolution Options:
* **Option A (Interactive)**: Reply with `The username is: <value>` and I will update the manifest and remove the TODOs for you.
* **Option B (Direct Edit)**:
* **Resource**: `PodMonitoring:<namespace>/<name>`
* **File**: [`<path-to-manifest>.yaml`](file:///<path-to-manifest>.yaml#L<line>)
* **Target Field**: `spec.endpoints[<index>].basicAuth.username`
* **Diff**:
```diff
spec:
endpoints:
- basicAuth:
- username: TODO_SET_<KEY>_FROM_SECRET_<NAME>
+ username: "<your-retrieved-username>"
password:
secret:
name: <secret-name>
key: password
```
View on GitHub