@intentius/chant-lexicon-k8s 0.0.14 → 0.0.16
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.
- package/dist/integrity.json +8 -4
- package/dist/manifest.json +1 -1
- package/dist/rules/latest-image-tag.ts +121 -0
- package/dist/rules/missing-resource-limits.ts +111 -0
- package/dist/rules/wk8204.ts +33 -1
- package/dist/rules/wk8304.ts +70 -0
- package/dist/rules/wk8305.ts +115 -0
- package/dist/rules/wk8306.ts +50 -0
- package/package.json +27 -24
- package/src/codegen/docs.ts +1 -1
- package/src/composites/adot-collector.ts +8 -2
- package/src/composites/agic-ingress.ts +148 -0
- package/src/composites/aks-external-dns-agent.ts +199 -0
- package/src/composites/alb-ingress.ts +2 -1
- package/src/composites/autoscaled-service.ts +25 -7
- package/src/composites/azure-disk-storage-class.ts +82 -0
- package/src/composites/azure-file-storage-class.ts +77 -0
- package/src/composites/azure-monitor-collector.ts +232 -0
- package/src/composites/batch-job.ts +36 -3
- package/src/composites/composites.test.ts +1060 -0
- package/src/composites/config-connector-context.ts +62 -0
- package/src/composites/configured-app.ts +6 -0
- package/src/composites/cron-workload.ts +6 -0
- package/src/composites/ebs-storage-class.ts +4 -4
- package/src/composites/external-dns-agent.ts +6 -0
- package/src/composites/filestore-storage-class.ts +79 -0
- package/src/composites/fluent-bit-agent.ts +5 -0
- package/src/composites/gce-ingress.ts +143 -0
- package/src/composites/gce-pd-storage-class.ts +85 -0
- package/src/composites/gke-external-dns-agent.ts +175 -0
- package/src/composites/gke-fluent-bit-agent.ts +219 -0
- package/src/composites/gke-gateway.ts +143 -0
- package/src/composites/gke-otel-collector.ts +229 -0
- package/src/composites/index.ts +31 -0
- package/src/composites/metrics-server.ts +1 -1
- package/src/composites/monitored-service.ts +6 -0
- package/src/composites/network-isolated-app.ts +6 -0
- package/src/composites/node-agent.ts +6 -0
- package/src/composites/security-context.ts +10 -0
- package/src/composites/sidecar-app.ts +6 -0
- package/src/composites/stateful-app.ts +4 -7
- package/src/composites/web-app.ts +4 -7
- package/src/composites/worker-pool.ts +4 -7
- package/src/composites/workload-identity-sa.ts +118 -0
- package/src/composites/workload-identity-service-account.ts +116 -0
- package/src/index.ts +20 -1
- package/src/lint/post-synth/post-synth.test.ts +362 -1
- package/src/lint/post-synth/wk8204.ts +33 -1
- package/src/lint/post-synth/wk8304.ts +70 -0
- package/src/lint/post-synth/wk8305.ts +115 -0
- package/src/lint/post-synth/wk8306.ts +50 -0
- package/src/lint/rules/latest-image-tag.ts +121 -0
- package/src/lint/rules/missing-resource-limits.ts +111 -0
- package/src/lint/rules/rules.test.ts +192 -0
- package/src/plugin.test.ts +2 -2
- package/src/plugin.ts +129 -209
- package/src/serializer.test.ts +120 -0
- package/src/serializer.ts +16 -4
- package/src/skills/chant-k8s-aks.md +146 -0
- package/src/skills/chant-k8s-gke.md +191 -0
- package/src/skills/kubernetes-patterns.md +183 -0
- package/src/skills/kubernetes-security.md +237 -0
- /package/{dist → src}/skills/chant-k8s-eks.md +0 -0
package/src/plugin.ts
CHANGED
|
@@ -18,7 +18,9 @@ export const k8sPlugin: LexiconPlugin = {
|
|
|
18
18
|
|
|
19
19
|
lintRules(): LintRule[] {
|
|
20
20
|
const { hardcodedNamespaceRule } = require("./lint/rules/hardcoded-namespace");
|
|
21
|
-
|
|
21
|
+
const { latestImageTagRule } = require("./lint/rules/latest-image-tag");
|
|
22
|
+
const { missingResourceLimitsRule } = require("./lint/rules/missing-resource-limits");
|
|
23
|
+
return [hardcodedNamespaceRule, latestImageTagRule, missingResourceLimitsRule];
|
|
22
24
|
},
|
|
23
25
|
|
|
24
26
|
postSynthChecks(): PostSynthCheck[] {
|
|
@@ -42,11 +44,14 @@ export const k8sPlugin: LexiconPlugin = {
|
|
|
42
44
|
const { wk8301 } = require("./lint/post-synth/wk8301");
|
|
43
45
|
const { wk8302 } = require("./lint/post-synth/wk8302");
|
|
44
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");
|
|
45
50
|
return [
|
|
46
51
|
wk8005, wk8006, wk8041, wk8042,
|
|
47
52
|
wk8101, wk8102, wk8103, wk8104, wk8105,
|
|
48
53
|
wk8201, wk8202, wk8203, wk8204, wk8205, wk8207, wk8208, wk8209,
|
|
49
|
-
wk8301, wk8302, wk8303,
|
|
54
|
+
wk8301, wk8302, wk8303, wk8304, wk8305, wk8306,
|
|
50
55
|
];
|
|
51
56
|
},
|
|
52
57
|
|
|
@@ -712,213 +717,6 @@ const { job, serviceAccount, role, roleBinding } = BatchJob({
|
|
|
712
717
|
command: ["python", "manage.py", "migrate"],
|
|
713
718
|
backoffLimit: 3,
|
|
714
719
|
ttlSecondsAfterFinished: 3600,
|
|
715
|
-
});`,
|
|
716
|
-
},
|
|
717
|
-
],
|
|
718
|
-
},
|
|
719
|
-
{
|
|
720
|
-
name: "chant-k8s-eks",
|
|
721
|
-
description: "EKS-specific Kubernetes composites — IRSA, ALB, EBS/EFS, Fluent Bit, ExternalDNS, ADOT",
|
|
722
|
-
content: `---
|
|
723
|
-
skill: chant-k8s-eks
|
|
724
|
-
description: EKS-specific Kubernetes patterns and composites
|
|
725
|
-
user-invocable: true
|
|
726
|
-
---
|
|
727
|
-
|
|
728
|
-
# EKS Kubernetes Patterns
|
|
729
|
-
|
|
730
|
-
## EKS Composites Overview
|
|
731
|
-
|
|
732
|
-
These composites produce K8s YAML with EKS-specific annotations and configurations.
|
|
733
|
-
|
|
734
|
-
### IrsaServiceAccount — ServiceAccount with IAM Role annotation
|
|
735
|
-
|
|
736
|
-
\`\`\`typescript
|
|
737
|
-
import { IrsaServiceAccount } from "@intentius/chant-lexicon-k8s";
|
|
738
|
-
|
|
739
|
-
const { serviceAccount, role, roleBinding } = IrsaServiceAccount({
|
|
740
|
-
name: "app-sa",
|
|
741
|
-
iamRoleArn: "arn:aws:iam::123456789012:role/my-app-role",
|
|
742
|
-
rbacRules: [
|
|
743
|
-
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
744
|
-
],
|
|
745
|
-
namespace: "prod",
|
|
746
|
-
});
|
|
747
|
-
\`\`\`
|
|
748
|
-
|
|
749
|
-
### AlbIngress — Ingress with AWS ALB Controller annotations
|
|
750
|
-
|
|
751
|
-
\`\`\`typescript
|
|
752
|
-
import { AlbIngress } from "@intentius/chant-lexicon-k8s";
|
|
753
|
-
|
|
754
|
-
const { ingress } = AlbIngress({
|
|
755
|
-
name: "api-ingress",
|
|
756
|
-
hosts: [
|
|
757
|
-
{
|
|
758
|
-
hostname: "api.example.com",
|
|
759
|
-
paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
760
|
-
},
|
|
761
|
-
],
|
|
762
|
-
scheme: "internet-facing",
|
|
763
|
-
certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
|
|
764
|
-
groupName: "shared-alb",
|
|
765
|
-
healthCheckPath: "/healthz",
|
|
766
|
-
});
|
|
767
|
-
\`\`\`
|
|
768
|
-
|
|
769
|
-
Features:
|
|
770
|
-
- Auto-sets \`alb.ingress.kubernetes.io/*\` annotations
|
|
771
|
-
- SSL redirect enabled by default when \`certificateArn\` set
|
|
772
|
-
- \`groupName\` for shared ALB across multiple Ingresses
|
|
773
|
-
- \`wafAclArn\` for WAFv2 integration
|
|
774
|
-
|
|
775
|
-
### EbsStorageClass — StorageClass for EBS CSI
|
|
776
|
-
|
|
777
|
-
\`\`\`typescript
|
|
778
|
-
import { EbsStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
779
|
-
|
|
780
|
-
const { storageClass } = EbsStorageClass({
|
|
781
|
-
name: "gp3-encrypted",
|
|
782
|
-
type: "gp3",
|
|
783
|
-
encrypted: true,
|
|
784
|
-
iops: "3000",
|
|
785
|
-
throughput: "125",
|
|
786
|
-
});
|
|
787
|
-
\`\`\`
|
|
788
|
-
|
|
789
|
-
### EfsStorageClass — StorageClass for EFS CSI (ReadWriteMany)
|
|
790
|
-
|
|
791
|
-
\`\`\`typescript
|
|
792
|
-
import { EfsStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
793
|
-
|
|
794
|
-
const { storageClass } = EfsStorageClass({
|
|
795
|
-
name: "efs-shared",
|
|
796
|
-
fileSystemId: "fs-12345678",
|
|
797
|
-
});
|
|
798
|
-
\`\`\`
|
|
799
|
-
|
|
800
|
-
Use EFS when you need ReadWriteMany (shared across pods/nodes). Use EBS for ReadWriteOnce (single pod).
|
|
801
|
-
|
|
802
|
-
### FluentBitAgent — DaemonSet for CloudWatch logging
|
|
803
|
-
|
|
804
|
-
\`\`\`typescript
|
|
805
|
-
import { FluentBitAgent } from "@intentius/chant-lexicon-k8s";
|
|
806
|
-
|
|
807
|
-
const result = FluentBitAgent({
|
|
808
|
-
logGroup: "/aws/eks/my-cluster/containers",
|
|
809
|
-
region: "us-east-1",
|
|
810
|
-
clusterName: "my-cluster",
|
|
811
|
-
});
|
|
812
|
-
\`\`\`
|
|
813
|
-
|
|
814
|
-
### ExternalDnsAgent — ExternalDNS for Route53
|
|
815
|
-
|
|
816
|
-
\`\`\`typescript
|
|
817
|
-
import { ExternalDnsAgent } from "@intentius/chant-lexicon-k8s";
|
|
818
|
-
|
|
819
|
-
const result = ExternalDnsAgent({
|
|
820
|
-
iamRoleArn: "arn:aws:iam::123456789012:role/external-dns-role",
|
|
821
|
-
domainFilters: ["example.com"],
|
|
822
|
-
txtOwnerId: "my-cluster",
|
|
823
|
-
});
|
|
824
|
-
\`\`\`
|
|
825
|
-
|
|
826
|
-
### AdotCollector — ADOT for CloudWatch/X-Ray
|
|
827
|
-
|
|
828
|
-
\`\`\`typescript
|
|
829
|
-
import { AdotCollector } from "@intentius/chant-lexicon-k8s";
|
|
830
|
-
|
|
831
|
-
const result = AdotCollector({
|
|
832
|
-
region: "us-east-1",
|
|
833
|
-
clusterName: "my-cluster",
|
|
834
|
-
exporters: ["cloudwatch", "xray"],
|
|
835
|
-
});
|
|
836
|
-
\`\`\`
|
|
837
|
-
|
|
838
|
-
## Pod Identity vs IRSA
|
|
839
|
-
|
|
840
|
-
| Feature | IRSA | Pod Identity |
|
|
841
|
-
|---------|------|-------------|
|
|
842
|
-
| K8s annotation needed | Yes (\`eks.amazonaws.com/role-arn\`) | No |
|
|
843
|
-
| Composite available | **IrsaServiceAccount** | None needed |
|
|
844
|
-
| Setup | OIDC provider + IAM role trust policy | EKS Pod Identity Agent add-on + association |
|
|
845
|
-
| When to use | Existing clusters, broad compatibility | New clusters (EKS 1.28+), simpler management |
|
|
846
|
-
|
|
847
|
-
For Pod Identity, no K8s-side composite is needed — configure the association via AWS API/CloudFormation and use a plain ServiceAccount.
|
|
848
|
-
|
|
849
|
-
## Karpenter
|
|
850
|
-
|
|
851
|
-
Karpenter replaces Cluster Autoscaler for node provisioning. Karpenter NodePool and EC2NodeClass are simple CRDs — use CRD import rather than composites:
|
|
852
|
-
|
|
853
|
-
\`\`\`bash
|
|
854
|
-
# Import Karpenter CRDs into your chant project
|
|
855
|
-
chant import --url https://raw.githubusercontent.com/aws/karpenter/main/pkg/apis/crds/karpenter.sh_nodepools.yaml
|
|
856
|
-
\`\`\`
|
|
857
|
-
|
|
858
|
-
## Fargate Considerations
|
|
859
|
-
|
|
860
|
-
When running on EKS Fargate:
|
|
861
|
-
- **No DaemonSets** — FluentBitAgent and AdotCollector cannot run on Fargate nodes
|
|
862
|
-
- **No hostPath volumes** — use EFS for shared storage
|
|
863
|
-
- **No privileged containers** — security context restrictions apply
|
|
864
|
-
- For Fargate logging, use the built-in Fluent Bit log router (Fargate logging configuration)
|
|
865
|
-
|
|
866
|
-
## EKS Add-ons
|
|
867
|
-
|
|
868
|
-
Common add-ons managed via AWS (not K8s manifests):
|
|
869
|
-
- **vpc-cni** — Amazon VPC CNI plugin
|
|
870
|
-
- **coredns** — Cluster DNS
|
|
871
|
-
- **kube-proxy** — Network proxy
|
|
872
|
-
- **aws-ebs-csi-driver** — EBS CSI driver (required for EbsStorageClass)
|
|
873
|
-
- **aws-efs-csi-driver** — EFS CSI driver (required for EfsStorageClass)
|
|
874
|
-
- **adot** — AWS Distro for OpenTelemetry (alternative to AdotCollector composite)
|
|
875
|
-
- **aws-guardduty-agent** — Runtime threat detection
|
|
876
|
-
|
|
877
|
-
Configure add-ons via the AWS lexicon (\`@intentius/chant-lexicon-aws\`) CloudFormation resources.
|
|
878
|
-
`,
|
|
879
|
-
triggers: [
|
|
880
|
-
{ type: "context", value: "eks" },
|
|
881
|
-
{ type: "context", value: "irsa" },
|
|
882
|
-
{ type: "context", value: "alb" },
|
|
883
|
-
{ type: "context", value: "ebs" },
|
|
884
|
-
{ type: "context", value: "efs" },
|
|
885
|
-
{ type: "context", value: "fluent-bit" },
|
|
886
|
-
{ type: "context", value: "cloudwatch" },
|
|
887
|
-
{ type: "context", value: "karpenter" },
|
|
888
|
-
{ type: "context", value: "fargate" },
|
|
889
|
-
],
|
|
890
|
-
preConditions: [
|
|
891
|
-
"chant CLI is installed (chant --version succeeds)",
|
|
892
|
-
"EKS cluster is provisioned",
|
|
893
|
-
"kubectl is configured for the EKS cluster",
|
|
894
|
-
],
|
|
895
|
-
postConditions: [
|
|
896
|
-
"EKS-specific resources are deployed and functional",
|
|
897
|
-
],
|
|
898
|
-
parameters: [],
|
|
899
|
-
examples: [
|
|
900
|
-
{
|
|
901
|
-
title: "IRSA ServiceAccount",
|
|
902
|
-
description: "Create a ServiceAccount with IAM role for S3 access",
|
|
903
|
-
input: "Create an IRSA ServiceAccount for my app that needs S3 access",
|
|
904
|
-
output: `import { IrsaServiceAccount } from "@intentius/chant-lexicon-k8s";
|
|
905
|
-
|
|
906
|
-
const { serviceAccount } = IrsaServiceAccount({
|
|
907
|
-
name: "app-sa",
|
|
908
|
-
iamRoleArn: "arn:aws:iam::123456789012:role/app-s3-role",
|
|
909
|
-
namespace: "prod",
|
|
910
|
-
});`,
|
|
911
|
-
},
|
|
912
|
-
{
|
|
913
|
-
title: "ALB Ingress with TLS",
|
|
914
|
-
description: "Create an ALB Ingress with ACM certificate",
|
|
915
|
-
input: "Set up an internet-facing ALB with TLS for my API",
|
|
916
|
-
output: `import { AlbIngress } from "@intentius/chant-lexicon-k8s";
|
|
917
|
-
|
|
918
|
-
const { ingress } = AlbIngress({
|
|
919
|
-
name: "api-ingress",
|
|
920
|
-
hosts: [{ hostname: "api.example.com", paths: [{ path: "/", serviceName: "api", servicePort: 80 }] }],
|
|
921
|
-
certificateArn: "arn:aws:acm:us-east-1:123456789012:certificate/abc-123",
|
|
922
720
|
});`,
|
|
923
721
|
},
|
|
924
722
|
],
|
|
@@ -1222,5 +1020,127 @@ const { deployment, service, serviceMonitor, prometheusRule } = MonitoredService
|
|
|
1222
1020
|
],
|
|
1223
1021
|
},
|
|
1224
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
|
+
{
|
|
1032
|
+
file: "kubernetes-patterns.md",
|
|
1033
|
+
name: "kubernetes-patterns",
|
|
1034
|
+
description: "Kubernetes deployment strategies, stateful workloads, RBAC, and networking patterns",
|
|
1035
|
+
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" },
|
|
1042
|
+
],
|
|
1043
|
+
parameters: [],
|
|
1044
|
+
examples: [
|
|
1045
|
+
{
|
|
1046
|
+
title: "Blue/Green Deployment",
|
|
1047
|
+
input: "Set up a blue/green deployment for my app",
|
|
1048
|
+
output: "import { WebApp } from \"@intentius/chant-lexicon-k8s\";\n\nconst blue = WebApp({ name: \"app-blue\", image: \"app:1.0\" });\nconst green = WebApp({ name: \"app-green\", image: \"app:2.0\" });",
|
|
1049
|
+
},
|
|
1050
|
+
],
|
|
1051
|
+
},
|
|
1052
|
+
{
|
|
1053
|
+
file: "kubernetes-security.md",
|
|
1054
|
+
name: "kubernetes-security",
|
|
1055
|
+
description: "Kubernetes pod security, image scanning, network policies, and secrets management",
|
|
1056
|
+
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" },
|
|
1062
|
+
],
|
|
1063
|
+
parameters: [],
|
|
1064
|
+
examples: [
|
|
1065
|
+
{
|
|
1066
|
+
title: "Hardened Container",
|
|
1067
|
+
input: "Create a hardened container with security context",
|
|
1068
|
+
output: "WebApp({ name: \"api\", image: \"api:1.0\", securityContext: { runAsNonRoot: true, readOnlyRootFilesystem: true, capabilities: { drop: [\"ALL\"] } } })",
|
|
1069
|
+
},
|
|
1070
|
+
],
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
file: "chant-k8s-eks.md",
|
|
1074
|
+
name: "chant-k8s-eks",
|
|
1075
|
+
description: "EKS-specific Kubernetes composites — IRSA, ALB, EBS/EFS, Fluent Bit, ExternalDNS, ADOT",
|
|
1076
|
+
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" },
|
|
1082
|
+
],
|
|
1083
|
+
parameters: [],
|
|
1084
|
+
examples: [
|
|
1085
|
+
{
|
|
1086
|
+
title: "IRSA ServiceAccount",
|
|
1087
|
+
input: "Create an IRSA ServiceAccount for my app",
|
|
1088
|
+
output: "import { IrsaServiceAccount } from \"@intentius/chant-lexicon-k8s\";\n\nconst { serviceAccount } = IrsaServiceAccount({ name: \"app-sa\", iamRoleArn: \"arn:aws:iam::123456789012:role/app-role\" });",
|
|
1089
|
+
},
|
|
1090
|
+
],
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
file: "chant-k8s-gke.md",
|
|
1094
|
+
name: "chant-k8s-gke",
|
|
1095
|
+
description: "GKE-specific Kubernetes composites — Workload Identity, GCE PD, Filestore, FluentBit, OTel, ExternalDNS, Gateway",
|
|
1096
|
+
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" },
|
|
1100
|
+
],
|
|
1101
|
+
parameters: [],
|
|
1102
|
+
examples: [
|
|
1103
|
+
{
|
|
1104
|
+
title: "GKE Workload Identity",
|
|
1105
|
+
input: "Create a ServiceAccount with GKE Workload Identity",
|
|
1106
|
+
output: "import { WorkloadIdentityServiceAccount } from \"@intentius/chant-lexicon-k8s\";\n\nconst { serviceAccount } = WorkloadIdentityServiceAccount({ name: \"app-sa\", gcpServiceAccountEmail: \"app@project.iam.gserviceaccount.com\" });",
|
|
1107
|
+
},
|
|
1108
|
+
],
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
file: "chant-k8s-aks.md",
|
|
1112
|
+
name: "chant-k8s-aks",
|
|
1113
|
+
description: "AKS-specific Kubernetes composites — Workload Identity, AGIC, Azure Disk/File, ExternalDNS, Azure Monitor",
|
|
1114
|
+
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" },
|
|
1118
|
+
],
|
|
1119
|
+
parameters: [],
|
|
1120
|
+
examples: [
|
|
1121
|
+
{
|
|
1122
|
+
title: "AKS Workload Identity",
|
|
1123
|
+
input: "Create a ServiceAccount with AKS Workload Identity",
|
|
1124
|
+
output: "import { AksWorkloadIdentityServiceAccount } from \"@intentius/chant-lexicon-k8s\";\n\nconst { serviceAccount } = AksWorkloadIdentityServiceAccount({ name: \"app-sa\", clientId: \"12345678-abcd-1234-abcd-123456789012\" });",
|
|
1125
|
+
},
|
|
1126
|
+
],
|
|
1127
|
+
},
|
|
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;
|
|
1225
1145
|
},
|
|
1226
1146
|
};
|
package/src/serializer.test.ts
CHANGED
|
@@ -143,6 +143,76 @@ describe("k8sSerializer", () => {
|
|
|
143
143
|
expect(result).not.toContain("spec:");
|
|
144
144
|
});
|
|
145
145
|
|
|
146
|
+
test("ClusterRole is specless type", () => {
|
|
147
|
+
const entities = new Map<string, any>();
|
|
148
|
+
entities.set(
|
|
149
|
+
"viewRole",
|
|
150
|
+
mockResource("K8s::Rbac::ClusterRole", {
|
|
151
|
+
metadata: { name: "view-role" },
|
|
152
|
+
rules: [{ apiGroups: [""], resources: ["pods"], verbs: ["get", "list"] }],
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const result = k8sSerializer.serialize(entities);
|
|
157
|
+
expect(result).toContain("kind: ClusterRole");
|
|
158
|
+
expect(result).toContain("name: view-role");
|
|
159
|
+
expect(result).not.toContain("spec:");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("ClusterRoleBinding is specless type", () => {
|
|
163
|
+
const entities = new Map<string, any>();
|
|
164
|
+
entities.set(
|
|
165
|
+
"viewBinding",
|
|
166
|
+
mockResource("K8s::Rbac::ClusterRoleBinding", {
|
|
167
|
+
metadata: { name: "view-binding" },
|
|
168
|
+
roleRef: { apiGroup: "rbac.authorization.k8s.io", kind: "ClusterRole", name: "view-role" },
|
|
169
|
+
subjects: [{ kind: "ServiceAccount", name: "default", namespace: "default" }],
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const result = k8sSerializer.serialize(entities);
|
|
174
|
+
expect(result).toContain("kind: ClusterRoleBinding");
|
|
175
|
+
expect(result).not.toContain("spec:");
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("StorageClass is specless type", () => {
|
|
179
|
+
const entities = new Map<string, any>();
|
|
180
|
+
entities.set(
|
|
181
|
+
"gp3",
|
|
182
|
+
mockResource("K8s::Storage::StorageClass", {
|
|
183
|
+
metadata: { name: "gp3-encrypted" },
|
|
184
|
+
provisioner: "ebs.csi.aws.com",
|
|
185
|
+
parameters: { type: "gp3", encrypted: "true" },
|
|
186
|
+
reclaimPolicy: "Delete",
|
|
187
|
+
volumeBindingMode: "WaitForFirstConsumer",
|
|
188
|
+
}),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const result = k8sSerializer.serialize(entities);
|
|
192
|
+
expect(result).toContain("kind: StorageClass");
|
|
193
|
+
expect(result).toContain("provisioner: ebs.csi.aws.com");
|
|
194
|
+
expect(result).not.toContain("spec:");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("APIService is specless type", () => {
|
|
198
|
+
const entities = new Map<string, any>();
|
|
199
|
+
entities.set(
|
|
200
|
+
"metricsApi",
|
|
201
|
+
mockResource("K8s::Admissionregistration::APIService", {
|
|
202
|
+
metadata: { name: "v1beta1.metrics.k8s.io" },
|
|
203
|
+
group: "metrics.k8s.io",
|
|
204
|
+
version: "v1beta1",
|
|
205
|
+
service: { name: "metrics-server", namespace: "kube-system" },
|
|
206
|
+
groupPriorityMinimum: 100,
|
|
207
|
+
versionPriority: 100,
|
|
208
|
+
}),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const result = k8sSerializer.serialize(entities);
|
|
212
|
+
expect(result).toContain("kind: APIService");
|
|
213
|
+
expect(result).not.toContain("spec:");
|
|
214
|
+
});
|
|
215
|
+
|
|
146
216
|
test("multi-resource entities joined by ---", () => {
|
|
147
217
|
const entities = new Map<string, any>();
|
|
148
218
|
entities.set(
|
|
@@ -248,6 +318,56 @@ describe("k8sSerializer", () => {
|
|
|
248
318
|
expect(result).toBe("");
|
|
249
319
|
});
|
|
250
320
|
|
|
321
|
+
test("Namespaces appear before other resources regardless of insertion order", () => {
|
|
322
|
+
const entities = new Map<string, any>();
|
|
323
|
+
// Insert Deployment first, then Namespace — Namespace should still come first in output
|
|
324
|
+
entities.set(
|
|
325
|
+
"deploy",
|
|
326
|
+
mockResource("K8s::Apps::Deployment", {
|
|
327
|
+
metadata: { name: "app", namespace: "my-ns" },
|
|
328
|
+
spec: { replicas: 1 },
|
|
329
|
+
}),
|
|
330
|
+
);
|
|
331
|
+
entities.set(
|
|
332
|
+
"ns",
|
|
333
|
+
mockResource("K8s::Core::Namespace", {
|
|
334
|
+
metadata: { name: "my-ns" },
|
|
335
|
+
}),
|
|
336
|
+
);
|
|
337
|
+
|
|
338
|
+
const result = k8sSerializer.serialize(entities);
|
|
339
|
+
const docs = result.split("---");
|
|
340
|
+
// First document should be the Namespace
|
|
341
|
+
expect(docs[0]).toContain("kind: Namespace");
|
|
342
|
+
expect(docs[0]).toContain("name: my-ns");
|
|
343
|
+
// Second document should be the Deployment
|
|
344
|
+
expect(docs[1]).toContain("kind: Deployment");
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("ConfigMap multiline data emits as | block scalar", () => {
|
|
348
|
+
const entities = new Map<string, any>();
|
|
349
|
+
const multilineConfig = "[SERVICE]\n Flush 5\n Log_Level info\n\n[INPUT]\n Name tail\n";
|
|
350
|
+
entities.set(
|
|
351
|
+
"config",
|
|
352
|
+
mockResource("K8s::Core::ConfigMap", {
|
|
353
|
+
metadata: { name: "app-config" },
|
|
354
|
+
data: { "fluent-bit.conf": multilineConfig },
|
|
355
|
+
}),
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const result = k8sSerializer.serialize(entities);
|
|
359
|
+
expect(result).toContain("kind: ConfigMap");
|
|
360
|
+
// Must use | block scalar, not flatten to single line
|
|
361
|
+
expect(result).toContain("fluent-bit.conf: |");
|
|
362
|
+
// Content lines should be preserved (indented under the key)
|
|
363
|
+
expect(result).toContain("[SERVICE]");
|
|
364
|
+
expect(result).toContain("Flush 5");
|
|
365
|
+
expect(result).toContain("[INPUT]");
|
|
366
|
+
expect(result).toContain("Name tail");
|
|
367
|
+
// Should NOT contain literal \n in the output
|
|
368
|
+
expect(result).not.toContain("\\n");
|
|
369
|
+
});
|
|
370
|
+
|
|
251
371
|
test("key ordering: apiVersion, kind, metadata, spec, then rest", () => {
|
|
252
372
|
const entities = new Map<string, any>();
|
|
253
373
|
entities.set(
|
package/src/serializer.ts
CHANGED
|
@@ -26,6 +26,13 @@ const SPECLESS_TYPES = new Set([
|
|
|
26
26
|
"Secret",
|
|
27
27
|
"Namespace",
|
|
28
28
|
"ServiceAccount",
|
|
29
|
+
"ClusterRole",
|
|
30
|
+
"ClusterRoleBinding",
|
|
31
|
+
"Role",
|
|
32
|
+
"RoleBinding",
|
|
33
|
+
"StorageClass",
|
|
34
|
+
"PersistentVolume",
|
|
35
|
+
"APIService",
|
|
29
36
|
]);
|
|
30
37
|
|
|
31
38
|
/**
|
|
@@ -157,7 +164,8 @@ export const k8sSerializer: Serializer = {
|
|
|
157
164
|
}
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
const
|
|
167
|
+
const namespaceDocs: string[] = [];
|
|
168
|
+
const otherDocs: string[] = [];
|
|
161
169
|
|
|
162
170
|
for (const [name, entity] of entities) {
|
|
163
171
|
if (isPropertyDeclarable(entity)) continue;
|
|
@@ -230,12 +238,16 @@ export const k8sSerializer: Serializer = {
|
|
|
230
238
|
}
|
|
231
239
|
}
|
|
232
240
|
|
|
233
|
-
// Emit as YAML
|
|
241
|
+
// Emit as YAML — sort Namespaces first so kubectl apply succeeds
|
|
234
242
|
const yamlDoc = emitK8sManifest(manifest);
|
|
235
|
-
|
|
243
|
+
if (gvk.kind === "Namespace") {
|
|
244
|
+
namespaceDocs.push(yamlDoc);
|
|
245
|
+
} else {
|
|
246
|
+
otherDocs.push(yamlDoc);
|
|
247
|
+
}
|
|
236
248
|
}
|
|
237
249
|
|
|
238
|
-
return
|
|
250
|
+
return [...namespaceDocs, ...otherDocs].join("\n---\n");
|
|
239
251
|
},
|
|
240
252
|
};
|
|
241
253
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-k8s-aks
|
|
3
|
+
description: AKS-specific Kubernetes patterns and composites
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# AKS Kubernetes Patterns
|
|
8
|
+
|
|
9
|
+
## AKS Composites Overview
|
|
10
|
+
|
|
11
|
+
These composites produce K8s YAML with AKS-specific annotations and configurations.
|
|
12
|
+
|
|
13
|
+
### AksWorkloadIdentityServiceAccount — ServiceAccount with Azure client ID annotation
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { AksWorkloadIdentityServiceAccount } from "@intentius/chant-lexicon-k8s";
|
|
17
|
+
|
|
18
|
+
const { serviceAccount, role, roleBinding } = AksWorkloadIdentityServiceAccount({
|
|
19
|
+
name: "app-sa",
|
|
20
|
+
clientId: "12345678-abcd-1234-abcd-123456789012",
|
|
21
|
+
rbacRules: [
|
|
22
|
+
{ apiGroups: [""], resources: ["secrets"], verbs: ["get"] },
|
|
23
|
+
],
|
|
24
|
+
namespace: "prod",
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Annotates the ServiceAccount with `azure.workload.identity/client-id` for AKS Workload Identity.
|
|
29
|
+
|
|
30
|
+
### AgicIngress — Ingress with Application Gateway annotations
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { AgicIngress } from "@intentius/chant-lexicon-k8s";
|
|
34
|
+
|
|
35
|
+
const { ingress } = AgicIngress({
|
|
36
|
+
name: "api-ingress",
|
|
37
|
+
hosts: [
|
|
38
|
+
{
|
|
39
|
+
hostname: "api.example.com",
|
|
40
|
+
paths: [{ path: "/", serviceName: "api", servicePort: 80 }],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
certificateArn: "keyvault-cert-name",
|
|
44
|
+
healthCheckPath: "/healthz",
|
|
45
|
+
wafPolicyId: "/subscriptions/.../applicationGatewayWebApplicationFirewallPolicies/my-waf",
|
|
46
|
+
cookieAffinity: false,
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Features:
|
|
51
|
+
- Auto-sets `appgw.ingress.kubernetes.io/*` annotations
|
|
52
|
+
- SSL redirect enabled by default when `certificateArn` set
|
|
53
|
+
- `wafPolicyId` for WAFv2 integration
|
|
54
|
+
- `healthCheckPath` for backend health probes
|
|
55
|
+
- `cookieAffinity` for session persistence
|
|
56
|
+
|
|
57
|
+
### AzureDiskStorageClass — StorageClass for Azure Disk CSI
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { AzureDiskStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
61
|
+
|
|
62
|
+
const { storageClass } = AzureDiskStorageClass({
|
|
63
|
+
name: "premium-lrs",
|
|
64
|
+
skuName: "Premium_LRS",
|
|
65
|
+
cachingMode: "ReadOnly",
|
|
66
|
+
allowVolumeExpansion: true,
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
SKU options: `Premium_LRS`, `StandardSSD_LRS`, `Standard_LRS`, `UltraSSD_LRS`.
|
|
71
|
+
|
|
72
|
+
### AzureFileStorageClass — StorageClass for Azure Files CSI (ReadWriteMany)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { AzureFileStorageClass } from "@intentius/chant-lexicon-k8s";
|
|
76
|
+
|
|
77
|
+
const { storageClass } = AzureFileStorageClass({
|
|
78
|
+
name: "azure-files-premium",
|
|
79
|
+
skuName: "Premium_LRS",
|
|
80
|
+
protocol: "smb",
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Protocol options: `smb` (default), `nfs`. Use Azure Files when you need ReadWriteMany (shared across pods/nodes). Use Azure Disk for ReadWriteOnce (single pod).
|
|
85
|
+
|
|
86
|
+
### AksExternalDnsAgent — ExternalDNS for Azure DNS
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { AksExternalDnsAgent } from "@intentius/chant-lexicon-k8s";
|
|
90
|
+
|
|
91
|
+
const result = AksExternalDnsAgent({
|
|
92
|
+
clientId: "12345678-abcd-1234-abcd-123456789012",
|
|
93
|
+
resourceGroup: "my-rg",
|
|
94
|
+
subscriptionId: "sub-id",
|
|
95
|
+
tenantId: "tenant-id",
|
|
96
|
+
domainFilters: ["example.com"],
|
|
97
|
+
txtOwnerId: "my-cluster",
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### AzureMonitorCollector — Azure Monitor + OTel for Log Analytics
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { AzureMonitorCollector } from "@intentius/chant-lexicon-k8s";
|
|
105
|
+
|
|
106
|
+
const result = AzureMonitorCollector({
|
|
107
|
+
workspaceId: "/subscriptions/.../workspaces/my-workspace",
|
|
108
|
+
clusterName: "my-cluster",
|
|
109
|
+
clientId: "12345678-abcd-1234-abcd-123456789012",
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## AKS Workload Identity vs Pod-Managed Identity
|
|
114
|
+
|
|
115
|
+
| Feature | Workload Identity | Pod-Managed Identity (deprecated) |
|
|
116
|
+
|---------|------------------|----------------------------------|
|
|
117
|
+
| K8s annotation needed | Yes (`azure.workload.identity/client-id`) | Yes (`aadpodidbinding` label) |
|
|
118
|
+
| Composite available | **AksWorkloadIdentityServiceAccount** | None (deprecated) |
|
|
119
|
+
| Setup | OIDC issuer + federated credential | AzureIdentity + AzureIdentityBinding CRDs |
|
|
120
|
+
| Security | OIDC token exchange, no NMI pod | NMI DaemonSet intercepts IMDS calls |
|
|
121
|
+
| When to use | Always (recommended) | Legacy only, migrate to Workload Identity |
|
|
122
|
+
|
|
123
|
+
Pod-managed identity (AAD Pod Identity v1) is deprecated. Always use AKS Workload Identity for new workloads.
|
|
124
|
+
|
|
125
|
+
## AGIC Considerations
|
|
126
|
+
|
|
127
|
+
Application Gateway Ingress Controller (AGIC) manages an Azure Application Gateway:
|
|
128
|
+
- **Application Gateway provisioned in ARM** — the gateway itself is an Azure resource created by the ARM template
|
|
129
|
+
- **AGIC addon** — runs as a pod in the cluster, watches Ingress resources and configures the gateway
|
|
130
|
+
- **Backend pools** — AGIC automatically adds pod IPs to the Application Gateway backend pool
|
|
131
|
+
- **Health probes** — set `healthCheckPath` for proper backend health checking
|
|
132
|
+
- **WAF integration** — attach a WAF policy via `wafPolicyId` for L7 protection
|
|
133
|
+
- **TLS termination** — reference Key Vault certificates via `certificateArn` (the certificate URI or secret name)
|
|
134
|
+
|
|
135
|
+
## AKS Add-ons
|
|
136
|
+
|
|
137
|
+
Common add-ons managed via AKS (not K8s manifests):
|
|
138
|
+
- **AGIC** — Application Gateway Ingress Controller (required for AgicIngress)
|
|
139
|
+
- **Azure Monitor (Container Insights)** — alternative to AzureMonitorCollector for managed monitoring
|
|
140
|
+
- **AKS Workload Identity** — OIDC-based identity federation (required for AksWorkloadIdentityServiceAccount)
|
|
141
|
+
- **Azure Disk CSI driver** — enabled by default, required for AzureDiskStorageClass
|
|
142
|
+
- **Azure Files CSI driver** — enabled by default, required for AzureFileStorageClass
|
|
143
|
+
- **Azure Key Vault Secrets Provider** — sync Key Vault secrets to K8s Secrets
|
|
144
|
+
- **Azure Policy** — enforce governance policies on cluster resources
|
|
145
|
+
|
|
146
|
+
Configure add-ons via the Azure lexicon (`@intentius/chant-lexicon-azure`) ARM resources.
|