@intentius/chant-lexicon-k8s 0.0.18 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/integrity.json +9 -4
  2. package/dist/manifest.json +1 -1
  3. package/dist/skills/chant-k8s-aks.md +146 -0
  4. package/{src/skills/kubernetes-patterns.md → dist/skills/chant-k8s-deployment-strategies.md} +1 -1
  5. package/dist/skills/chant-k8s-eks.md +156 -0
  6. package/dist/skills/chant-k8s-gke.md +246 -0
  7. package/{src/skills/kubernetes-security.md → dist/skills/chant-k8s-security.md} +1 -1
  8. package/dist/skills/chant-k8s.md +66 -3
  9. package/package.json +20 -2
  10. package/src/composites/adot-collector.ts +34 -22
  11. package/src/composites/agic-ingress.ts +14 -6
  12. package/src/composites/aks-external-dns-agent.ts +29 -18
  13. package/src/composites/alb-ingress.ts +14 -6
  14. package/src/composites/autoscaled-service.ts +25 -20
  15. package/src/composites/azure-disk-storage-class.ts +14 -6
  16. package/src/composites/azure-file-storage-class.ts +14 -6
  17. package/src/composites/azure-monitor-collector.ts +34 -22
  18. package/src/composites/batch-job.ts +25 -17
  19. package/src/composites/cockroachdb-cluster.ts +148 -58
  20. package/src/composites/composites.test.ts +369 -363
  21. package/src/composites/config-connector-context.ts +15 -8
  22. package/src/composites/configured-app.ts +21 -15
  23. package/src/composites/cron-workload.ts +25 -20
  24. package/src/composites/ebs-storage-class.ts +14 -6
  25. package/src/composites/efs-storage-class.ts +14 -6
  26. package/src/composites/external-dns-agent.ts +26 -20
  27. package/src/composites/filestore-storage-class.ts +14 -6
  28. package/src/composites/fluent-bit-agent.ts +30 -24
  29. package/src/composites/gce-ingress.ts +14 -6
  30. package/src/composites/gce-pd-storage-class.ts +14 -6
  31. package/src/composites/gke-external-dns-agent.ts +34 -21
  32. package/src/composites/gke-fluent-bit-agent.ts +34 -22
  33. package/src/composites/gke-gateway.ts +19 -12
  34. package/src/composites/gke-otel-collector.ts +34 -22
  35. package/src/composites/irsa-service-account.ts +22 -14
  36. package/src/composites/metrics-server.ts +41 -26
  37. package/src/composites/monitored-service.ts +26 -19
  38. package/src/composites/namespace-env.ts +26 -17
  39. package/src/composites/network-isolated-app.ts +21 -16
  40. package/src/composites/node-agent.ts +33 -22
  41. package/src/composites/secure-ingress.ts +19 -11
  42. package/src/composites/sidecar-app.ts +17 -12
  43. package/src/composites/stateful-app.ts +21 -12
  44. package/src/composites/web-app.ts +25 -21
  45. package/src/composites/worker-pool.ts +40 -26
  46. package/src/composites/workload-identity-sa.ts +22 -14
  47. package/src/composites/workload-identity-service-account.ts +22 -16
  48. package/src/plugin.ts +130 -614
  49. package/src/serializer.ts +3 -0
  50. package/src/skills/chant-k8s-deployment-strategies.md +183 -0
  51. package/src/skills/chant-k8s-gke.md +55 -0
  52. package/src/skills/chant-k8s-patterns.md +245 -0
  53. package/src/skills/chant-k8s-security.md +237 -0
  54. package/src/skills/chant-k8s.md +305 -0
package/src/plugin.ts CHANGED
@@ -6,10 +6,13 @@
6
6
  */
7
7
 
8
8
  import { createRequire } from "module";
9
- import type { LexiconPlugin, SkillDefinition, InitTemplateSet } from "@intentius/chant/lexicon";
9
+ import type { LexiconPlugin, InitTemplateSet, ResourceMetadata } from "@intentius/chant/lexicon";
10
10
  const require = createRequire(import.meta.url);
11
11
  import type { LintRule } from "@intentius/chant/lint/rule";
12
- import type { PostSynthCheck } from "@intentius/chant/lint/post-synth";
12
+ import { discoverPostSynthChecks } from "@intentius/chant/lint/discover";
13
+ import { createSkillsLoader, createDiffTool, createCatalogResource } from "@intentius/chant/lexicon-plugin-helpers";
14
+ import { join, dirname } from "path";
15
+ import { fileURLToPath } from "url";
13
16
  import { k8sSerializer } from "./serializer";
14
17
 
