@intentius/chant-lexicon-k8s 0.1.14 → 0.1.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/README.md +0 -1
- package/dist/integrity.json +14 -6
- package/dist/manifest.json +1 -1
- package/dist/meta.json +216 -0
- package/dist/rules/argo-appset-single-project.ts +66 -0
- package/dist/rules/argo-ast.ts +121 -0
- package/dist/rules/argo-automated-prune.ts +75 -0
- package/dist/rules/argo-helpers.ts +49 -0
- package/dist/rules/argo002.ts +47 -0
- package/dist/rules/argo003.ts +80 -0
- package/dist/rules/argo005.ts +59 -0
- package/dist/rules/wk8301.ts +11 -3
- package/dist/skills/chant-k8s-argo.md +176 -0
- package/dist/types/index.d.ts +34 -0
- package/package.json +1 -1
- package/src/codegen/docs.ts +14 -1
- package/src/codegen/versions.ts +8 -5
- package/src/composites/argo-app.ts +380 -0
- package/src/composites/composites.test.ts +136 -0
- package/src/composites/index.ts +12 -0
- package/src/crd/crd-sources.ts +18 -0
- package/src/crd/loader.ts +4 -5
- package/src/crd/parser.test.ts +61 -0
- package/src/crd/parser.ts +37 -2
- package/src/describe-resources.ts +8 -1
- package/src/export-resources-io.test.ts +72 -0
- package/src/export-resources.ts +60 -0
- package/src/generated/index.d.ts +34 -0
- package/src/generated/index.ts +25 -0
- package/src/generated/lexicon-k8s.json +216 -0
- package/src/import/live-export.test.ts +114 -0
- package/src/import/live-export.ts +89 -0
- package/src/index.ts +5 -0
- package/src/lifecycle-integration.test.ts +111 -0
- package/src/lint/post-synth/argo-helpers.ts +49 -0
- package/src/lint/post-synth/argo002.ts +47 -0
- package/src/lint/post-synth/argo003.ts +80 -0
- package/src/lint/post-synth/argo005.ts +59 -0
- package/src/lint/post-synth/post-synth.test.ts +146 -2
- package/src/lint/post-synth/wk8301.ts +11 -3
- package/src/lint/rules/argo-appset-single-project.ts +66 -0
- package/src/lint/rules/argo-ast.ts +121 -0
- package/src/lint/rules/argo-automated-prune.ts +75 -0
- package/src/lint/rules/rules.test.ts +109 -0
- package/src/plugin.test.ts +6 -1
- package/src/plugin.ts +44 -1
- package/src/serializer-ownership.test.ts +44 -0
- package/src/serializer.test.ts +25 -0
- package/src/serializer.ts +9 -4
- package/src/skills/chant-k8s-argo.md +176 -0
package/README.md
CHANGED
|
@@ -17,7 +17,6 @@ npm install --save-dev @intentius/chant @intentius/chant-lexicon-k8s
|
|
|
17
17
|
| [@intentius/chant](https://www.npmjs.com/package/@intentius/chant) | Core type system, CLI, build pipeline |
|
|
18
18
|
| [@intentius/chant-lexicon-aws](https://www.npmjs.com/package/@intentius/chant-lexicon-aws) | AWS CloudFormation lexicon |
|
|
19
19
|
| [@intentius/chant-lexicon-gitlab](https://www.npmjs.com/package/@intentius/chant-lexicon-gitlab) | GitLab CI lexicon |
|
|
20
|
-
| [@intentius/chant-lexicon-flyway](https://www.npmjs.com/package/@intentius/chant-lexicon-flyway) | Flyway migration lexicon |
|
|
21
20
|
|
|
22
21
|
## License
|
|
23
22
|
|
package/dist/integrity.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
5
|
-
"meta.json": "
|
|
6
|
-
"types/index.d.ts": "
|
|
4
|
+
"manifest.json": "ac0a7f483cd0bf803770c6f429b2adf60dcd3ca77dac1d784d14ea73381c1cb5",
|
|
5
|
+
"meta.json": "b16aba17feef7b62df4057b714b51e34f88330a843d77e3904ea207d6bb073c5",
|
|
6
|
+
"types/index.d.ts": "be4a0a86c911d4407a22f1c252850d35d87c1234fac46eec8a0aebee248d60f5",
|
|
7
|
+
"rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
|
|
8
|
+
"rules/argo-ast.ts": "f57b7d84fef9c9cd326a7cad0dff290914e01299415d64bdab48e2f6eb4181c3",
|
|
9
|
+
"rules/argo-automated-prune.ts": "176ac5441c3ccb7106a194abab3abe22fd8a29f976716761c66c243b72d57b14",
|
|
7
10
|
"rules/hardcoded-namespace.ts": "ba3f43f2adbffdd87db20a2c45839354ceecda1b9f04f29ae31c4c077dddc7ec",
|
|
8
11
|
"rules/latest-image-tag.ts": "9dd961b791b4f424fc4edcdbdce69ad30476dc1cd03f66b8c8e19cb5f8a4082d",
|
|
9
12
|
"rules/missing-resource-limits.ts": "d64b5ed1c7c87d60e38eecfdad7668620222bbaf6ff6f4860adf54e816265bb2",
|
|
13
|
+
"rules/argo-helpers.ts": "9d665bf15443740bc0e1f5655e4380bf619d11025b7e0baaeffc2ee884266b45",
|
|
14
|
+
"rules/argo002.ts": "d2f07daabfc7a4624d3d60ea151fd2380d43f1578dd6a17825f6f6e0ebc610ba",
|
|
15
|
+
"rules/argo003.ts": "a61024fbd3db772eface6b2e4fee618a5818f12a1050faea8e358e0fdc992396",
|
|
16
|
+
"rules/argo005.ts": "611ea489e005e3ead925013e636f0d7c6899fd0ce68c59a97541fc6fe4e8d25c",
|
|
10
17
|
"rules/k8s-helpers.ts": "d7d5020bc9b47bfb1827e8c8cb13ad1d406890d08a964a521c9fbcb93b230827",
|
|
11
18
|
"rules/wk8005.ts": "71c3f6e68da973c466b2d5cf109bf4728b1671794aeca8e8fde79c80c8715ec1",
|
|
12
19
|
"rules/wk8006.ts": "8705b24db14c9af12c6c45c5a8ecb5f4f427a5f76706ac2c82ba2949d41c498c",
|
|
@@ -25,7 +32,7 @@
|
|
|
25
32
|
"rules/wk8207.ts": "e82dbf47e20fc186a105069b5923a8532553f236c58178332f9264f39ac31390",
|
|
26
33
|
"rules/wk8208.ts": "0e9d654fa70f9ee91de4711df43689fcee4fde831ce0eb0081fdc4753632e9dd",
|
|
27
34
|
"rules/wk8209.ts": "617324b7988a7c842502c9e76388412c5855dcd321e40a2df6039c9faf87ac47",
|
|
28
|
-
"rules/wk8301.ts": "
|
|
35
|
+
"rules/wk8301.ts": "b7293376340ae511d6bec1691356558c38e2d42f5823e599125fdce1ddeefea6",
|
|
29
36
|
"rules/wk8302.ts": "3e8e336e72618b543c3208f9b2b7a582b4535d3d284481cc6c77812c35812e71",
|
|
30
37
|
"rules/wk8303.ts": "fdf8b072063359d2e654b1e810491738316eae01e85f9d18df1c341578caf2a6",
|
|
31
38
|
"rules/wk8304.ts": "fd98b1bd1e62713b29af5fbb8e45d8ddf2b8b751eab1819caa9ea813efb9e54d",
|
|
@@ -40,7 +47,8 @@
|
|
|
40
47
|
"skills/chant-k8s-security.md": "f4250284000fde0d380192ba4cd73f2190471da2142e3bf77d848881552807f7",
|
|
41
48
|
"skills/chant-k8s-eks.md": "c391217039b35d1c9d88214e2cb7b8c1166331d6ab26996f5f7d907027508ab9",
|
|
42
49
|
"skills/chant-k8s-gke.md": "8938840bf9ef5ed58d6333fdd773b3dd54ecaf25a9df35e58f7f5c3355d4928f",
|
|
43
|
-
"skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9"
|
|
50
|
+
"skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9",
|
|
51
|
+
"skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5"
|
|
44
52
|
},
|
|
45
|
-
"composite": "
|
|
53
|
+
"composite": "dbeeb60c7e3490151a54580d4fb83094de74efa196f6f98aab1770f2efe3dba6"
|
|
46
54
|
}
|
package/dist/manifest.json
CHANGED
package/dist/meta.json
CHANGED
|
@@ -46,6 +46,137 @@
|
|
|
46
46
|
"kind": "property",
|
|
47
47
|
"lexicon": "k8s"
|
|
48
48
|
},
|
|
49
|
+
"AppProject": {
|
|
50
|
+
"resourceType": "K8s::Argo::AppProject",
|
|
51
|
+
"kind": "resource",
|
|
52
|
+
"lexicon": "k8s",
|
|
53
|
+
"apiVersion": "argoproj.io/v1alpha1",
|
|
54
|
+
"gvkKind": "AppProject"
|
|
55
|
+
},
|
|
56
|
+
"AppProject_ClusterResourceBlacklist": {
|
|
57
|
+
"resourceType": "K8s::Argo::AppProject.clusterResourceBlacklist",
|
|
58
|
+
"kind": "property",
|
|
59
|
+
"lexicon": "k8s"
|
|
60
|
+
},
|
|
61
|
+
"AppProject_ClusterResourceWhitelist": {
|
|
62
|
+
"resourceType": "K8s::Argo::AppProject.clusterResourceWhitelist",
|
|
63
|
+
"kind": "property",
|
|
64
|
+
"lexicon": "k8s"
|
|
65
|
+
},
|
|
66
|
+
"AppProject_Destination": {
|
|
67
|
+
"resourceType": "K8s::Argo::AppProject.destinations",
|
|
68
|
+
"kind": "property",
|
|
69
|
+
"lexicon": "k8s"
|
|
70
|
+
},
|
|
71
|
+
"AppProject_DestinationServiceAccount": {
|
|
72
|
+
"resourceType": "K8s::Argo::AppProject.destinationServiceAccounts",
|
|
73
|
+
"kind": "property",
|
|
74
|
+
"lexicon": "k8s"
|
|
75
|
+
},
|
|
76
|
+
"AppProject_NamespaceResourceBlacklist": {
|
|
77
|
+
"resourceType": "K8s::Argo::AppProject.namespaceResourceBlacklist",
|
|
78
|
+
"kind": "property",
|
|
79
|
+
"lexicon": "k8s"
|
|
80
|
+
},
|
|
81
|
+
"AppProject_NamespaceResourceWhitelist": {
|
|
82
|
+
"resourceType": "K8s::Argo::AppProject.namespaceResourceWhitelist",
|
|
83
|
+
"kind": "property",
|
|
84
|
+
"lexicon": "k8s"
|
|
85
|
+
},
|
|
86
|
+
"AppProject_OrphanedResources": {
|
|
87
|
+
"resourceType": "K8s::Argo::AppProject.orphanedResources",
|
|
88
|
+
"kind": "property",
|
|
89
|
+
"lexicon": "k8s"
|
|
90
|
+
},
|
|
91
|
+
"AppProject_Role": {
|
|
92
|
+
"resourceType": "K8s::Argo::AppProject.roles",
|
|
93
|
+
"kind": "property",
|
|
94
|
+
"lexicon": "k8s"
|
|
95
|
+
},
|
|
96
|
+
"AppProject_SignatureKey": {
|
|
97
|
+
"resourceType": "K8s::Argo::AppProject.signatureKeys",
|
|
98
|
+
"kind": "property",
|
|
99
|
+
"lexicon": "k8s"
|
|
100
|
+
},
|
|
101
|
+
"AppProject_SyncWindow": {
|
|
102
|
+
"resourceType": "K8s::Argo::AppProject.syncWindows",
|
|
103
|
+
"kind": "property",
|
|
104
|
+
"lexicon": "k8s"
|
|
105
|
+
},
|
|
106
|
+
"Application": {
|
|
107
|
+
"resourceType": "K8s::Argo::Application",
|
|
108
|
+
"kind": "resource",
|
|
109
|
+
"lexicon": "k8s",
|
|
110
|
+
"apiVersion": "argoproj.io/v1alpha1",
|
|
111
|
+
"gvkKind": "Application"
|
|
112
|
+
},
|
|
113
|
+
"ApplicationSet": {
|
|
114
|
+
"resourceType": "K8s::Argo::ApplicationSet",
|
|
115
|
+
"kind": "resource",
|
|
116
|
+
"lexicon": "k8s",
|
|
117
|
+
"apiVersion": "argoproj.io/v1alpha1",
|
|
118
|
+
"gvkKind": "ApplicationSet"
|
|
119
|
+
},
|
|
120
|
+
"ApplicationSet_Generator": {
|
|
121
|
+
"resourceType": "K8s::Argo::ApplicationSet.generators",
|
|
122
|
+
"kind": "property",
|
|
123
|
+
"lexicon": "k8s"
|
|
124
|
+
},
|
|
125
|
+
"ApplicationSet_IgnoreApplicationDifference": {
|
|
126
|
+
"resourceType": "K8s::Argo::ApplicationSet.ignoreApplicationDifferences",
|
|
127
|
+
"kind": "property",
|
|
128
|
+
"lexicon": "k8s"
|
|
129
|
+
},
|
|
130
|
+
"ApplicationSet_PreservedFields": {
|
|
131
|
+
"resourceType": "K8s::Argo::ApplicationSet.preservedFields",
|
|
132
|
+
"kind": "property",
|
|
133
|
+
"lexicon": "k8s"
|
|
134
|
+
},
|
|
135
|
+
"ApplicationSet_Strategy": {
|
|
136
|
+
"resourceType": "K8s::Argo::ApplicationSet.strategy",
|
|
137
|
+
"kind": "property",
|
|
138
|
+
"lexicon": "k8s"
|
|
139
|
+
},
|
|
140
|
+
"ApplicationSet_SyncPolicy": {
|
|
141
|
+
"resourceType": "K8s::Argo::ApplicationSet.syncPolicy",
|
|
142
|
+
"kind": "property",
|
|
143
|
+
"lexicon": "k8s"
|
|
144
|
+
},
|
|
145
|
+
"ApplicationSet_Template": {
|
|
146
|
+
"resourceType": "K8s::Argo::ApplicationSet.template",
|
|
147
|
+
"kind": "property",
|
|
148
|
+
"lexicon": "k8s"
|
|
149
|
+
},
|
|
150
|
+
"Application_Destination": {
|
|
151
|
+
"resourceType": "K8s::Argo::Application.destination",
|
|
152
|
+
"kind": "property",
|
|
153
|
+
"lexicon": "k8s"
|
|
154
|
+
},
|
|
155
|
+
"Application_IgnoreDifference": {
|
|
156
|
+
"resourceType": "K8s::Argo::Application.ignoreDifferences",
|
|
157
|
+
"kind": "property",
|
|
158
|
+
"lexicon": "k8s"
|
|
159
|
+
},
|
|
160
|
+
"Application_Info": {
|
|
161
|
+
"resourceType": "K8s::Argo::Application.info",
|
|
162
|
+
"kind": "property",
|
|
163
|
+
"lexicon": "k8s"
|
|
164
|
+
},
|
|
165
|
+
"Application_Source": {
|
|
166
|
+
"resourceType": "K8s::Argo::Application.source",
|
|
167
|
+
"kind": "property",
|
|
168
|
+
"lexicon": "k8s"
|
|
169
|
+
},
|
|
170
|
+
"Application_Sources": {
|
|
171
|
+
"resourceType": "K8s::Argo::Application.sources",
|
|
172
|
+
"kind": "property",
|
|
173
|
+
"lexicon": "k8s"
|
|
174
|
+
},
|
|
175
|
+
"Application_SyncPolicy": {
|
|
176
|
+
"resourceType": "K8s::Argo::Application.syncPolicy",
|
|
177
|
+
"kind": "property",
|
|
178
|
+
"lexicon": "k8s"
|
|
179
|
+
},
|
|
49
180
|
"AutoscalerOptions": {
|
|
50
181
|
"resourceType": "K8s::Ray::RayCluster.autoscalerOptions",
|
|
51
182
|
"kind": "property",
|
|
@@ -133,6 +264,16 @@
|
|
|
133
264
|
"apiVersion": "certificates.k8s.io/v1",
|
|
134
265
|
"gvkKind": "CertificateSigningRequestList"
|
|
135
266
|
},
|
|
267
|
+
"ClusterResourceBlacklist": {
|
|
268
|
+
"resourceType": "K8s::Argo::AppProject.clusterResourceBlacklist",
|
|
269
|
+
"kind": "property",
|
|
270
|
+
"lexicon": "k8s"
|
|
271
|
+
},
|
|
272
|
+
"ClusterResourceWhitelist": {
|
|
273
|
+
"resourceType": "K8s::Argo::AppProject.clusterResourceWhitelist",
|
|
274
|
+
"kind": "property",
|
|
275
|
+
"lexicon": "k8s"
|
|
276
|
+
},
|
|
136
277
|
"ClusterRole": {
|
|
137
278
|
"resourceType": "K8s::Rbac::ClusterRole",
|
|
138
279
|
"kind": "resource",
|
|
@@ -325,6 +466,11 @@
|
|
|
325
466
|
"kind": "property",
|
|
326
467
|
"lexicon": "k8s"
|
|
327
468
|
},
|
|
469
|
+
"DestinationServiceAccount": {
|
|
470
|
+
"resourceType": "K8s::Argo::AppProject.destinationServiceAccounts",
|
|
471
|
+
"kind": "property",
|
|
472
|
+
"lexicon": "k8s"
|
|
473
|
+
},
|
|
328
474
|
"DeviceClass": {
|
|
329
475
|
"resourceType": "K8s::Resource::DeviceClass",
|
|
330
476
|
"kind": "resource",
|
|
@@ -449,6 +595,11 @@
|
|
|
449
595
|
"kind": "property",
|
|
450
596
|
"lexicon": "k8s"
|
|
451
597
|
},
|
|
598
|
+
"Generator": {
|
|
599
|
+
"resourceType": "K8s::Argo::ApplicationSet.generators",
|
|
600
|
+
"kind": "property",
|
|
601
|
+
"lexicon": "k8s"
|
|
602
|
+
},
|
|
452
603
|
"HPA": {
|
|
453
604
|
"resourceType": "K8s::Autoscaling::HorizontalPodAutoscaler",
|
|
454
605
|
"kind": "resource",
|
|
@@ -509,6 +660,21 @@
|
|
|
509
660
|
"apiVersion": "networking.k8s.io/v1beta1",
|
|
510
661
|
"gvkKind": "IPAddressList"
|
|
511
662
|
},
|
|
663
|
+
"IgnoreApplicationDifference": {
|
|
664
|
+
"resourceType": "K8s::Argo::ApplicationSet.ignoreApplicationDifferences",
|
|
665
|
+
"kind": "property",
|
|
666
|
+
"lexicon": "k8s"
|
|
667
|
+
},
|
|
668
|
+
"IgnoreDifference": {
|
|
669
|
+
"resourceType": "K8s::Argo::Application.ignoreDifferences",
|
|
670
|
+
"kind": "property",
|
|
671
|
+
"lexicon": "k8s"
|
|
672
|
+
},
|
|
673
|
+
"Info": {
|
|
674
|
+
"resourceType": "K8s::Argo::Application.info",
|
|
675
|
+
"kind": "property",
|
|
676
|
+
"lexicon": "k8s"
|
|
677
|
+
},
|
|
512
678
|
"Ing": {
|
|
513
679
|
"resourceType": "K8s::Networking::Ingress",
|
|
514
680
|
"kind": "resource",
|
|
@@ -678,6 +844,16 @@
|
|
|
678
844
|
"apiVersion": "v1",
|
|
679
845
|
"gvkKind": "NamespaceList"
|
|
680
846
|
},
|
|
847
|
+
"NamespaceResourceBlacklist": {
|
|
848
|
+
"resourceType": "K8s::Argo::AppProject.namespaceResourceBlacklist",
|
|
849
|
+
"kind": "property",
|
|
850
|
+
"lexicon": "k8s"
|
|
851
|
+
},
|
|
852
|
+
"NamespaceResourceWhitelist": {
|
|
853
|
+
"resourceType": "K8s::Argo::AppProject.namespaceResourceWhitelist",
|
|
854
|
+
"kind": "property",
|
|
855
|
+
"lexicon": "k8s"
|
|
856
|
+
},
|
|
681
857
|
"NetPol": {
|
|
682
858
|
"resourceType": "K8s::Networking::NetworkPolicy",
|
|
683
859
|
"kind": "resource",
|
|
@@ -756,6 +932,11 @@
|
|
|
756
932
|
"kind": "property",
|
|
757
933
|
"lexicon": "k8s"
|
|
758
934
|
},
|
|
935
|
+
"OrphanedResources": {
|
|
936
|
+
"resourceType": "K8s::Argo::AppProject.orphanedResources",
|
|
937
|
+
"kind": "property",
|
|
938
|
+
"lexicon": "k8s"
|
|
939
|
+
},
|
|
759
940
|
"PDB": {
|
|
760
941
|
"resourceType": "K8s::Policy::PodDisruptionBudget",
|
|
761
942
|
"kind": "resource",
|
|
@@ -899,6 +1080,11 @@
|
|
|
899
1080
|
"kind": "property",
|
|
900
1081
|
"lexicon": "k8s"
|
|
901
1082
|
},
|
|
1083
|
+
"PreservedFields": {
|
|
1084
|
+
"resourceType": "K8s::Argo::ApplicationSet.preservedFields",
|
|
1085
|
+
"kind": "property",
|
|
1086
|
+
"lexicon": "k8s"
|
|
1087
|
+
},
|
|
902
1088
|
"PriorityClass": {
|
|
903
1089
|
"resourceType": "K8s::Scheduling::PriorityClass",
|
|
904
1090
|
"kind": "resource",
|
|
@@ -1330,6 +1516,21 @@
|
|
|
1330
1516
|
}
|
|
1331
1517
|
}
|
|
1332
1518
|
},
|
|
1519
|
+
"SignatureKey": {
|
|
1520
|
+
"resourceType": "K8s::Argo::AppProject.signatureKeys",
|
|
1521
|
+
"kind": "property",
|
|
1522
|
+
"lexicon": "k8s"
|
|
1523
|
+
},
|
|
1524
|
+
"Source": {
|
|
1525
|
+
"resourceType": "K8s::Argo::Application.source",
|
|
1526
|
+
"kind": "property",
|
|
1527
|
+
"lexicon": "k8s"
|
|
1528
|
+
},
|
|
1529
|
+
"Sources": {
|
|
1530
|
+
"resourceType": "K8s::Argo::Application.sources",
|
|
1531
|
+
"kind": "property",
|
|
1532
|
+
"lexicon": "k8s"
|
|
1533
|
+
},
|
|
1333
1534
|
"StatefulSet": {
|
|
1334
1535
|
"resourceType": "K8s::Apps::StatefulSet",
|
|
1335
1536
|
"kind": "resource",
|
|
@@ -1370,6 +1571,11 @@
|
|
|
1370
1571
|
"apiVersion": "storage.k8s.io/v1",
|
|
1371
1572
|
"gvkKind": "StorageClassList"
|
|
1372
1573
|
},
|
|
1574
|
+
"Strategy": {
|
|
1575
|
+
"resourceType": "K8s::Argo::ApplicationSet.strategy",
|
|
1576
|
+
"kind": "property",
|
|
1577
|
+
"lexicon": "k8s"
|
|
1578
|
+
},
|
|
1373
1579
|
"Subject": {
|
|
1374
1580
|
"resourceType": "K8s::Rbac::Subject",
|
|
1375
1581
|
"kind": "property",
|
|
@@ -1392,11 +1598,21 @@
|
|
|
1392
1598
|
"kind": "property",
|
|
1393
1599
|
"lexicon": "k8s"
|
|
1394
1600
|
},
|
|
1601
|
+
"SyncWindow": {
|
|
1602
|
+
"resourceType": "K8s::Argo::AppProject.syncWindows",
|
|
1603
|
+
"kind": "property",
|
|
1604
|
+
"lexicon": "k8s"
|
|
1605
|
+
},
|
|
1395
1606
|
"TCPSocketAction": {
|
|
1396
1607
|
"resourceType": "K8s::Core::TCPSocketAction",
|
|
1397
1608
|
"kind": "property",
|
|
1398
1609
|
"lexicon": "k8s"
|
|
1399
1610
|
},
|
|
1611
|
+
"Template": {
|
|
1612
|
+
"resourceType": "K8s::Argo::ApplicationSet.template",
|
|
1613
|
+
"kind": "property",
|
|
1614
|
+
"lexicon": "k8s"
|
|
1615
|
+
},
|
|
1400
1616
|
"TokenRequest": {
|
|
1401
1617
|
"resourceType": "K8s::Authentication::TokenRequest",
|
|
1402
1618
|
"kind": "resource",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
2
|
+
import { findResourceLiterals, getNestedObject, getNestedString, lineCol } from "./argo-ast";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ARGO004: ApplicationSet template must scope to a single AppProject
|
|
6
|
+
*
|
|
7
|
+
* An `ApplicationSet` generates many Applications from one template. The
|
|
8
|
+
* template's `spec.project` should name a single, static `AppProject` so every
|
|
9
|
+
* generated Application lands in the same security boundary. If `project` is
|
|
10
|
+
* missing, the generated Applications fall back to whatever default and dodge
|
|
11
|
+
* project-level RBAC; if it is templated with a generator placeholder
|
|
12
|
+
* (`{{...}}`) the set sprays Applications across many projects, which defeats
|
|
13
|
+
* the point of an AppProject as a guardrail.
|
|
14
|
+
*
|
|
15
|
+
* Bad: new ApplicationSet({ spec: { template: { spec: { repoURL: "..." } } } }) // no project
|
|
16
|
+
* Bad: new ApplicationSet({ spec: { template: { spec: { project: "{{path.basename}}" } } } }) // templated
|
|
17
|
+
* Good: new ApplicationSet({ spec: { template: { spec: { project: "team-a" } } } })
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const argoAppSetSingleProjectRule: LintRule = {
|
|
21
|
+
id: "ARGO004",
|
|
22
|
+
severity: "warning",
|
|
23
|
+
category: "correctness",
|
|
24
|
+
description:
|
|
25
|
+
"ApplicationSet template must scope to a single static AppProject (spec.template.spec.project)",
|
|
26
|
+
|
|
27
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
28
|
+
const { sourceFile } = context;
|
|
29
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
30
|
+
|
|
31
|
+
for (const { literal } of findResourceLiterals(sourceFile, new Set(["ApplicationSet"]))) {
|
|
32
|
+
const templateSpec = getNestedObject(literal, ["spec", "template", "spec"]);
|
|
33
|
+
// No template/spec to inspect — leave it to other tooling.
|
|
34
|
+
if (!templateSpec) continue;
|
|
35
|
+
|
|
36
|
+
const project = getNestedString(literal, ["spec", "template", "spec", "project"]);
|
|
37
|
+
const { line, column } = lineCol(sourceFile, templateSpec);
|
|
38
|
+
|
|
39
|
+
if (project === undefined) {
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
file: sourceFile.fileName,
|
|
42
|
+
line,
|
|
43
|
+
column,
|
|
44
|
+
ruleId: "ARGO004",
|
|
45
|
+
severity: "warning",
|
|
46
|
+
message:
|
|
47
|
+
"ApplicationSet template has no spec.project — scope it to a single AppProject so every generated Application inherits the same RBAC boundary.",
|
|
48
|
+
});
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (project.includes("{{")) {
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
file: sourceFile.fileName,
|
|
55
|
+
line,
|
|
56
|
+
column,
|
|
57
|
+
ruleId: "ARGO004",
|
|
58
|
+
severity: "warning",
|
|
59
|
+
message: `ApplicationSet template scopes spec.project to a generator placeholder ("${project}"), spraying Applications across projects. Pin it to a single static AppProject.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return diagnostics;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared AST helpers for the Argo declarative lint rules (ARGO001, ARGO004).
|
|
3
|
+
*
|
|
4
|
+
* The Argo CRDs are constructed like any other k8s resource —
|
|
5
|
+
* `new Application({ metadata, spec })` / `new ApplicationSet({ ... })`. These
|
|
6
|
+
* helpers walk the object-literal first argument so a rule can read a nested
|
|
7
|
+
* property by path without re-implementing the traversal each time.
|
|
8
|
+
*/
|
|
9
|
+
import * as ts from "typescript";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Find the object-literal first argument of every `new <Kind>(...)` expression
|
|
13
|
+
* in the file, where Kind is one of the supplied resource kinds.
|
|
14
|
+
*/
|
|
15
|
+
export function findResourceLiterals(
|
|
16
|
+
sourceFile: ts.SourceFile,
|
|
17
|
+
kinds: Set<string>,
|
|
18
|
+
): Array<{ kind: string; literal: ts.ObjectLiteralExpression; node: ts.NewExpression }> {
|
|
19
|
+
const found: Array<{ kind: string; literal: ts.ObjectLiteralExpression; node: ts.NewExpression }> = [];
|
|
20
|
+
|
|
21
|
+
function visit(node: ts.Node): void {
|
|
22
|
+
if (
|
|
23
|
+
ts.isNewExpression(node) &&
|
|
24
|
+
ts.isIdentifier(node.expression) &&
|
|
25
|
+
kinds.has(node.expression.text) &&
|
|
26
|
+
node.arguments &&
|
|
27
|
+
node.arguments.length > 0 &&
|
|
28
|
+
ts.isObjectLiteralExpression(node.arguments[0])
|
|
29
|
+
) {
|
|
30
|
+
found.push({
|
|
31
|
+
kind: node.expression.text,
|
|
32
|
+
literal: node.arguments[0],
|
|
33
|
+
node,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
ts.forEachChild(node, visit);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
visit(sourceFile);
|
|
40
|
+
return found;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Read the initializer node for `key` on an object literal, if present. */
|
|
44
|
+
export function getProp(
|
|
45
|
+
obj: ts.ObjectLiteralExpression,
|
|
46
|
+
key: string,
|
|
47
|
+
): ts.Expression | undefined {
|
|
48
|
+
for (const prop of obj.properties) {
|
|
49
|
+
if (
|
|
50
|
+
ts.isPropertyAssignment(prop) &&
|
|
51
|
+
(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name)) &&
|
|
52
|
+
prop.name.text === key
|
|
53
|
+
) {
|
|
54
|
+
return prop.initializer;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Read a nested object literal by walking `path` (each segment an object). */
|
|
61
|
+
export function getNestedObject(
|
|
62
|
+
obj: ts.ObjectLiteralExpression,
|
|
63
|
+
path: string[],
|
|
64
|
+
): ts.ObjectLiteralExpression | undefined {
|
|
65
|
+
let current: ts.ObjectLiteralExpression | undefined = obj;
|
|
66
|
+
for (const segment of path) {
|
|
67
|
+
if (!current) return undefined;
|
|
68
|
+
const next = getProp(current, segment);
|
|
69
|
+
current = next && ts.isObjectLiteralExpression(next) ? next : undefined;
|
|
70
|
+
}
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Read a string-literal property value by path (last segment is the key). */
|
|
75
|
+
export function getNestedString(
|
|
76
|
+
obj: ts.ObjectLiteralExpression,
|
|
77
|
+
path: string[],
|
|
78
|
+
): string | undefined {
|
|
79
|
+
const key = path[path.length - 1];
|
|
80
|
+
const parent = getNestedObject(obj, path.slice(0, -1));
|
|
81
|
+
if (!parent) return undefined;
|
|
82
|
+
const value = getProp(parent, key);
|
|
83
|
+
return value && ts.isStringLiteral(value) ? value.text : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Read a boolean-literal property value by path (last segment is the key). */
|
|
87
|
+
export function getNestedBoolean(
|
|
88
|
+
obj: ts.ObjectLiteralExpression,
|
|
89
|
+
path: string[],
|
|
90
|
+
): boolean | undefined {
|
|
91
|
+
const key = path[path.length - 1];
|
|
92
|
+
const parent = getNestedObject(obj, path.slice(0, -1));
|
|
93
|
+
if (!parent) return undefined;
|
|
94
|
+
const value = getProp(parent, key);
|
|
95
|
+
if (!value) return undefined;
|
|
96
|
+
if (value.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
97
|
+
if (value.kind === ts.SyntaxKind.FalseKeyword) return false;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* True if the literal carries the given annotation key under
|
|
103
|
+
* `metadata.annotations`, regardless of value.
|
|
104
|
+
*/
|
|
105
|
+
export function hasAnnotation(
|
|
106
|
+
obj: ts.ObjectLiteralExpression,
|
|
107
|
+
annotationKey: string,
|
|
108
|
+
): boolean {
|
|
109
|
+
const annotations = getNestedObject(obj, ["metadata", "annotations"]);
|
|
110
|
+
if (!annotations) return false;
|
|
111
|
+
return getProp(annotations, annotationKey) !== undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Line/column (1-based) for a node, for diagnostics. */
|
|
115
|
+
export function lineCol(
|
|
116
|
+
sourceFile: ts.SourceFile,
|
|
117
|
+
node: ts.Node,
|
|
118
|
+
): { line: number; column: number } {
|
|
119
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
120
|
+
return { line: line + 1, column: character + 1 };
|
|
121
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
|
|
2
|
+
import {
|
|
3
|
+
findResourceLiterals,
|
|
4
|
+
getNestedBoolean,
|
|
5
|
+
getNestedString,
|
|
6
|
+
getProp,
|
|
7
|
+
hasAnnotation,
|
|
8
|
+
lineCol,
|
|
9
|
+
} from "./argo-ast";
|
|
10
|
+
import * as ts from "typescript";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ARGO001: Production Application must not enable automated prune
|
|
14
|
+
*
|
|
15
|
+
* An Argo CD `Application` whose `syncPolicy.automated.prune` is `true` lets the
|
|
16
|
+
* controller delete live resources that disappear from git. On a production
|
|
17
|
+
* Application that is a foot-gun — a bad merge can sweep away running
|
|
18
|
+
* infrastructure. Require `prune: false` for prod Applications unless the author
|
|
19
|
+
* opts in explicitly with the `argocd.chant.dev/allow-prune` annotation.
|
|
20
|
+
*
|
|
21
|
+
* "Production" is inferred from the Application name, its `metadata.namespace`,
|
|
22
|
+
* or its `spec.destination.namespace` containing `prod`.
|
|
23
|
+
*
|
|
24
|
+
* Bad: new Application({ metadata: { name: "api-prod" }, spec: { syncPolicy: { automated: { prune: true } } } })
|
|
25
|
+
* Good: new Application({ metadata: { name: "api-prod" }, spec: { syncPolicy: { automated: { prune: false } } } })
|
|
26
|
+
* Good: new Application({ metadata: { name: "api-prod", annotations: { "argocd.chant.dev/allow-prune": "true" } }, spec: { syncPolicy: { automated: { prune: true } } } })
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const ALLOW_PRUNE_ANNOTATION = "argocd.chant.dev/allow-prune";
|
|
30
|
+
|
|
31
|
+
function looksProd(obj: ts.ObjectLiteralExpression): boolean {
|
|
32
|
+
const candidates = [
|
|
33
|
+
getNestedString(obj, ["metadata", "name"]),
|
|
34
|
+
getNestedString(obj, ["metadata", "namespace"]),
|
|
35
|
+
getNestedString(obj, ["spec", "destination", "namespace"]),
|
|
36
|
+
];
|
|
37
|
+
return candidates.some((v) => v !== undefined && /prod/i.test(v));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const argoAutomatedPruneRule: LintRule = {
|
|
41
|
+
id: "ARGO001",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
category: "correctness",
|
|
44
|
+
description:
|
|
45
|
+
"Production Argo Application must not enable syncPolicy.automated.prune without the allow-prune override annotation",
|
|
46
|
+
|
|
47
|
+
check(context: LintContext): LintDiagnostic[] {
|
|
48
|
+
const { sourceFile } = context;
|
|
49
|
+
const diagnostics: LintDiagnostic[] = [];
|
|
50
|
+
|
|
51
|
+
for (const { literal } of findResourceLiterals(sourceFile, new Set(["Application"]))) {
|
|
52
|
+
const prune = getNestedBoolean(literal, ["spec", "syncPolicy", "automated", "prune"]);
|
|
53
|
+
if (prune !== true) continue;
|
|
54
|
+
if (!looksProd(literal)) continue;
|
|
55
|
+
if (hasAnnotation(literal, ALLOW_PRUNE_ANNOTATION)) continue;
|
|
56
|
+
|
|
57
|
+
// Anchor the diagnostic at the `prune` assignment when we can find it.
|
|
58
|
+
const automated = getProp(literal, "spec");
|
|
59
|
+
const anchor: ts.Node = automated ?? literal;
|
|
60
|
+
const { line, column } = lineCol(sourceFile, anchor);
|
|
61
|
+
|
|
62
|
+
const name = getNestedString(literal, ["metadata", "name"]) ?? "(unnamed)";
|
|
63
|
+
diagnostics.push({
|
|
64
|
+
file: sourceFile.fileName,
|
|
65
|
+
line,
|
|
66
|
+
column,
|
|
67
|
+
ruleId: "ARGO001",
|
|
68
|
+
severity: "warning",
|
|
69
|
+
message: `Production Application "${name}" enables automated prune. Set syncPolicy.automated.prune=false, or opt in explicitly with the "${ALLOW_PRUNE_ANNOTATION}" annotation.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return diagnostics;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the Argo post-synth checks (ARGO002, ARGO003, ARGO005).
|
|
3
|
+
*
|
|
4
|
+
* Excluded from check auto-discovery by the "helper" filename filter.
|
|
5
|
+
*/
|
|
6
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
7
|
+
import { getPrimaryOutput, parseK8sManifests, type K8sManifest } from "./k8s-helpers";
|
|
8
|
+
|
|
9
|
+
/** The always-present, in-cluster Argo destination. */
|
|
10
|
+
export const IN_CLUSTER_SERVER = "https://kubernetes.default.svc";
|
|
11
|
+
export const IN_CLUSTER_NAME = "in-cluster";
|
|
12
|
+
|
|
13
|
+
/** Label that marks a Secret as an Argo CD external cluster registration. */
|
|
14
|
+
export const CLUSTER_SECRET_TYPE_LABEL = "argocd.argoproj.io/secret-type";
|
|
15
|
+
|
|
16
|
+
/** All manifests across every lexicon output. */
|
|
17
|
+
export function allManifests(ctx: PostSynthContext): K8sManifest[] {
|
|
18
|
+
const manifests: K8sManifest[] = [];
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
manifests.push(...parseK8sManifests(getPrimaryOutput(output)));
|
|
21
|
+
}
|
|
22
|
+
return manifests;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Manifests of a given Argo kind. */
|
|
26
|
+
export function manifestsOfKind(manifests: K8sManifest[], kind: string): K8sManifest[] {
|
|
27
|
+
return manifests.filter((m) => m.kind === kind);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read a string field from a Secret's stringData or data block.
|
|
32
|
+
* (Argo cluster Secrets conventionally use stringData.)
|
|
33
|
+
*/
|
|
34
|
+
export function secretField(manifest: K8sManifest, key: string): string | undefined {
|
|
35
|
+
const stringData = manifest.stringData as Record<string, unknown> | undefined;
|
|
36
|
+
const data = manifest.data as Record<string, unknown> | undefined;
|
|
37
|
+
const fromStringData = stringData?.[key];
|
|
38
|
+
if (typeof fromStringData === "string") return fromStringData;
|
|
39
|
+
const fromData = data?.[key];
|
|
40
|
+
if (typeof fromData === "string") return fromData;
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** True if the Secret is labelled as an Argo cluster registration. */
|
|
45
|
+
export function isClusterSecret(manifest: K8sManifest): boolean {
|
|
46
|
+
if (manifest.kind !== "Secret") return false;
|
|
47
|
+
const labels = manifest.metadata?.labels;
|
|
48
|
+
return labels?.[CLUSTER_SECRET_TYPE_LABEL] === "cluster";
|
|
49
|
+
}
|