|
| 1 | +# Maestro Client Guide |
| 2 | + |
| 3 | +This guide describes how to use Maestro's APIs to manage resources on target consumers (clusters). |
| 4 | + |
| 5 | +## Architecture Overview |
| 6 | + |
| 7 | +For detailed internal architecture and data flows, see [Maestro Overview](./maestro.md). |
| 8 | + |
| 9 | +```mermaid |
| 10 | +flowchart LR |
| 11 | + subgraph Clients |
| 12 | + Admin["Admin"] |
| 13 | + Source["Source<br/>(e.g. CS)"] |
| 14 | + end |
| 15 | +
|
| 16 | + subgraph "Maestro Server" |
| 17 | + REST["REST API"] |
| 18 | + GRPC["gRPC API"] |
| 19 | + DB[(Database)] |
| 20 | + Broker["Broker"] |
| 21 | + end |
| 22 | +
|
| 23 | + Admin -->|"manage consumers"| REST |
| 24 | + Admin -->|"read status"| REST |
| 25 | + REST <--> DB |
| 26 | +
|
| 27 | + Source -->|"create/update/delete<br/>resource bundles"| GRPC |
| 28 | + Source -->|"subscribe to<br/>status updates"| GRPC |
| 29 | + GRPC <--> DB |
| 30 | + DB --> Broker |
| 31 | +``` |
| 32 | + |
| 33 | +## Client Libraries |
| 34 | + |
| 35 | +### REST API Client (OpenAPI-generated) |
| 36 | + |
| 37 | +**Package:** `github.com/openshift-online/maestro/pkg/api/openapi` |
| 38 | + |
| 39 | +```go |
| 40 | +import "github.com/openshift-online/maestro/pkg/api/openapi" |
| 41 | + |
| 42 | +client := openapi.NewAPIClient(cfg) |
| 43 | + |
| 44 | +// Consumers |
| 45 | +client.DefaultAPI.ApiMaestroV1ConsumersGet(ctx) // List |
| 46 | +client.DefaultAPI.ApiMaestroV1ConsumersIdGet(ctx, id) // Get |
| 47 | +client.DefaultAPI.ApiMaestroV1ConsumersPost(ctx) // Create |
| 48 | +client.DefaultAPI.ApiMaestroV1ConsumersIdPatch(ctx, id) // Update |
| 49 | +client.DefaultAPI.ApiMaestroV1ConsumersIdDelete(ctx, id)// Delete |
| 50 | + |
| 51 | +// Resource Bundles (read-only + delete) |
| 52 | +client.DefaultAPI.ApiMaestroV1ResourceBundlesGet(ctx) // List |
| 53 | +client.DefaultAPI.ApiMaestroV1ResourceBundlesIdGet(ctx, id) // Get (includes status) |
| 54 | +client.DefaultAPI.ApiMaestroV1ResourceBundlesIdDelete(ctx, id)// Delete |
| 55 | +``` |
| 56 | + |
| 57 | +### gRPC Client (for sending resources + watching status) |
| 58 | + |
| 59 | +**Package:** `github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource` |
| 60 | + |
| 61 | +For a complete working example, see [examples/manifestwork](../examples/manifestwork/). For gRPC authentication setup (mTLS or token-based), see [gRPC Authentication](./maestro.md#authentication-and-authorization). |
| 62 | + |
| 63 | +```go |
| 64 | +import "github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource" |
| 65 | + |
| 66 | +// Returns a workv1client.WorkV1Interface (standard OCM ManifestWork client) |
| 67 | +workClient, err := grpcsource.NewMaestroGRPCSourceWorkClient( |
| 68 | + ctx, |
| 69 | + logger, |
| 70 | + apiClient, // the openapi client (used internally for watching) |
| 71 | + grpcOpts, // gRPC connection options |
| 72 | + sourceID, |
| 73 | +) |
| 74 | + |
| 75 | +// Then use it like a regular ManifestWork client |
| 76 | +workClient.ManifestWorks(clusterName).Create(ctx, manifestWork, metav1.CreateOptions{}) |
| 77 | +workClient.ManifestWorks(clusterName).Update(ctx, manifestWork, metav1.UpdateOptions{}) |
| 78 | +workClient.ManifestWorks(clusterName).Delete(ctx, name, metav1.DeleteOptions{}) |
| 79 | +workClient.ManifestWorks(clusterName).Watch(ctx, metav1.ListOptions{}) // streams status updates |
| 80 | +``` |
| 81 | + |
| 82 | +## Consumer Lifecycle |
| 83 | + |
| 84 | +A **Consumer** represents a target cluster. You must create a consumer before sending resources to it. |
| 85 | + |
| 86 | +1. **Create consumer** via REST API (provides consumer name/ID) |
| 87 | +2. **Agent connects** using that consumer name |
| 88 | +3. **Send resources** via gRPC, specifying the consumer name as the target |
| 89 | +4. **Delete consumer** when the cluster is decommissioned (resources are cleaned up) |
| 90 | + |
| 91 | +```go |
| 92 | +// 1. Create consumer via REST |
| 93 | +consumer, _, _ := client.DefaultAPI.ApiMaestroV1ConsumersPost(ctx). |
| 94 | + Consumer(openapi.Consumer{Name: openapi.PtrString("my-cluster")}). |
| 95 | + Execute() |
| 96 | + |
| 97 | +// 2. Send resources via gRPC to that consumer |
| 98 | +workClient.ManifestWorks(*consumer.Name).Create(ctx, manifestWork, metav1.CreateOptions{}) |
| 99 | +``` |
| 100 | + |
| 101 | +## Key Concepts |
| 102 | + |
| 103 | +### Resource Bundles and ManifestWorks |
| 104 | + |
| 105 | +- Maestro only supports **ResourceBundles**, not single resources |
| 106 | +- **ManifestWork** is the Kubernetes CR representation of a ResourceBundle |
| 107 | +- A ResourceBundle can contain one or more Kubernetes manifests |
| 108 | +- FeedbackRules can be defined inside the ManifestWork to extract data from applied resources. They are calculated by the agent and reported as part of the status. |
| 109 | + |
| 110 | +### ManifestWork Structure |
| 111 | + |
| 112 | +For detailed create/update/delete flow diagrams, see [Resource Flows](./maestro.md#maestro-resource-flow). |
| 113 | + |
| 114 | +``` |
| 115 | +ManifestWork |
| 116 | +├── metadata.generation ← MW spec version (you set this) |
| 117 | +├── spec.workload.manifests[] ← The K8s resources |
| 118 | +├── spec.manifestConfigs[] ← Feedback rules |
| 119 | +└── status |
| 120 | + ├── conditions[] |
| 121 | + │ ├── type: Applied/Available |
| 122 | + │ └── observedGeneration ← MW version agent processed |
| 123 | + └── resourceStatus.manifests[] |
| 124 | + ├── resourceMeta ← Which object |
| 125 | + ├── conditions[] ← Did agent apply it? (no observedGen here) |
| 126 | + └── statusFeedback.jsonRaw ← Contains fields requested via feedbackRules |
| 127 | + (e.g., observedGeneration, conditions, replicas) |
| 128 | +``` |
| 129 | + |
| 130 | +## Update Strategy Types |
| 131 | + |
| 132 | +| Type | Behavior | Use Case | |
| 133 | +|------|----------|----------| |
| 134 | +| **Update** | Uses Kubernetes PUT - replaces the entire object, last write wins | Normal apply when you own the full resource | |
| 135 | +| **CreateOnly** | Creates resource but never updates it | Bootstrap resources that shouldn't be modified | |
| 136 | +| **ServerSideApply** | Uses Kubernetes PATCH with field ownership tracking - only sends fields you manage, merges with other controllers | Recommended. Safe when multiple controllers touch the same resource | |
| 137 | +| **ReadOnly** | Does NOT apply. Only checks existence and collects status | Watch resources created by others | |
| 138 | + |
| 139 | +### Example: ReadOnly for watching external resources |
| 140 | + |
| 141 | +```go |
| 142 | +ManifestConfigs: []workv1.ManifestConfigOption{ |
| 143 | + { |
| 144 | + ResourceIdentifier: workv1.ResourceIdentifier{ |
| 145 | + Group: "apps", |
| 146 | + Resource: "deployments", |
| 147 | + Name: "some-existing-deployment", // created by something else |
| 148 | + Namespace: "kube-system", |
| 149 | + }, |
| 150 | + UpdateStrategy: &workv1.UpdateStrategy{ |
| 151 | + Type: workv1.UpdateStrategyTypeReadOnly, // Don't touch it, just watch |
| 152 | + }, |
| 153 | + FeedbackRules: []workv1.FeedbackRule{ |
| 154 | + { |
| 155 | + Type: workv1.JSONPathsType, |
| 156 | + JsonPaths: []workv1.JsonPath{ |
| 157 | + {Name: "status", Path: ".status"}, |
| 158 | + }, |
| 159 | + }, |
| 160 | + }, |
| 161 | + }, |
| 162 | +}, |
| 163 | +``` |
| 164 | + |
| 165 | +## Feedback Rules |
| 166 | + |
| 167 | +FeedbackRules tell the agent what data to extract from each applied resource and send back. Without them, you only get conditions (Applied/Available), not the actual resource status. |
| 168 | + |
| 169 | +```go |
| 170 | +FeedbackRules: []workv1.FeedbackRule{ |
| 171 | + { |
| 172 | + Type: workv1.JSONPathsType, |
| 173 | + JsonPaths: []workv1.JsonPath{ |
| 174 | + {Name: "status", Path: ".status"}, // entire status object |
| 175 | + {Name: "observedGen", Path: ".status.observedGeneration"}, // specific field |
| 176 | + {Name: "replicas", Path: ".status.readyReplicas"}, // any field |
| 177 | + {Name: "resource", Path: "@"}, // entire resource (spec + status) |
| 178 | + }, |
| 179 | + }, |
| 180 | +}, |
| 181 | +``` |
| 182 | + |
| 183 | +| Path | Returns | |
| 184 | +|------|---------| |
| 185 | +| `.status` | The resource's status object | |
| 186 | +| `.status.observedGeneration` | A specific field | |
| 187 | +| `@` | The entire resource (including spec and status) | |
| 188 | + |
| 189 | +The extracted values appear in `status.resourceStatus.manifests[].statusFeedback.values[].fieldValue.jsonRaw` and must be parsed by the client. |
| 190 | + |
| 191 | +## Deletion Behavior |
| 192 | + |
| 193 | +For a visual sequence diagram of the delete flow, see [Resource Delete Flow](./maestro.md#maestro-resource-flow). |
| 194 | + |
| 195 | +> **Note:** Maestro's database is a tracking layer, not the source of truth for what's running on clusters. If the database is wiped directly (bypassing the API), resources on target clusters remain intact. Source clients can resync to restore Maestro's tracking state. |
| 196 | +
|
| 197 | +### Deletion Flow |
| 198 | + |
| 199 | +When you delete a ManifestWork/ResourceBundle, the following sequence occurs: |
| 200 | + |
| 201 | +``` |
| 202 | +1. User requests deletion (REST DELETE or gRPC Delete) |
| 203 | + │ |
| 204 | + ▼ |
| 205 | +2. Maestro marks resource as "deleting" (soft delete, sets deleted_at timestamp) |
| 206 | + │ |
| 207 | + ▼ |
| 208 | +3. Maestro sends delete CloudEvent to agent |
| 209 | + │ |
| 210 | + ▼ |
| 211 | +4. Agent deletes resources from target cluster (per PropagationPolicy) |
| 212 | + │ |
| 213 | + ▼ |
| 214 | +5. Agent sends confirmation CloudEvent back |
| 215 | + │ |
| 216 | + ▼ |
| 217 | +6. Maestro hard deletes the record from database |
| 218 | +``` |
| 219 | + |
| 220 | +### Propagation Policy |
| 221 | + |
| 222 | +The `DeleteOption.PropagationPolicy` controls what happens to the resources on the target cluster: |
| 223 | + |
| 224 | +| Policy | Behavior | Use Case | |
| 225 | +|--------|----------|----------| |
| 226 | +| **Foreground** | Delete resources and wait for them to be gone before completing | Default. Clean deletion. | |
| 227 | +| **Orphan** | Remove from Maestro tracking but leave resources running on cluster | Hand off to another system | |
| 228 | +| **SelectivelyOrphan** | Orphan only specific resources, delete others | Transfer ownership of some resources | |
| 229 | + |
| 230 | +### Setting Delete Options |
| 231 | + |
| 232 | +Delete options are set in the ManifestWork spec: |
| 233 | + |
| 234 | +```go |
| 235 | +&workv1.ManifestWork{ |
| 236 | + Spec: workv1.ManifestWorkSpec{ |
| 237 | + DeleteOption: &workv1.DeleteOption{ |
| 238 | + PropagationPolicy: workv1.DeletePropagationPolicyTypeForeground, // or Orphan |
| 239 | + }, |
| 240 | + Workload: workv1.ManifestsTemplate{ |
| 241 | + Manifests: []workv1.Manifest{...}, |
| 242 | + }, |
| 243 | + }, |
| 244 | +} |
| 245 | +``` |
| 246 | + |
| 247 | +### Selective Orphan Example |
| 248 | + |
| 249 | +Transfer ownership of a specific resource to another ManifestWork: |
| 250 | + |
| 251 | +```go |
| 252 | +DeleteOption: &workv1.DeleteOption{ |
| 253 | + PropagationPolicy: workv1.DeletePropagationPolicyTypeSelectivelyOrphan, |
| 254 | + SelectivelyOrphan: &workv1.SelectivelyOrphan{ |
| 255 | + OrphaningRules: []workv1.OrphaningRule{ |
| 256 | + { |
| 257 | + Group: "apps", |
| 258 | + Resource: "deployments", |
| 259 | + Name: "shared-component", |
| 260 | + Namespace: "default", |
| 261 | + }, |
| 262 | + }, |
| 263 | + }, |
| 264 | +}, |
| 265 | +``` |
| 266 | + |
| 267 | +### TTL Auto-Deletion |
| 268 | + |
| 269 | +ManifestWorks can be automatically deleted after completion: |
| 270 | + |
| 271 | +```go |
| 272 | +DeleteOption: &workv1.DeleteOption{ |
| 273 | + PropagationPolicy: workv1.DeletePropagationPolicyTypeForeground, |
| 274 | + TTLSecondsAfterFinished: ptr.To(int64(3600)), // Delete 1 hour after completion |
| 275 | +}, |
| 276 | +``` |
| 277 | + |
| 278 | +### Checking Deletion Status |
| 279 | + |
| 280 | +A resource being deleted will have: |
| 281 | +- `deleted_at` timestamp set (visible in REST API response) |
| 282 | +- Status condition `type: Deleted` once agent confirms deletion |
| 283 | + |
| 284 | +```go |
| 285 | +bundle, _, _ := client.DefaultAPI.ApiMaestroV1ResourceBundlesIdGet(ctx, id).Execute() |
| 286 | +if bundle.DeletedAt != nil { |
| 287 | + // Resource is being deleted |
| 288 | +} |
| 289 | +``` |
| 290 | + |
| 291 | +## Troubleshooting |
| 292 | + |
| 293 | +If resources are not being applied or status is not being reported, see [Troubleshooting](./troubleshooting.md) for health checks, log analysis, and common issues. |
0 commit comments