@rancher/shell 3.0.12-rc.2 → 3.0.12-rc.3

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 (272) hide show
  1. package/apis/impl/apis.ts +6 -0
  2. package/apis/index.ts +26 -0
  3. package/apis/intf/resources-api/cluster-api.ts +18 -0
  4. package/apis/intf/resources-api/mgmt-api.ts +15 -0
  5. package/apis/intf/resources-api/resource-base.ts +107 -0
  6. package/apis/intf/resources-api/resource-constants.ts +147 -0
  7. package/apis/intf/resources-api/resources-api.ts +143 -0
  8. package/apis/intf/resources.ts +49 -0
  9. package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
  10. package/apis/intf/shell-api/proxy.ts +216 -0
  11. package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
  12. package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
  13. package/apis/intf/shell.ts +12 -6
  14. package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
  15. package/apis/resources/index.ts +22 -0
  16. package/apis/resources/resources-api-class.ts +187 -0
  17. package/apis/shell/__tests__/proxy.test.ts +369 -0
  18. package/apis/shell/index.ts +8 -1
  19. package/apis/shell/modal.ts +4 -1
  20. package/apis/shell/notifications.ts +9 -6
  21. package/apis/shell/proxy.ts +256 -0
  22. package/apis/shell/slide-in.ts +4 -1
  23. package/apis/vue-shim.d.ts +2 -1
  24. package/assets/data/aws-regions.json +4 -0
  25. package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
  26. package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
  27. package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
  28. package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
  29. package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
  30. package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
  31. package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
  32. package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
  33. package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
  34. package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
  35. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
  36. package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
  37. package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
  38. package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
  39. package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
  40. package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
  41. package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
  42. package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
  43. package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
  44. package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
  45. package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
  46. package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
  47. package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
  48. package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
  49. package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
  50. package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
  51. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
  52. package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
  53. package/assets/styles/base/_variables.scss +2 -0
  54. package/assets/styles/fonts/_fontstack.scss +132 -8
  55. package/assets/translations/en-us.yaml +22 -5
  56. package/chart/monitoring/index.vue +10 -1
  57. package/components/ActionDropdownShell.vue +2 -1
  58. package/components/CruResourceFooter.vue +9 -5
  59. package/components/ExplorerProjectsNamespaces.vue +1 -1
  60. package/components/InstallHelmCharts.vue +2 -2
  61. package/components/LandingPagePreference.vue +14 -5
  62. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
  63. package/components/Resource/Detail/Metadata/index.vue +6 -0
  64. package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
  65. package/components/Resource/Detail/SpacedRow.vue +3 -1
  66. package/components/Resource/Detail/TitleBar/index.vue +10 -11
  67. package/components/ResourceList/Masthead.vue +12 -8
  68. package/components/SelectIconGrid.vue +0 -10
  69. package/components/SingleClusterInfo.vue +1 -0
  70. package/components/SortableTable/__tests__/sorting.test.ts +126 -0
  71. package/components/SortableTable/index.vue +6 -9
  72. package/components/SortableTable/selection.js +23 -5
  73. package/components/SortableTable/sorting.js +6 -3
  74. package/components/Wizard.vue +14 -13
  75. package/components/fleet/FleetBundles.vue +100 -12
  76. package/components/fleet/FleetClusterTargets/index.vue +37 -15
  77. package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
  78. package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
  79. package/components/form/LabeledSelect.vue +20 -3
  80. package/components/form/NameNsDescription.vue +11 -0
  81. package/components/form/Security.vue +6 -2
  82. package/components/form/WorkloadPorts.vue +2 -7
  83. package/components/form/__tests__/Security.test.ts +76 -0
  84. package/components/formatter/Autoscaler.vue +4 -4
  85. package/components/formatter/ClusterKubeVersion.vue +27 -0
  86. package/components/formatter/ClusterLink.vue +1 -7
  87. package/components/formatter/ClusterProvider.vue +6 -10
  88. package/components/formatter/FleetSummaryGraph.vue +0 -3
  89. package/components/formatter/MachineSummaryGraph.vue +1 -1
  90. package/components/formatter/PodsUsage.vue +2 -2
  91. package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
  92. package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
  93. package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
  94. package/components/nav/NamespaceFilter.vue +2 -2
  95. package/components/nav/TopLevelMenu.helper.ts +15 -3
  96. package/components/nav/TopLevelMenu.vue +16 -5
  97. package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
  98. package/components/templates/home.vue +18 -0
  99. package/components/templates/plain.vue +18 -0
  100. package/components/templates/standalone.vue +17 -0
  101. package/composables/useFormValidation.ts +93 -0
  102. package/composables/useVeeValidateField.test.ts +159 -0
  103. package/composables/useVeeValidateField.ts +67 -0
  104. package/config/pagination-table-headers.js +18 -1
  105. package/config/product/manager.js +82 -21
  106. package/config/router/routes.js +6 -0
  107. package/config/table-headers.js +20 -1
  108. package/config/types.js +2 -1
  109. package/core/__tests__/plugin-products.test.ts +904 -20
  110. package/core/plugin-products-base.ts +107 -7
  111. package/core/plugin-products.ts +4 -0
  112. package/core/plugin-types.ts +111 -1
  113. package/core/plugin.ts +15 -7
  114. package/core/productDebugger.js +9 -4
  115. package/core/types-provisioning.ts +43 -30
  116. package/core/types.ts +57 -20
  117. package/detail/__tests__/pod.test.ts +41 -0
  118. package/detail/harvesterhci.io.management.cluster.vue +6 -2
  119. package/detail/pod.vue +1 -1
  120. package/detail/provisioning.cattle.io.cluster.vue +4 -10
  121. package/edit/auth/__tests__/azuread.test.ts +217 -34
  122. package/edit/auth/azuread.vue +122 -14
  123. package/edit/auth/oidc.vue +2 -2
  124. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
  125. package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
  126. package/edit/networking.k8s.io.ingress/index.vue +75 -20
  127. package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
  128. package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
  129. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
  130. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
  131. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
  132. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
  133. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
  134. package/edit/secret/__tests__/ssh.test.ts +5 -6
  135. package/edit/secret/basic.vue +31 -0
  136. package/edit/secret/index.vue +68 -17
  137. package/edit/secret/registry.vue +38 -0
  138. package/edit/secret/ssh.vue +29 -0
  139. package/edit/secret/tls.vue +30 -0
  140. package/edit/service.vue +4 -4
  141. package/edit/workload/Upgrading.vue +3 -3
  142. package/edit/workload/__tests__/Upgrading.test.ts +6 -9
  143. package/edit/workload/mixins/workload.js +2 -1
  144. package/list/fleet.cattle.io.bundle.vue +7 -104
  145. package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
  146. package/list/provisioning.cattle.io.cluster.vue +262 -180
  147. package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
  148. package/mixins/__tests__/chart.test.ts +112 -0
  149. package/mixins/brand.js +2 -1
  150. package/mixins/chart.js +12 -8
  151. package/mixins/resource-fetch-api-pagination.js +41 -5
  152. package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
  153. package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
  154. package/models/__tests__/management.cattle.io.node.ts +6 -5
  155. package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
  156. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
  157. package/models/base-cluster.x-k8s.io.js +26 -0
  158. package/models/cluster.js +1 -1
  159. package/models/cluster.x-k8s.io.machine.js +4 -22
  160. package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
  161. package/models/cluster.x-k8s.io.machineset.js +2 -20
  162. package/models/compliance.cattle.io.clusterscan.js +130 -2
  163. package/models/ext.cattle.io.kubeconfig.ts +4 -7
  164. package/models/fleet-application.js +3 -1
  165. package/models/management.cattle.io.cluster.js +417 -40
  166. package/models/management.cattle.io.node.js +6 -4
  167. package/models/management.cattle.io.nodepool.js +1 -1
  168. package/models/networking.k8s.io.ingress.js +12 -4
  169. package/models/provisioning.cattle.io.cluster.js +47 -330
  170. package/models/rke.cattle.io.etcdsnapshot.js +1 -2
  171. package/package.json +11 -29
  172. package/pages/__tests__/readme.test.ts +49 -0
  173. package/pages/auth/setup.vue +2 -3
  174. package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
  175. package/pages/c/_cluster/apps/charts/chart.vue +60 -8
  176. package/pages/c/_cluster/apps/charts/install.vue +10 -7
  177. package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
  178. package/pages/c/_cluster/explorer/index.vue +5 -49
  179. package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
  180. package/pages/c/_cluster/istio/index.vue +21 -6
  181. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
  182. package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
  183. package/pages/c/_cluster/uiplugins/index.vue +203 -197
  184. package/pages/diagnostic.vue +13 -17
  185. package/pages/fail-whale.vue +18 -0
  186. package/pages/home.vue +77 -260
  187. package/pages/readme.vue +88 -0
  188. package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
  189. package/plugins/dashboard-store/actions.js +40 -18
  190. package/plugins/dashboard-store/resource-class.js +5 -2
  191. package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
  192. package/plugins/steve/steve-pagination-utils.ts +11 -3
  193. package/plugins/steve/subscribe.js +35 -5
  194. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
  195. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
  196. package/rancher-components/RcButton/RcButton.test.ts +37 -1
  197. package/rancher-components/RcButton/RcButton.vue +38 -8
  198. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
  199. package/store/__tests__/catalog.test.ts +115 -1
  200. package/store/__tests__/type-map.test.ts +556 -1
  201. package/store/action-menu.js +8 -3
  202. package/store/auth.js +1 -1
  203. package/store/aws.js +27 -16
  204. package/store/catalog.js +27 -3
  205. package/store/digitalocean.js +20 -38
  206. package/store/index.js +2 -0
  207. package/store/linode.js +25 -40
  208. package/store/pnap.js +1 -0
  209. package/store/type-map.js +111 -29
  210. package/tsconfig.paths.json +8 -8
  211. package/types/kube/kube-api.ts +14 -1
  212. package/types/rancher/steve.api.ts +12 -12
  213. package/types/resources/settings.d.ts +2 -1
  214. package/types/shell/index.d.ts +102 -2
  215. package/types/store/dashboard-store.types.ts +108 -11
  216. package/types/store/pagination.types.ts +6 -3
  217. package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
  218. package/utils/__tests__/async.test.ts +87 -0
  219. package/utils/__tests__/aws.test.ts +140 -0
  220. package/utils/__tests__/banners.test.ts +176 -0
  221. package/utils/__tests__/chart.test.ts +64 -1
  222. package/utils/__tests__/color.test.ts +226 -0
  223. package/utils/__tests__/duration.test.ts +140 -0
  224. package/utils/__tests__/fleet.test.ts +340 -0
  225. package/utils/__tests__/ingress.test.ts +553 -0
  226. package/utils/__tests__/kube.test.ts +68 -0
  227. package/utils/__tests__/namespace-filter.test.ts +109 -0
  228. package/utils/__tests__/pagination-utils.test.ts +361 -0
  229. package/utils/__tests__/parse-externalid.test.ts +137 -0
  230. package/utils/__tests__/perf-setting.utils.test.ts +98 -0
  231. package/utils/__tests__/poller-sequential.test.ts +177 -0
  232. package/utils/__tests__/poller.test.ts +170 -0
  233. package/utils/__tests__/promise.test.ts +346 -0
  234. package/utils/__tests__/settings.test.ts +140 -0
  235. package/utils/__tests__/sort-utils.test.ts +301 -0
  236. package/utils/__tests__/string-utils.test.ts +798 -0
  237. package/utils/__tests__/string.test.ts +23 -1
  238. package/utils/__tests__/style.test.ts +154 -0
  239. package/utils/__tests__/svg-filter.test.ts +184 -0
  240. package/utils/__tests__/units.test.ts +417 -0
  241. package/utils/__tests__/versions.test.ts +128 -0
  242. package/utils/__tests__/xccdf.test.ts +391 -0
  243. package/utils/chart.js +36 -0
  244. package/utils/fleet.ts +13 -3
  245. package/utils/gatekeeper/__tests__/util.test.ts +174 -0
  246. package/utils/gc/__tests__/gc-interval.test.ts +119 -0
  247. package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
  248. package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
  249. package/utils/gc/__tests__/gc.test.ts +487 -0
  250. package/utils/ingress.ts +9 -1
  251. package/utils/pagination-utils.ts +2 -1
  252. package/utils/string.js +25 -2
  253. package/utils/uiplugins.ts +5 -5
  254. package/utils/validators/__tests__/cluster-name.test.ts +110 -0
  255. package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
  256. package/utils/validators/__tests__/index.test.ts +481 -0
  257. package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
  258. package/utils/validators/__tests__/misc-validators.test.ts +246 -0
  259. package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
  260. package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
  261. package/utils/validators/__tests__/role-template.test.ts +149 -0
  262. package/utils/validators/__tests__/service.test.ts +283 -0
  263. package/utils/validators/__tests__/setting.test.js +32 -0
  264. package/utils/validators/formRules/__tests__/index.test.ts +50 -0
  265. package/utils/validators/formRules/index.ts +5 -5
  266. package/utils/validators/machine-pool.ts +1 -1
  267. package/utils/validators/setting.js +18 -3
  268. package/utils/xccdf.ts +418 -0
  269. package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
  270. package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
  271. package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
  272. package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