15
18
  export const k8sPlugin: LexiconPlugin = {
@@ -23,36 +26,9 @@ export const k8sPlugin: LexiconPlugin = {
23
26
  return [hardcodedNamespaceRule, latestImageTagRule, missingResourceLimitsRule];
24
27
  },
25
28
 
26
- postSynthChecks(): PostSynthCheck[] {
27
- const { wk8005 } = require("./lint/post-synth/wk8005");
28
- const { wk8006 } = require("./lint/post-synth/wk8006");
29
- const { wk8041 } = require("./lint/post-synth/wk8041");
30
- const { wk8042 } = require("./lint/post-synth/wk8042");
31
- const { wk8101 } = require("./lint/post-synth/wk8101");
32
- const { wk8102 } = require("./lint/post-synth/wk8102");
33
- const { wk8103 } = require("./lint/post-synth/wk8103");
34
- const { wk8104 } = require("./lint/post-synth/wk8104");
35
- const { wk8105 } = require("./lint/post-synth/wk8105");
36
- const { wk8201 } = require("./lint/post-synth/wk8201");
37
- const { wk8202 } = require("./lint/post-synth/wk8202");
38
- const { wk8203 } = require("./lint/post-synth/wk8203");
39
- const { wk8204 } = require("./lint/post-synth/wk8204");
40
- const { wk8205 } = require("./lint/post-synth/wk8205");
41
- const { wk8207 } = require("./lint/post-synth/wk8207");
42
- const { wk8208 } = require("./lint/post-synth/wk8208");
43
- const { wk8209 } = require("./lint/post-synth/wk8209");
44
- const { wk8301 } = require("./lint/post-synth/wk8301");
45
- const { wk8302 } = require("./lint/post-synth/wk8302");
46
- const { wk8303 } = require("./lint/post-synth/wk8303");
47
- const { wk8304 } = require("./lint/post-synth/wk8304");
48
- const { wk8305 } = require("./lint/post-synth/wk8305");
49
- const { wk8306 } = require("./lint/post-synth/wk8306");
50
- return [
51
- wk8005, wk8006, wk8041, wk8042,
52
- wk8101, wk8102, wk8103, wk8104, wk8105,
53
- wk8201, wk8202, wk8203, wk8204, wk8205, wk8207, wk8208, wk8209,
54
- wk8301, wk8302, wk8303, wk8304, wk8305, wk8306,
55
- ];
29
+ postSynthChecks() {
30
+ const postSynthDir = join(dirname(fileURLToPath(import.meta.url)), "lint", "post-synth");
31
+ return discoverPostSynthChecks(postSynthDir, import.meta.url);
56
32
  },
57
33
 
58
34
  // K8s YAML has no template interpolation functions like CloudFormation's
@@ -299,49 +275,103 @@ export const service = new Service({
299
275
  console.error(`Packaged ${stats.resources} resources, ${stats.ruleCount} rules, ${stats.skillCount} skills`);
300
276
  },
301
277
 
302
- mcpTools() {
303
- return [
304
- {
305
- name: "diff",
306
- description: "Compare current build output against previous output for Kubernetes manifests",
307
- inputSchema: {
308
- type: "object" as const,
309
- properties: {
310
- path: {
311
- type: "string",
312
- description: "Path to the infrastructure project directory",
313
- },
314
- },
315
- },
316
- async handler(params: Record<string, unknown>): Promise<unknown> {
317
- const { diffCommand } = await import("@intentius/chant/cli/commands/diff");
318
- const result = await diffCommand({
319
- path: (params.path as string) ?? ".",
320
- serializers: [k8sSerializer],
278
+ async describeResources(options: {
279
+ environment: string;
280
+ buildOutput: string;
281
+ entityNames: string[];
282
+ }): Promise<Record<string, ResourceMetadata>> {
283
+ const { getRuntime } = await import("@intentius/chant/runtime-adapter");
284
+ const rt = getRuntime();
285
+ const resources: Record<string, ResourceMetadata> = {};
286
+
287
+ // Resolve namespace from environment (convention: env name = namespace)
288
+ const namespace = options.environment;
289
+
290
+ // Parse build output to extract kind/name pairs for each entity
291
+ let manifests: Array<{ kind: string; metadata: { name: string; namespace?: string }; apiVersion: string }> = [];
292
+ try {
293
+ // K8s build output is YAML with --- separators
294
+ const docs = options.buildOutput.split(/^---$/m).filter((d) => d.trim());
295
+ for (const doc of docs) {
296
+ // Simple YAML parsing — look for kind: and metadata.name:
297
+ const kindMatch = doc.match(/^kind:\s*(.+)$/m);
298
+ const nameMatch = doc.match(/^\s+name:\s*(.+)$/m);
299
+ const apiVersionMatch = doc.match(/^apiVersion:\s*(.+)$/m);
300
+ if (kindMatch && nameMatch && apiVersionMatch) {
301
+ manifests.push({
302
+ kind: kindMatch[1].trim(),
303
+ metadata: { name: nameMatch[1].trim() },
304
+ apiVersion: apiVersionMatch[1].trim(),
321
305
  });
322
- return result;
323
- },
324
- },
325
- ];
306
+ }
307
+ }
308
+ } catch {
309
+ // If build output parsing fails, skip
310
+ }
311
+
312
+ // Query each resource
313
+ for (const entityName of options.entityNames) {
314
+ // Find the corresponding manifest
315
+ const manifest = manifests.find((m) => {
316
+ // Try matching by entity name convention
317
+ return m.metadata.name === entityName ||
318
+ entityName.toLowerCase().includes(m.metadata.name.toLowerCase());
319
+ });
320
+
321
+ if (!manifest) continue;
322
+
323
+ // Build kubectl resource type from apiVersion + kind
324
+ const resourceType = manifest.kind.toLowerCase();
325
+ const getResult = await rt.spawn([
326
+ "kubectl", "get", resourceType, manifest.metadata.name,
327
+ "-n", namespace,
328
+ "-o", "json",
329
+ ]);
330
+
331
+ if (getResult.exitCode !== 0) continue;
332
+
333
+ try {
334
+ const obj = JSON.parse(getResult.stdout) as {
335
+ metadata: { name: string; uid: string; creationTimestamp: string };
336
+ status?: { phase?: string; conditions?: Array<{ type: string; status: string }> };
337
+ };
338
+
339
+ // Derive status from conditions or phase
340
+ let status = "Unknown";
341
+ if (obj.status?.phase) {
342
+ status = obj.status.phase;
343
+ } else if (obj.status?.conditions) {
344
+ const ready = obj.status.conditions.find((c) => c.type === "Ready" || c.type === "Available");
345
+ status = ready?.status === "True" ? "Ready" : "NotReady";
346
+ }
347
+
348
+ // Build attributes, scrubbing sensitive data
349
+ const attributes: Record<string, unknown> = {
350
+ uid: obj.metadata.uid,
351
+ };
352
+
353
+ resources[entityName] = {
354
+ type: `${manifest.apiVersion}/${manifest.kind}`,
355
+ physicalId: obj.metadata.name,
356
+ status,
357
+ lastUpdated: obj.metadata.creationTimestamp,
358
+ attributes,
359
+ };
360
+ } catch {
361
+ // Skip parse failures
362
+ }
363
+ }
364
+
365
+ return resources;
366
+ },
367
+
368
+ mcpTools() {
369
+ return [createDiffTool(k8sSerializer, "Compare current build output against previous output for Kubernetes manifests")];
326
370
  },
327
371
 
328
372
  mcpResources() {
329
373
  return [
330
- {
331
- uri: "resource-catalog",
332
- name: "Kubernetes Resource Catalog",
333
- description: "JSON list of all supported Kubernetes resource types",
334
- mimeType: "application/json",
335
- async handler(): Promise<string> {
336
- const lexicon = require("./generated/lexicon-k8s.json") as Record<string, { resourceType: string; kind: string }>;
337
- const entries = Object.entries(lexicon).map(([className, entry]) => ({
338
- className,
339
- resourceType: entry.resourceType,
340
- kind: entry.kind,
341
- }));
342
- return JSON.stringify(entries);
343
- },
344
- },
374
+ createCatalogResource(import.meta.url, "Kubernetes Resource Catalog", "JSON list of all supported Kubernetes resource types", "lexicon-k8s.json"),
345
375
  {
346
376
  uri: "examples/basic-deployment",
347
377
  name: "Basic Deployment Example",
@@ -385,254 +415,11 @@ export const service = new Service({
385
415
  ];
386
416
  },
387
417
 
388
- skills(): SkillDefinition[] {
389
- return [
418
+ skills: createSkillsLoader(import.meta.url, [
390
419
  {
420
+ file: "chant-k8s.md",
391
421
  name: "chant-k8s",
392
422
  description: "Kubernetes manifest lifecycle — scaffold, generate, lint, build, apply, troubleshoot, rollback",
393
- content: `---
394
- skill: chant-k8s
395
- description: Build, validate, and deploy Kubernetes manifests from a chant project
396
- user-invocable: true
397
- ---
398
-
399
- # Kubernetes Operational Playbook
400
-
401
- ## How chant and Kubernetes relate
402
-
403
- chant is a **synthesis-only** tool — it compiles TypeScript source files into Kubernetes YAML manifests. chant does NOT call the Kubernetes API. Your job as an agent is to bridge the two:
404
-
405
- - Use **chant** for: build, lint, diff (local YAML comparison)
406
- - Use **kubectl / k8s API** for: apply, rollback, monitoring, troubleshooting
407
-
408
- The source of truth for infrastructure is the TypeScript in \`src/\`. The generated YAML manifests are intermediate artifacts — never edit them by hand.
409
-
410
- ## Scaffolding a new project
411
-
412
- ### Initialize with a template
413
-
414
- \`\`\`bash
415
- chant init --lexicon k8s # default: Deployment + Service
416
- chant init --lexicon k8s --template microservice # Deployment + Service + HPA + PDB
417
- chant init --lexicon k8s --template stateful # StatefulSet + PVC + Service
418
- \`\`\`
419
-
420
- ### Available templates
421
-
422
- | Template | What it generates | Best for |
423
- |----------|-------------------|----------|
424
- | *(default)* | Deployment + Service | Simple stateless apps |
425
- | \`microservice\` | Deployment + Service + HPA + PDB | Production microservices |
426
- | \`stateful\` | StatefulSet + PVC + headless Service | Databases, caches |
427
-
428
- ## Build and validate
429
-
430
- ### Build manifests
431
-
432
- \`\`\`bash
433
- chant build src/ --output manifests.yaml
434
- \`\`\`
435
-
436
- Options:
437
- - \`--format yaml\` — emit YAML (default for K8s)
438
- - \`--watch\` — rebuild on source changes
439
- - \`--output <path>\` — write to a specific file
440
-
441
- ### Lint the source
442
-
443
- \`\`\`bash
444
- chant lint src/
445
- \`\`\`
446
-
447
- ### What each step catches
448
-
449
- | Step | Catches | When to run |
450
- |------|---------|-------------|
451
- | \`chant lint\` | Hardcoded namespaces (WK8001) | Every edit |
452
- | \`chant build\` | Post-synth: secrets in env (WK8005), latest tags (WK8006), API keys (WK8041), missing probes (WK8301), no resource limits (WK8201), privileged containers (WK8202), and more | Before apply |
453
- | \`kubectl --dry-run=server\` | K8s API validation: schema errors, admission webhooks | Before production apply |
454
-
455
- ## Deploying to Kubernetes
456
-
457
- ### Apply manifests
458
-
459
- \`\`\`bash
460
- # Build
461
- chant build src/ --output manifests.yaml
462
-
463
- # Dry run first
464
- kubectl apply -f manifests.yaml --dry-run=server
465
-
466
- # Apply
467
- kubectl apply -f manifests.yaml
468
- \`\`\`
469
-
470
- ### Check rollout status
471
-
472
- \`\`\`bash
473
- kubectl rollout status deployment/my-app
474
- \`\`\`
475
-
476
- ### Rollback
477
-
478
- \`\`\`bash
479
- kubectl rollout undo deployment/my-app
480
- kubectl rollout undo deployment/my-app --to-revision=2
481
- \`\`\`
482
-
483
- ## Debugging strategies
484
-
485
- ### Check pod status and events
486
-
487
- \`\`\`bash
488
- # Overview
489
- kubectl get pods -l app.kubernetes.io/name=my-app
490
- kubectl get events --sort-by=.lastTimestamp -n <namespace>
491
-
492
- # Deep dive into a specific pod
493
- kubectl describe pod <pod-name>
494
-
495
- # Logs (current and previous crash)
496
- kubectl logs <pod-name>
497
- kubectl logs <pod-name> --previous
498
- kubectl logs <pod-name> -c <container-name> # specific container
499
- kubectl logs deployment/my-app --all-containers
500
-
501
- # Debug containers (K8s 1.25+)
502
- kubectl debug <pod-name> -it --image=busybox --target=<container>
503
-
504
- # Port-forwarding for local testing
505
- kubectl port-forward svc/my-app 8080:80
506
- kubectl port-forward pod/<pod-name> 8080:8080
507
- \`\`\`
508
-
509
- ### Common error patterns
510
-
511
- | Status | Meaning | Diagnostic command | Typical fix |
512
- |--------|---------|-------------------|-------------|
513
- | Pending | Not scheduled | \`kubectl describe pod\` → Events | Check resource requests, node selectors, taints, PVC binding |
514
- | CrashLoopBackOff | App crashing on start | \`kubectl logs --previous\` | Fix app startup, check probe config, increase initialDelaySeconds |
515
- | ImagePullBackOff | Image not found | \`kubectl describe pod\` → Events | Verify image name/tag, check imagePullSecrets, registry auth |
516
- | OOMKilled | Out of memory | \`kubectl describe pod\` → Last State | Increase memory limit, profile app memory usage |
517
- | Evicted | Node disk/memory pressure | \`kubectl describe node\` | Increase limits, add node capacity, check for log/tmp bloat |
518
- | CreateContainerError | Container config issue | \`kubectl describe pod\` → Events | Check volume mounts, configmap/secret refs, security context |
519
- | Init:CrashLoopBackOff | Init container failing | \`kubectl logs -c <init-container>\` | Fix init container command, check dependencies |
520
-
521
- ## Production safety
522
-
523
- ### Pre-apply validation
524
-
525
- \`\`\`bash
526
- # Always diff before applying
527
- kubectl diff -f manifests.yaml
528
-
529
- # Server-side dry run (validates with admission webhooks)
530
- kubectl apply -f manifests.yaml --dry-run=server
531
-
532
- # Client-side dry run (fast, but no webhook validation)
533
- kubectl apply -f manifests.yaml --dry-run=client
534
- \`\`\`
535
-
536
- ### Deployment strategies
537
-
538
- - **RollingUpdate** (default): Gradually replaces pods. Set \`maxSurge\` and \`maxUnavailable\`.
539
- - **Recreate**: All pods terminated before new ones created. Use for stateful apps that cannot run multiple versions.
540
- - **Canary**: Deploy a second Deployment with 1 replica + same selector labels. Route percentage via Ingress annotations or service mesh.
541
- - **Blue/Green**: Two full Deployments (blue/green), switch Service selector between them.
542
-
543
- ## Choosing the Right Composite
544
-
545
- Composites are higher-level functions that produce multiple coordinated K8s resources from a single call. They return plain prop objects — not class instances.
546
-
547
- ### Decision Tree
548
-
549
- | Need | Composite | Resources |
550
- |------|-----------|-----------|
551
- | Stateless web app | **WebApp** | Deployment + Service + optional Ingress + optional PDB |
552
- | Stateful database/cache | **StatefulApp** | StatefulSet + headless Service + PVC + optional PDB |
553
- | Production HTTP service with autoscaling | **AutoscaledService** | Deployment + Service + HPA + PDB |
554
- | Background queue workers | **WorkerPool** | Deployment + RBAC + optional ConfigMap + optional HPA + optional PDB |
555
- | Scheduled jobs | **CronWorkload** | CronJob + RBAC |
556
- | One-shot batch jobs | **BatchJob** | Job + optional RBAC |
557
- | App with ConfigMap/Secret mounts | **ConfiguredApp** | Deployment + Service + optional ConfigMap |
558
- | Multi-container sidecar patterns | **SidecarApp** | Deployment + Service |
559
- | App with Prometheus monitoring | **MonitoredService** | Deployment + Service + ServiceMonitor + optional PrometheusRule |
560
- | App with fine-grained network policies | **NetworkIsolatedApp** | Deployment + Service + NetworkPolicy |
561
- | Namespace with quotas and isolation | **NamespaceEnv** | Namespace + ResourceQuota + LimitRange + NetworkPolicy |
562
- | Per-node agent (custom) | **NodeAgent** | DaemonSet + RBAC + optional ConfigMap |
563
- | Multi-host TLS Ingress (cert-manager) | **SecureIngress** | Ingress + optional Certificate |
564
- | EKS IRSA ServiceAccount | **IrsaServiceAccount** | ServiceAccount + optional RBAC |
565
- | AWS ALB Ingress | **AlbIngress** | Ingress with ALB annotations |
566
- | EBS StorageClass | **EbsStorageClass** | StorageClass (ebs.csi.aws.com) |
567
- | EFS StorageClass | **EfsStorageClass** | StorageClass (efs.csi.aws.com) |
568
- | Fluent Bit for CloudWatch | **FluentBitAgent** | DaemonSet + RBAC + ConfigMap |
569
- | ExternalDNS for Route53 | **ExternalDnsAgent** | Deployment + IRSA SA + ClusterRole |
570
- | ADOT for CloudWatch/X-Ray | **AdotCollector** | DaemonSet + RBAC + ConfigMap |
571
-
572
- ### Hardening options (available on Deployment-based composites)
573
-
574
- - \`minAvailable\` — creates a PodDisruptionBudget (WebApp, StatefulApp, WorkerPool; AutoscaledService always has one)
575
- - \`initContainers\` — run before main containers (WebApp, StatefulApp, AutoscaledService, ConfiguredApp, SidecarApp)
576
- - \`securityContext\` — container security settings (WebApp, StatefulApp, AutoscaledService, WorkerPool)
577
- - \`terminationGracePeriodSeconds\` — graceful shutdown (WebApp, StatefulApp, AutoscaledService, WorkerPool)
578
- - \`priorityClassName\` — pod scheduling priority (WebApp, StatefulApp, AutoscaledService, WorkerPool)
579
-
580
- ### Common patterns across all composites
581
-
582
- - All resources carry \`app.kubernetes.io/name\`, \`app.kubernetes.io/managed-by: chant\`, and \`app.kubernetes.io/component\` labels
583
- - Pass \`labels: { team: "platform" }\` to add extra labels to all resources
584
- - Pass \`namespace: "prod"\` to set namespace on all namespaced resources
585
- - Pass \`env: [{ name: "KEY", value: "val" }]\` for container environment variables
586
-
587
- ## Troubleshooting reference table
588
-
589
- | Symptom | Likely cause | Resolution |
590
- |---------|-------------|------------|
591
- | Pod stuck in Pending | Insufficient CPU/memory on nodes | Scale up cluster or reduce resource requests |
592
- | Pod stuck in Pending | PVC not bound | Check StorageClass exists, PV available |
593
- | Pod stuck in Pending | Node selector/affinity mismatch | Verify node labels match selectors |
594
- | Pod stuck in ContainerCreating | ConfigMap/Secret not found | Ensure referenced ConfigMaps/Secrets exist |
595
- | Service returns 503 | No ready endpoints | Check pod readiness probes, selector match |
596
- | Ingress returns 404 | Backend service not found | Check Ingress rules, service name/port |
597
- | HPA not scaling | Metrics server not installed | Install metrics-server |
598
- | HPA not scaling | Resource requests not set | Add CPU/memory requests to containers |
599
- | CronJob not running | Invalid cron expression | Validate cron syntax (5-field format) |
600
- | NetworkPolicy blocking | Default deny applied | Add explicit allow rules for required traffic |
601
- | RBAC permission denied | Missing Role/RoleBinding | Check ServiceAccount bindings and verb permissions |
602
-
603
- ## Quick reference
604
-
605
- \`\`\`bash
606
- # Build
607
- chant build src/ --output manifests.yaml
608
-
609
- # Lint
610
- chant lint src/
611
-
612
- # Validate
613
- kubectl apply -f manifests.yaml --dry-run=server
614
-
615
- # Diff
616
- kubectl diff -f manifests.yaml
617
-
618
- # Apply
619
- kubectl apply -f manifests.yaml
620
-
621
- # Status
622
- kubectl get pods,svc,deploy
623
-
624
- # Logs
625
- kubectl logs deployment/my-app
626
-
627
- # Rollback
628
- kubectl rollout undo deployment/my-app
629
-
630
- # Debug
631
- kubectl describe pod <name>
632
- kubectl logs <name> --previous
633
- kubectl get events --sort-by=.lastTimestamp
634
- \`\`\`
635
- `,
636
423
  triggers: [
637
424
  { type: "file-pattern", value: "**/*.k8s.ts" },
638
425
  { type: "file-pattern", value: "**/k8s/**/*.ts" },
@@ -722,254 +509,9 @@ const { job, serviceAccount, role, roleBinding } = BatchJob({
722
509
  ],
723
510
  },
724
511
  {
512
+ file: "chant-k8s-patterns.md",
725
513
  name: "chant-k8s-patterns",
726
514
  description: "Advanced K8s patterns — sidecars, observability, TLS, network isolation, config/secret mounting",
727
- content: `---
728
- skill: chant-k8s-patterns
729
- description: Advanced Kubernetes deployment patterns and composites
730
- user-invocable: true
731
- ---
732
-
733
- # Advanced Kubernetes Patterns
734
-
735
- ## Sidecar Patterns
736
-
737
- ### SidecarApp — multi-container Deployment
738
-
739
- \`\`\`typescript
740
- import { SidecarApp } from "@intentius/chant-lexicon-k8s";
741
-
742
- // Envoy sidecar proxy
743
- const { deployment, service } = SidecarApp({
744
- name: "api",
745
- image: "api:1.0",
746
- port: 8080,
747
- sidecars: [
748
- {
749
- name: "envoy",
750
- image: "envoyproxy/envoy:v1.28",
751
- ports: [{ containerPort: 9901, name: "admin" }],
752
- resources: { requests: { cpu: "100m", memory: "128Mi" }, limits: { cpu: "200m", memory: "256Mi" } },
753
- },
754
- ],
755
- initContainers: [
756
- { name: "migrate", image: "api:1.0", command: ["python", "manage.py", "migrate"] },
757
- ],
758
- sharedVolumes: [{ name: "tmp", emptyDir: {} }],
759
- });
760
- \`\`\`
761
-
762
- Common sidecar use cases:
763
- - **Envoy proxy** — service mesh, mTLS, traffic management
764
- - **Log forwarder** — Fluent Bit sidecar for app-specific log routing
765
- - **Auth proxy** — OAuth2 Proxy for authentication
766
- - **Config watcher** — reload config on ConfigMap changes
767
-
768
- ## Config and Secret Mounting
769
-
770
- ### ConfiguredApp — automatic volume wiring
771
-
772
- \`\`\`typescript
773
- import { ConfiguredApp } from "@intentius/chant-lexicon-k8s";
774
-
775
- const { deployment, service, configMap } = ConfiguredApp({
776
- name: "api",
777
- image: "api:1.0",
778
- port: 8080,
779
- // Mount ConfigMap as volume
780
- configData: { "app.conf": "key=value\\nother=setting" },
781
- configMountPath: "/etc/api",
782
- // Mount existing Secret as volume
783
- secretName: "api-creds",
784
- secretMountPath: "/secrets",
785
- // Inject as environment variables
786
- envFrom: { secretRef: "api-env-secret", configMapRef: "api-env-config" },
787
- // Run migrations before the app starts
788
- initContainers: [
789
- { name: "migrate", image: "api:1.0", command: ["./migrate.sh"] },
790
- ],
791
- });
792
- \`\`\`
793
-
794
- ### Volume patterns
795
-
796
- | Pattern | Use Case | ConfiguredApp Props |
797
- |---------|----------|---------------------|
798
- | ConfigMap as file | Config files, templates | \`configData\` + \`configMountPath\` |
799
- | Secret as file | TLS certs, credentials | \`secretName\` + \`secretMountPath\` |
800
- | ConfigMap as env | Simple key-value config | \`envFrom.configMapRef\` |
801
- | Secret as env | Database URLs, API keys | \`envFrom.secretRef\` |
802
-
803
- ## TLS / cert-manager
804
-
805
- ### SecureIngress — multi-host TLS with cert-manager
806
-
807
- \`\`\`typescript
808
- import { SecureIngress } from "@intentius/chant-lexicon-k8s";
809
-
810
- const { ingress, certificate } = SecureIngress({
811
- name: "app-ingress",
812
- hosts: [
813
- {
814
- hostname: "api.example.com",
815
- paths: [
816
- { path: "/v1", serviceName: "api-v1", servicePort: 80 },
817
- { path: "/v2", serviceName: "api-v2", servicePort: 80 },
818
- ],
819
- },
820
- {
821
- hostname: "admin.example.com",
822
- paths: [{ path: "/", serviceName: "admin", servicePort: 80 }],
823
- },
824
- ],
825
- clusterIssuer: "letsencrypt-prod",
826
- ingressClassName: "nginx",
827
- });
828
- \`\`\`
829
-
830
- Features:
831
- - Multiple hosts and paths per Ingress
832
- - Automatic cert-manager Certificate when \`clusterIssuer\` set
833
- - TLS secret auto-provisioned by cert-manager
834
-
835
- ### cert-manager setup (prerequisite)
836
-
837
- \`\`\`bash
838
- # Install cert-manager
839
- kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml
840
-
841
- # Create ClusterIssuer for Let's Encrypt
842
- kubectl apply -f - <<EOF
843
- apiVersion: cert-manager.io/v1
844
- kind: ClusterIssuer
845
- metadata:
846
- name: letsencrypt-prod
847
- spec:
848
- acme:
849
- server: https://acme-v02.api.letsencrypt.org/directory
850
- email: admin@example.com
851
- privateKeySecretRef:
852
- name: letsencrypt-prod-key
853
- solvers:
854
- - http01:
855
- ingress:
856
- class: nginx
857
- EOF
858
- \`\`\`
859
-
860
- ## Observability
861
-
862
- ### MonitoredService — Prometheus monitoring
863
-
864
- \`\`\`typescript
865
- import { MonitoredService } from "@intentius/chant-lexicon-k8s";
866
-
867
- const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService({
868
- name: "api",
869
- image: "api:1.0",
870
- port: 8080,
871
- metricsPort: 9090,
872
- metricsPath: "/metrics",
873
- scrapeInterval: "15s",
874
- alertRules: [
875
- {
876
- name: "HighErrorRate",
877
- expr: 'rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) > 0.05',
878
- for: "5m",
879
- severity: "critical",
880
- annotations: { summary: "High error rate on {{ $labels.instance }}" },
881
- },
882
- {
883
- name: "HighLatency",
884
- expr: 'histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) > 1',
885
- for: "10m",
886
- severity: "warning",
887
- },
888
- ],
889
- });
890
- \`\`\`
891
-
892
- ### Prerequisites
893
-
894
- - Prometheus Operator installed (for ServiceMonitor/PrometheusRule CRDs)
895
- - Prometheus configured to discover ServiceMonitors
896
-
897
- ## Network Isolation
898
-
899
- ### NetworkIsolatedApp — per-app firewall rules
900
-
901
- \`\`\`typescript
902
- import { NetworkIsolatedApp } from "@intentius/chant-lexicon-k8s";
903
-
904
- const { deployment, service, networkPolicy } = NetworkIsolatedApp({
905
- name: "api",
906
- image: "api:1.0",
907
- port: 8080,
908
- allowIngressFrom: [
909
- { podSelector: { "app.kubernetes.io/name": "frontend" } },
910
- { namespaceSelector: { "kubernetes.io/metadata.name": "monitoring" } },
911
- ],
912
- allowEgressTo: [
913
- { podSelector: { "app.kubernetes.io/name": "postgres" }, ports: [{ port: 5432 }] },
914
- { podSelector: { "app.kubernetes.io/name": "redis" }, ports: [{ port: 6379 }] },
915
- ],
916
- });
917
- \`\`\`
918
-
919
- ### Combining with NamespaceEnv
920
-
921
- Use NamespaceEnv for namespace-level default-deny, then NetworkIsolatedApp for per-app allow rules:
922
-
923
- \`\`\`typescript
924
- // Namespace: deny all by default
925
- const ns = NamespaceEnv({ name: "prod", defaultDenyIngress: true, defaultDenyEgress: true });
926
-
927
- // App: allow specific traffic
928
- const app = NetworkIsolatedApp({
929
- name: "api",
930
- image: "api:1.0",
931
- namespace: "prod",
932
- allowIngressFrom: [{ podSelector: { "app.kubernetes.io/name": "gateway" } }],
933
- allowEgressTo: [{ podSelector: { "app.kubernetes.io/name": "db" }, ports: [{ port: 5432 }] }],
934
- });
935
- \`\`\`
936
-
937
- ## Blue/Green and Canary
938
-
939
- These patterns use standard K8s resources — no special composite needed.
940
-
941
- ### Blue/Green
942
-
943
- \`\`\`typescript
944
- // Two Deployments with different versions
945
- const blue = WebApp({ name: "app-blue", image: "app:1.0", labels: { version: "blue" } });
946
- const green = WebApp({ name: "app-green", image: "app:2.0", labels: { version: "green" } });
947
-
948
- // Service points to active version — switch by changing selector
949
- // Active: blue → green (update the Service selector)
950
- \`\`\`
951
-
952
- ### Canary
953
-
954
- \`\`\`typescript
955
- // Main deployment (90% traffic)
956
- const main = AutoscaledService({ name: "app", image: "app:1.0", minReplicas: 9, maxReplicas: 20, ... });
957
-
958
- // Canary deployment (10% traffic) — same app label, fewer replicas
959
- const canary = WebApp({ name: "app-canary", image: "app:2.0", replicas: 1, labels: { track: "canary" } });
960
- // Both share the same Service selector ("app.kubernetes.io/name": "app") for traffic splitting
961
- \`\`\`
962
-
963
- ## Gateway API (future direction)
964
-
965
- Gateway API is the successor to Ingress. Key differences:
966
- - **HTTPRoute** replaces Ingress rules
967
- - **Gateway** replaces IngressClass
968
- - Built-in traffic splitting, header matching, URL rewriting
969
- - Currently in beta — use Ingress/SecureIngress/AlbIngress for production today
970
-
971
- When Gateway API reaches GA, new composites will be added. For now, use CRD import if you need Gateway API resources.
972
- `,
973
515
  triggers: [
974
516
  { type: "context", value: "sidecar" },
975
517
  { type: "context", value: "init-container" },
@@ -1019,26 +561,17 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1019
561
  },
1020
562
  ],
1021
563
  },
1022
- ];
1023
-
1024
- // Load file-based skills from src/skills/
1025
- const { readFileSync } = require("fs");
1026
- const { join, dirname } = require("path");
1027
- const { fileURLToPath } = require("url");
1028
- const dir = dirname(fileURLToPath(import.meta.url));
1029
-
1030
- const skillFiles = [
1031
564
  {
1032
- file: "kubernetes-patterns.md",
1033
- name: "kubernetes-patterns",
565
+ file: "chant-k8s-deployment-strategies.md",
566
+ name: "chant-k8s-deployment-strategies",
1034
567
  description: "Kubernetes deployment strategies, stateful workloads, RBAC, and networking patterns",
1035
568
  triggers: [
1036
- { type: "context" as const, value: "deployment strategy" },
1037
- { type: "context" as const, value: "statefulset" },
1038
- { type: "context" as const, value: "rbac" },
1039
- { type: "context" as const, value: "network policy" },
1040
- { type: "context" as const, value: "rolling update" },
1041
- { type: "context" as const, value: "blue green" },
569
+ { type: "context", value: "deployment strategy" },
570
+ { type: "context", value: "statefulset" },
571
+ { type: "context", value: "rbac" },
572
+ { type: "context", value: "network policy" },
573
+ { type: "context", value: "rolling update" },
574
+ { type: "context", value: "blue green" },
1042
575
  ],
1043
576
  parameters: [],
1044
577
  examples: [
@@ -1050,15 +583,15 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1050
583
  ],
1051
584
  },
1052
585
  {
1053
- file: "kubernetes-security.md",
1054
- name: "kubernetes-security",
586
+ file: "chant-k8s-security.md",
587
+ name: "chant-k8s-security",
1055
588
  description: "Kubernetes pod security, image scanning, network policies, and secrets management",
1056
589
  triggers: [
1057
- { type: "context" as const, value: "k8s security" },
1058
- { type: "context" as const, value: "pod security" },
1059
- { type: "context" as const, value: "image security" },
1060
- { type: "context" as const, value: "k8s secrets" },
1061
- { type: "context" as const, value: "security context" },
590
+ { type: "context", value: "k8s security" },
591
+ { type: "context", value: "pod security" },
592
+ { type: "context", value: "image security" },
593
+ { type: "context", value: "k8s secrets" },
594
+ { type: "context", value: "security context" },
1062
595
  ],
1063
596
  parameters: [],
1064
597
  examples: [
@@ -1074,11 +607,11 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1074
607
  name: "chant-k8s-eks",
1075
608
  description: "EKS-specific Kubernetes composites — IRSA, ALB, EBS/EFS, Fluent Bit, ExternalDNS, ADOT",
1076
609
  triggers: [
1077
- { type: "context" as const, value: "eks composites" },
1078
- { type: "context" as const, value: "irsa" },
1079
- { type: "context" as const, value: "alb ingress" },
1080
- { type: "context" as const, value: "ebs storage" },
1081
- { type: "context" as const, value: "karpenter" },
610
+ { type: "context", value: "eks composites" },
611
+ { type: "context", value: "irsa" },
612
+ { type: "context", value: "alb ingress" },
613
+ { type: "context", value: "ebs storage" },
614
+ { type: "context", value: "karpenter" },
1082
615
  ],
1083
616
  parameters: [],
1084
617
  examples: [
@@ -1094,9 +627,9 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1094
627
  name: "chant-k8s-gke",
1095
628
  description: "GKE-specific Kubernetes composites — Workload Identity, GCE PD, Filestore, FluentBit, OTel, ExternalDNS, Gateway",
1096
629
  triggers: [
1097
- { type: "context" as const, value: "gke composites" },
1098
- { type: "context" as const, value: "workload identity gke" },
1099
- { type: "context" as const, value: "config connector k8s" },
630
+ { type: "context", value: "gke composites" },
631
+ { type: "context", value: "workload identity gke" },
632
+ { type: "context", value: "config connector k8s" },
1100
633
  ],
1101
634
  parameters: [],
1102
635
  examples: [
@@ -1112,9 +645,9 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1112
645
  name: "chant-k8s-aks",
1113
646
  description: "AKS-specific Kubernetes composites — Workload Identity, AGIC, Azure Disk/File, ExternalDNS, Azure Monitor",
1114
647
  triggers: [
1115
- { type: "context" as const, value: "aks composites" },
1116
- { type: "context" as const, value: "workload identity aks" },
1117
- { type: "context" as const, value: "agic ingress" },
648
+ { type: "context", value: "aks composites" },
649
+ { type: "context", value: "workload identity aks" },
650
+ { type: "context", value: "agic ingress" },
1118
651
  ],
1119
652
  parameters: [],
1120
653
  examples: [
@@ -1125,22 +658,5 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
1125
658
  },
1126
659
  ],
1127
660
  },
1128
- ];
1129
-
1130
- for (const skill of skillFiles) {
1131
- try {
1132
- const content = readFileSync(join(dir, "skills", skill.file), "utf-8");
1133
- skills.push({
1134
- name: skill.name,
1135
- description: skill.description,
1136
- content,
1137
- triggers: skill.triggers,
1138
- parameters: skill.parameters,
1139
- examples: skill.examples,
1140
- });
1141
- } catch { /* skip missing skills */ }
1142
- }
1143
-
1144
- return skills;
1145
- },
661
+ ]),
1146
662
  };