@@ -0,0 +1,391 @@
1
+ import { generateXCCDF, generateXCCDFPerNode } from '@shell/utils/xccdf';
2
+
3
+ describe('xccdf util: generateXCCDF', () => {
4
+ const baseReport = {
5
+ version: '1.0',
6
+ total: 2,
7
+ pass: 1,
8
+ nodes: { master: ['node-a'], node: ['node-b'] },
9
+ results: [{
10
+ id: '5.1',
11
+ description: 'RBAC and Service Accounts',
12
+ checks: [{
13
+ id: '5.1.1',
14
+ description: 'Ensure that the cluster-admin role is only used where required',
15
+ audit: 'kubectl get clusterrolebindings',
16
+ remediation: 'Identify all clusterrolebindings to the cluster-admin role',
17
+ scored: true,
18
+ state: 'pass',
19
+ }, {
20
+ id: '5.1.2',
21
+ description: 'Minimize access to secrets',
22
+ audit: 'kubectl get roles',
23
+ remediation: 'Where possible, remove get, list and watch access to Secret objects',
24
+ scored: false,
25
+ state: 'fail',
26
+ }],
27
+ }],
28
+ };
29
+
30
+ it('produces an XML document with an XML header and a Benchmark root', () => {
31
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
32
+
33
+ expect(xml).toMatch(/^<\?xml version="1\.0" encoding="UTF-8"\?>/);
34
+ expect(xml).toContain('<Benchmark');
35
+ expect(xml).toContain('xmlns="http://checklists.nist.gov/xccdf/1.2"');
36
+ expect(xml).toContain('id="xccdf_compliance-operator_benchmark_kubernetes"');
37
+ });
38
+
39
+ it('uses the metadata title when provided, otherwise falls back to a CIS title from benchmarkVersion', () => {
40
+ const fallback = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
41
+
42
+ expect(fallback).toContain('<title>Kubernetes CIS Benchmark cis-1.7</title>');
43
+
44
+ const withTitle = generateXCCDF({
45
+ report: baseReport,
46
+ benchmarkVersion: 'cis-1.7',
47
+ metadata: { title: 'Custom Benchmark Title' },
48
+ });
49
+
50
+ expect(withTitle).toContain('<title>Custom Benchmark Title</title>');
51
+ });
52
+
53
+ it('emits one Group per result and one Rule per check', () => {
54
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
55
+
56
+ expect(xml).toContain('<Group id="5.1">');
57
+ expect(xml).toContain('id="xccdf_compliance-operator_rule_5.1.1"');
58
+ expect(xml).toContain('id="xccdf_compliance-operator_rule_5.1.2"');
59
+ });
60
+
61
+ it('maps state values to XCCDF result strings', () => {
62
+ const report = {
63
+ ...baseReport,
64
+ results: [{
65
+ id: '1',
66
+ description: 'g',
67
+ checks: [
68
+ {
69
+ id: 'a', description: 'a', state: 'pass' as const
70
+ },
71
+ {
72
+ id: 'b', description: 'b', state: 'fail' as const
73
+ },
74
+ {
75
+ id: 'c', description: 'c', state: 'skip' as const
76
+ },
77
+ {
78
+ id: 'd', description: 'd', state: 'warn' as const
79
+ },
80
+ {
81
+ id: 'e', description: 'e', state: 'notApplicable' as const
82
+ },
83
+ ],
84
+ }],
85
+ };
86
+
87
+ const xml = generateXCCDF({ report, benchmarkVersion: 'cis-1.7' });
88
+
89
+ expect(xml).toContain('<result>pass</result>');
90
+ expect(xml).toContain('<result>fail</result>');
91
+ expect(xml).toContain('<result>notselected</result>');
92
+ expect(xml).toContain('<result>informational</result>');
93
+ expect(xml).toContain('<result>notapplicable</result>');
94
+ });
95
+
96
+ it('marks scored checks with severity=medium and weight=10; unscored as low and 0', () => {
97
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
98
+
99
+ expect(xml).toMatch(/id="xccdf_compliance-operator_rule_5\.1\.1"[^>]*severity="medium"/);
100
+ expect(xml).toMatch(/id="xccdf_compliance-operator_rule_5\.1\.1"[^>]*weight="10"/);
101
+ expect(xml).toMatch(/id="xccdf_compliance-operator_rule_5\.1\.2"[^>]*severity="low"/);
102
+ expect(xml).toMatch(/id="xccdf_compliance-operator_rule_5\.1\.2"[^>]*weight="0"/);
103
+ });
104
+
105
+ it('computes score as pass/total*100, formatted to one decimal', () => {
106
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
107
+
108
+ expect(xml).toMatch(/<score[^>]*maximum="100\.0"[^>]*>50\.0<\/score>/);
109
+ });
110
+
111
+ it('returns score 0.0 when total is 0', () => {
112
+ const xml = generateXCCDF({
113
+ report: {
114
+ ...baseReport, total: 0, pass: 0, results: [],
115
+ },
116
+ benchmarkVersion: 'cis-1.7',
117
+ });
118
+
119
+ expect(xml).toMatch(/<score[^>]*>0\.0<\/score>/);
120
+ });
121
+
122
+ it('uses report.nodes for <target> elements when no clusterName is set', () => {
123
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
124
+
125
+ expect(xml).toContain('<target>node-a</target>');
126
+ expect(xml).toContain('<target>node-b</target>');
127
+ });
128
+
129
+ it('clusterName argument overrides metadata.clusterName and node-derived targets', () => {
130
+ const xml = generateXCCDF({
131
+ report: baseReport,
132
+ benchmarkVersion: 'cis-1.7',
133
+ metadata: { clusterName: 'from-metadata' },
134
+ clusterName: 'from-arg',
135
+ });
136
+
137
+ expect(xml).toContain('<target>from-arg</target>');
138
+ expect(xml).not.toContain('<target>from-metadata</target>');
139
+ expect(xml).not.toContain('<target>node-a</target>');
140
+ });
141
+
142
+ it('falls back to "not-applicable" for missing target addresses and target facts', () => {
143
+ const xml = generateXCCDF({ report: baseReport, benchmarkVersion: 'cis-1.7' });
144
+
145
+ expect(xml).toContain('<target-address>not-applicable</target-address>');
146
+ expect(xml).toContain('<fact name="not-applicable" type="string">not-applicable</fact>');
147
+ });
148
+
149
+ it('emits a single placeholder Group when results is empty', () => {
150
+ const xml = generateXCCDF({
151
+ report: {
152
+ version: '1.0', total: 0, pass: 0, results: [],
153
+ },
154
+ benchmarkVersion: 'cis-1.7',
155
+ });
156
+
157
+ expect(xml).toContain('<Group id="not-applicable">');
158
+ });
159
+
160
+ it('uses STIG metadata to override rule id, version, severity, fix, check system, and CCI idents', () => {
161
+ const xml = generateXCCDF({
162
+ report: {
163
+ ...baseReport,
164
+ results: [{
165
+ id: 'g1',
166
+ description: 'g',
167
+ checks: [{
168
+ id: 'V-254553-TLS-apiserver', description: 'STIG check', scored: true, state: 'pass',
169
+ }],
170
+ }],
171
+ },
172
+ benchmarkVersion: 'rke2-stig-1.31',
173
+ stigChecks: {
174
+ 'V-254553': {
175
+ ruleId: 'SV-254553r1016525_rule', version: 'SV-254553r1016525_rule', severity: 'high', fixId: 'F-254553', checkId: 'C-254553', cci: ['CCI-000366'],
176
+ },
177
+ },
178
+ });
179
+
180
+ expect(xml).toContain('idref="SV-254553r1016525_rule_TLS-apiserver"');
181
+ expect(xml).toContain('severity="high"');
182
+ expect(xml).toContain('<ident system="http://cyber.mil/cci">CCI-000366</ident>');
183
+ expect(xml).toContain('fixref="F-254553"');
184
+ expect(xml).toContain('<check system="C-254553">');
185
+ });
186
+
187
+ it('preserves the operator-style rule id when there is no STIG metadata', () => {
188
+ const xml = generateXCCDF({
189
+ report: baseReport,
190
+ benchmarkVersion: 'cis-1.7',
191
+ });
192
+
193
+ expect(xml).toContain('id="xccdf_compliance-operator_rule_5.1.1"');
194
+ });
195
+ });
196
+
197
+ describe('xccdf util: generateXCCDFPerNode', () => {
198
+ const multiNodeReport = {
199
+ version: '1.0',
200
+ total: 3,
201
+ pass: 1,
202
+ nodes: { master: ['m-1'], node: ['w-1', 'w-2'] },
203
+ results: [{
204
+ id: '1.1',
205
+ description: 'Master Node Configuration',
206
+ checks: [{
207
+ id: '1.1.1',
208
+ description: 'master check',
209
+ scored: true,
210
+ state: 'pass' as const,
211
+ }],
212
+ }, {
213
+ id: '4.1',
214
+ description: 'Worker Node Configuration',
215
+ checks: [{
216
+ id: '4.1.1',
217
+ description: 'mixed check',
218
+ scored: true,
219
+ state: 'mixed' as const,
220
+ nodes: ['w-2'],
221
+ }, {
222
+ id: '4.1.2',
223
+ description: 'failing check',
224
+ scored: false,
225
+ state: 'fail' as const,
226
+ }],
227
+ }],
228
+ };
229
+
230
+ it('emits a single <target> equal to the hostname', () => {
231
+ const xml = generateXCCDFPerNode({
232
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
233
+ });
234
+
235
+ expect(xml).toContain('<target>w-1</target>');
236
+ expect(xml).not.toContain('<target>w-2</target>');
237
+ expect(xml).not.toContain('<target>m-1</target>');
238
+ });
239
+
240
+ it('assigns each per-node document a TestResult id suffixed with the hostname so co-loaded files do not collide', () => {
241
+ const a = generateXCCDFPerNode({
242
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
243
+ });
244
+ const b = generateXCCDFPerNode({
245
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-2', role: 'node',
246
+ });
247
+
248
+ expect(a).toContain('<TestResult id="xccdf_compliance-operator_testresult_1_w-1"');
249
+ expect(b).toContain('<TestResult id="xccdf_compliance-operator_testresult_1_w-2"');
250
+ expect(a).not.toContain('id="xccdf_compliance-operator_testresult_1_w-2"');
251
+ });
252
+
253
+ it('emits every rule from the cluster report regardless of role', () => {
254
+ const workerXml = generateXCCDFPerNode({
255
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
256
+ });
257
+ const masterXml = generateXCCDFPerNode({
258
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'm-1', role: 'master',
259
+ });
260
+
261
+ [workerXml, masterXml].forEach((xml) => {
262
+ expect(xml).toContain('xccdf_compliance-operator_rule_1.1.1');
263
+ expect(xml).toContain('xccdf_compliance-operator_rule_4.1.1');
264
+ expect(xml).toContain('xccdf_compliance-operator_rule_4.1.2');
265
+ });
266
+ });
267
+
268
+ it('maps mixed-state checks to fail for dissenting hosts and pass for the rest', () => {
269
+ const dissenter = generateXCCDFPerNode({
270
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-2', role: 'node',
271
+ });
272
+ const compliant = generateXCCDFPerNode({
273
+ report: multiNodeReport, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
274
+ });
275
+
276
+ expect(dissenter).toMatch(/idref="xccdf_compliance-operator_rule_4\.1\.1"[\s\S]*?<result>fail<\/result>/);
277
+ expect(compliant).toMatch(/idref="xccdf_compliance-operator_rule_4\.1\.1"[\s\S]*?<result>pass<\/result>/);
278
+ });
279
+
280
+ it('treats mixed-state checks with no dissent list as pass for all nodes', () => {
281
+ const report = {
282
+ ...multiNodeReport,
283
+ results: [{
284
+ id: '4.1',
285
+ description: 'g',
286
+ checks: [{
287
+ id: '4.1.9', description: 'm', state: 'mixed' as const,
288
+ }],
289
+ }],
290
+ };
291
+ const xml = generateXCCDFPerNode({
292
+ report, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
293
+ });
294
+
295
+ expect(xml).toMatch(/idref="xccdf_compliance-operator_rule_4\.1\.9"[\s\S]*?<result>pass<\/result>/);
296
+ });
297
+
298
+ it('recomputes pass count per node while preserving cluster total as the scoring denominator', () => {
299
+ const report = {
300
+ version: '1.0',
301
+ total: 2,
302
+ pass: 1,
303
+ nodes: { node: ['w-1', 'w-2'] },
304
+ results: [{
305
+ id: '1',
306
+ description: 'g',
307
+ checks: [
308
+ {
309
+ id: 'a', description: 'a', state: 'pass' as const
310
+ },
311
+ {
312
+ id: 'b', description: 'b', state: 'mixed' as const, nodes: ['w-2'],
313
+ },
314
+ ],
315
+ }],
316
+ };
317
+ const compliant = generateXCCDFPerNode({
318
+ report, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
319
+ });
320
+ const dissenter = generateXCCDFPerNode({
321
+ report, benchmarkVersion: 'cis-1.7', hostname: 'w-2', role: 'node',
322
+ });
323
+
324
+ expect(compliant).toMatch(/<score[^>]*>100\.0<\/score>/);
325
+ expect(dissenter).toMatch(/<score[^>]*>50\.0<\/score>/);
326
+ });
327
+
328
+ it('preserves full rule metadata (title, fixtext, idents, check) from the cluster report', () => {
329
+ const report = {
330
+ version: '1.0',
331
+ total: 1,
332
+ pass: 1,
333
+ nodes: { node: ['w-1'] },
334
+ results: [{
335
+ id: 'V-254554',
336
+ description: 'controller manager group',
337
+ checks: [{
338
+ id: 'V-254554',
339
+ description: 'use-service-account-credentials',
340
+ audit: '/bin/ps -fC kube-controller-manager',
341
+ remediation: 'set use-service-account-credentials=true',
342
+ scored: true,
343
+ state: 'pass' as const,
344
+ }],
345
+ }],
346
+ };
347
+ const xml = generateXCCDFPerNode({
348
+ report, benchmarkVersion: 'rke2-stig-1.31-rgs', hostname: 'w-1', role: 'node',
349
+ });
350
+
351
+ expect(xml).toContain('<Group id="V-254554">');
352
+ expect(xml).toContain('<check-content>/bin/ps -fC kube-controller-manager</check-content>');
353
+ expect(xml).toContain('set use-service-account-credentials=true');
354
+ });
355
+
356
+ it('passes through non-mixed states unchanged', () => {
357
+ const report = {
358
+ ...multiNodeReport,
359
+ results: [{
360
+ id: '4.1',
361
+ description: 'g',
362
+ checks: [
363
+ {
364
+ id: 'a', description: 'a', state: 'pass' as const
365
+ },
366
+ {
367
+ id: 'b', description: 'b', state: 'fail' as const
368
+ },
369
+ {
370
+ id: 'c', description: 'c', state: 'skip' as const
371
+ },
372
+ {
373
+ id: 'd', description: 'd', state: 'warn' as const
374
+ },
375
+ {
376
+ id: 'e', description: 'e', state: 'notApplicable' as const
377
+ },
378
+ ],
379
+ }],
380
+ };
381
+ const xml = generateXCCDFPerNode({
382
+ report, benchmarkVersion: 'cis-1.7', hostname: 'w-1', role: 'node',
383
+ });
384
+
385
+ expect(xml).toContain('<result>pass</result>');
386
+ expect(xml).toContain('<result>fail</result>');
387
+ expect(xml).toContain('<result>notselected</result>');
388
+ expect(xml).toContain('<result>informational</result>');
389
+ expect(xml).toContain('<result>notapplicable</result>');
390
+ });
391
+ });
package/utils/chart.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import semver from 'semver';
2
2
  import { compare } from '@shell/utils/version';
3
3
  import { compatibleVersionsFor } from '@shell/store/catalog';
4
+ import {
5
+ CHART, REPO, REPO_TYPE, VERSION, DEPRECATED
6
+ } from '@shell/config/query-params';
4
7
 
5
8
  /**
6
9
  * Compares two chart versions using SemVer logic, with special handling for Rancher's "up" build metadata.
@@ -80,3 +83,36 @@ export function getLatestCompatibleVersion(chart, workerOSs, showPrerelease) {
80
83
 
81
84
  return (compatible.length ? compatible[0] : chart.versions[0]) || {};
82
85
  }
86
+
87
+ /**
88
+ * Builds the route URL for the standalone chart readme page.
89
+ *
90
+ * The helper maps chart context into query params so the readme page can fetch
91
+ * version info directly (instead of relying on local/session storage transfer).
92
+ */
93
+ export function getStandaloneReadmeUrl(router, {
94
+ cluster,
95
+ repoType,
96
+ repoName,
97
+ chartName,
98
+ versionName,
99
+ deprecated,
100
+ showAppReadme = true,
101
+ hideReadmeFirstTitle = true,
102
+ } = {}) {
103
+ const { href } = router.resolve({
104
+ name: 'readme',
105
+ params: { cluster },
106
+ query: {
107
+ [REPO_TYPE]: repoType,
108
+ [REPO]: repoName,
109
+ [CHART]: chartName,
110
+ [VERSION]: versionName,
111
+ [DEPRECATED]: deprecated,
112
+ showAppReadme: String(showAppReadme),
113
+ hideReadmeFirstTitle: String(hideReadmeFirstTitle),
114
+ }
115
+ });
116
+
117
+ return href;
118
+ }
package/utils/fleet.ts CHANGED
@@ -33,6 +33,13 @@ function conditionIsTrue(conditions: Condition[] | undefined, type: string): boo
33
33
  }
34
34
 
35
35
  class Application {
36
+ /**
37
+ * gitrepos/helmops are already restricted to clusters in their own namespace
38
+ *
39
+ * this empty selector means all applicable clusters will be selected
40
+ */
41
+ includeAllWorkgroupRule = { clusterSelector: { matchExpressions: [] } }
42
+
36
43
  excludeHarvesterRule = {
37
44
  clusterSelector: {
38
45
  matchExpressions: [{
@@ -45,7 +52,7 @@ class Application {
45
52
  },
46
53
  };
47
54
 
48
- getTargetMode(targets: Target[], namespace: string): TargetMode {
55
+ getTargetMode(targets: Target[], namespace: string, areHarvesterHostsVisible: boolean): TargetMode {
49
56
  if (namespace === 'fleet-local') {
50
57
  return 'local';
51
58
  }
@@ -83,8 +90,11 @@ class Application {
83
90
  return target;
84
91
  });
85
92
 
86
- // Check if targets contains only harvester rule after name normalizing
87
- if (isEqual(normalized, [this.excludeHarvesterRule])) {
93
+ // Check if targets contains only harvester rule or no rule at all after name normalizing
94
+ // That means the ALL option has been selected previously
95
+ // one case for feature harvester-baremetal-container-workload ON
96
+ // and another for feature harvester-baremetal-container-workload OFF
97
+ if (isEqual(normalized, [this.includeAllWorkgroupRule]) || isEqual(normalized, [this.excludeHarvesterRule])) {
88
98
  mode = 'all';
89
99
  }
90
100
 
@@ -0,0 +1,174 @@
1
+ import { findTemplateType, findAllConstraintTypes, findAllTemplates, findAllConstraints } from '../util';
2
+ import { GATEKEEPER, SCHEMA } from '@shell/config/types';
3
+
4
+ describe('gatekeeper/util.js', () => {
5
+ describe('findTemplateType', () => {
6
+ it('returns the schema id when a matching schema exists', () => {
7
+ const schemas = [
8
+ {
9
+ id: 'templates.gatekeeper.sh.constrainttemplate',
10
+ attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
11
+ },
12
+ ];
13
+
14
+ expect(findTemplateType(schemas)).toStrictEqual('templates.gatekeeper.sh.constrainttemplate');
15
+ });
16
+
17
+ it('returns undefined when no schemas match', () => {
18
+ const schemas = [
19
+ {
20
+ id: 'other.group.somekind',
21
+ attributes: { group: 'other.group', kind: 'SomeKind' },
22
+ },
23
+ ];
24
+
25
+ expect(findTemplateType(schemas)).toBeUndefined();
26
+ });
27
+
28
+ it('returns undefined for an empty schemas array', () => {
29
+ expect(findTemplateType([])).toBeUndefined();
30
+ });
31
+
32
+ it('matches only on the correct group and kind together', () => {
33
+ const schemas = [
34
+ {
35
+ id: 'templates.gatekeeper.sh.other',
36
+ attributes: { group: 'templates.gatekeeper.sh', kind: 'Other' },
37
+ },
38
+ {
39
+ id: 'other.group.constrainttemplate',
40
+ attributes: { group: 'other.group', kind: 'ConstraintTemplate' },
41
+ },
42
+ ];
43
+
44
+ expect(findTemplateType(schemas)).toBeUndefined();
45
+ });
46
+
47
+ it('returns undefined when schema has no attributes', () => {
48
+ const schemas = [{ id: 'some.id' }];
49
+
50
+ expect(findTemplateType(schemas)).toBeUndefined();
51
+ });
52
+
53
+ it('returns the first matching schema id when multiple matches exist', () => {
54
+ const schemas = [
55
+ {
56
+ id: 'first-match',
57
+ attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
58
+ },
59
+ {
60
+ id: 'second-match',
61
+ attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
62
+ },
63
+ ];
64
+
65
+ expect(findTemplateType(schemas)).toStrictEqual('first-match');
66
+ });
67
+
68
+ it('handles schemas with null attributes gracefully', () => {
69
+ const schemas = [
70
+ { id: 'null-attrs', attributes: null },
71
+ {
72
+ id: 'templates.gatekeeper.sh.constrainttemplate',
73
+ attributes: { group: 'templates.gatekeeper.sh', kind: 'ConstraintTemplate' },
74
+ },
75
+ ];
76
+
77
+ expect(findTemplateType(schemas)).toStrictEqual('templates.gatekeeper.sh.constrainttemplate');
78
+ });
79
+ });
80
+
81
+ describe('findAllConstraintTypes', () => {
82
+ it('returns constraint type schema ids filtered by constraints.gatekeeper.sh group', () => {
83
+ const schemas = [
84
+ { id: 'constraints.gatekeeper.sh.k8srequiredlabels', attributes: { group: 'constraints.gatekeeper.sh' } },
85
+ { id: 'constraints.gatekeeper.sh.k8sallowedrepos', attributes: { group: 'constraints.gatekeeper.sh' } },
86
+ { id: 'other.group.something', attributes: { group: 'other.group' } },
87
+ ];
88
+ const store = { getters: { [`cluster/all`]: () => schemas } };
89
+
90
+ const result = findAllConstraintTypes(store);
91
+
92
+ expect(result).toStrictEqual([
93
+ 'constraints.gatekeeper.sh.k8srequiredlabels',
94
+ 'constraints.gatekeeper.sh.k8sallowedrepos',
95
+ ]);
96
+ });
97
+
98
+ it('returns an empty array when no constraint schemas exist', () => {
99
+ const store = { getters: { [`cluster/all`]: () => [] } };
100
+
101
+ expect(findAllConstraintTypes(store)).toStrictEqual([]);
102
+ });
103
+
104
+ it('calls store.getters["cluster/all"] with SCHEMA constant', () => {
105
+ const mockAll = jest.fn().mockReturnValue([]);
106
+ const store = { getters: { [`cluster/all`]: mockAll } };
107
+
108
+ findAllConstraintTypes(store);
109
+
110
+ expect(mockAll).toHaveBeenCalledWith(SCHEMA);
111
+ });
112
+
113
+ it('filters out schemas with missing or non-matching group', () => {
114
+ const schemas = [
115
+ { id: 'no-attrs' },
116
+ { id: 'null-group', attributes: { group: null } },
117
+ { id: 'constraints.gatekeeper.sh.valid', attributes: { group: 'constraints.gatekeeper.sh' } },
118
+ ];
119
+ const store = { getters: { [`cluster/all`]: () => schemas } };
120
+
121
+ const result = findAllConstraintTypes(store);
122
+
123
+ expect(result).toStrictEqual(['constraints.gatekeeper.sh.valid']);
124
+ });
125
+ });
126
+
127
+ describe('findAllTemplates', () => {
128
+ it('dispatches cluster/findAll with the constraint template type', async() => {
129
+ const expectedResult = [{ id: GATEKEEPER.CONSTRAINT_TEMPLATE }];
130
+ const mockDispatch = jest.fn().mockResolvedValue(expectedResult);
131
+ const store = { dispatch: mockDispatch };
132
+
133
+ const result = await findAllTemplates(store);
134
+
135
+ expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: GATEKEEPER.CONSTRAINT_TEMPLATE });
136
+ expect(result).toStrictEqual(expectedResult);
137
+ });
138
+ });
139
+
140
+ describe('findAllConstraints', () => {
141
+ it('returns all constraint resources flattened across all constraint types', async() => {
142
+ const schemas = [
143
+ { id: 'constraints.gatekeeper.sh.type1', attributes: { group: 'constraints.gatekeeper.sh' } },
144
+ { id: 'constraints.gatekeeper.sh.type2', attributes: { group: 'constraints.gatekeeper.sh' } },
145
+ ];
146
+ const type1Resources = [{ id: 'r1' }, { id: 'r2' }];
147
+ const type2Resources = [{ id: 'r3' }];
148
+ const mockDispatch = jest.fn()
149
+ .mockResolvedValueOnce(type1Resources)
150
+ .mockResolvedValueOnce(type2Resources);
151
+ const store = {
152
+ getters: { [`cluster/all`]: () => schemas },
153
+ dispatch: mockDispatch,
154
+ };
155
+
156
+ const result = await findAllConstraints(store);
157
+
158
+ expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: 'constraints.gatekeeper.sh.type1', opt: { force: true } });
159
+ expect(mockDispatch).toHaveBeenCalledWith('cluster/findAll', { type: 'constraints.gatekeeper.sh.type2', opt: { force: true } });
160
+ expect(result).toStrictEqual([{ id: 'r1' }, { id: 'r2' }, { id: 'r3' }]);
161
+ });
162
+
163
+ it('returns an empty array when there are no constraint types', async() => {
164
+ const store = {
165
+ getters: { [`cluster/all`]: () => [] },
166
+ dispatch: jest.fn(),
167
+ };
168
+
169
+ const result = await findAllConstraints(store);
170
+
171
+ expect(result).toStrictEqual([]);
172
+ });
173
+ });
174
+ });