@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.
- package/apis/impl/apis.ts +6 -0
- package/apis/index.ts +26 -0
- package/apis/intf/resources-api/cluster-api.ts +18 -0
- package/apis/intf/resources-api/mgmt-api.ts +15 -0
- package/apis/intf/resources-api/resource-base.ts +107 -0
- package/apis/intf/resources-api/resource-constants.ts +147 -0
- package/apis/intf/resources-api/resources-api.ts +143 -0
- package/apis/intf/resources.ts +49 -0
- package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
- package/apis/intf/shell-api/proxy.ts +216 -0
- package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
- package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
- package/apis/intf/shell.ts +12 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
- package/apis/resources/index.ts +22 -0
- package/apis/resources/resources-api-class.ts +187 -0
- package/apis/shell/__tests__/proxy.test.ts +369 -0
- package/apis/shell/index.ts +8 -1
- package/apis/shell/modal.ts +4 -1
- package/apis/shell/notifications.ts +9 -6
- package/apis/shell/proxy.ts +256 -0
- package/apis/shell/slide-in.ts +4 -1
- package/apis/vue-shim.d.ts +2 -1
- package/assets/data/aws-regions.json +4 -0
- package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/fonts/_fontstack.scss +132 -8
- package/assets/translations/en-us.yaml +22 -5
- package/chart/monitoring/index.vue +10 -1
- package/components/ActionDropdownShell.vue +2 -1
- package/components/CruResourceFooter.vue +9 -5
- package/components/ExplorerProjectsNamespaces.vue +1 -1
- package/components/InstallHelmCharts.vue +2 -2
- package/components/LandingPagePreference.vue +14 -5
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
- package/components/Resource/Detail/Metadata/index.vue +6 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
- package/components/Resource/Detail/SpacedRow.vue +3 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -11
- package/components/ResourceList/Masthead.vue +12 -8
- package/components/SelectIconGrid.vue +0 -10
- package/components/SingleClusterInfo.vue +1 -0
- package/components/SortableTable/__tests__/sorting.test.ts +126 -0
- package/components/SortableTable/index.vue +6 -9
- package/components/SortableTable/selection.js +23 -5
- package/components/SortableTable/sorting.js +6 -3
- package/components/Wizard.vue +14 -13
- package/components/fleet/FleetBundles.vue +100 -12
- package/components/fleet/FleetClusterTargets/index.vue +37 -15
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
- package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
- package/components/form/LabeledSelect.vue +20 -3
- package/components/form/NameNsDescription.vue +11 -0
- package/components/form/Security.vue +6 -2
- package/components/form/WorkloadPorts.vue +2 -7
- package/components/form/__tests__/Security.test.ts +76 -0
- package/components/formatter/Autoscaler.vue +4 -4
- package/components/formatter/ClusterKubeVersion.vue +27 -0
- package/components/formatter/ClusterLink.vue +1 -7
- package/components/formatter/ClusterProvider.vue +6 -10
- package/components/formatter/FleetSummaryGraph.vue +0 -3
- package/components/formatter/MachineSummaryGraph.vue +1 -1
- package/components/formatter/PodsUsage.vue +2 -2
- package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
- package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
- package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
- package/components/nav/NamespaceFilter.vue +2 -2
- package/components/nav/TopLevelMenu.helper.ts +15 -3
- package/components/nav/TopLevelMenu.vue +16 -5
- package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
- package/components/templates/home.vue +18 -0
- package/components/templates/plain.vue +18 -0
- package/components/templates/standalone.vue +17 -0
- package/composables/useFormValidation.ts +93 -0
- package/composables/useVeeValidateField.test.ts +159 -0
- package/composables/useVeeValidateField.ts +67 -0
- package/config/pagination-table-headers.js +18 -1
- package/config/product/manager.js +82 -21
- package/config/router/routes.js +6 -0
- package/config/table-headers.js +20 -1
- package/config/types.js +2 -1
- package/core/__tests__/plugin-products.test.ts +904 -20
- package/core/plugin-products-base.ts +107 -7
- package/core/plugin-products.ts +4 -0
- package/core/plugin-types.ts +111 -1
- package/core/plugin.ts +15 -7
- package/core/productDebugger.js +9 -4
- package/core/types-provisioning.ts +43 -30
- package/core/types.ts +57 -20
- package/detail/__tests__/pod.test.ts +41 -0
- package/detail/harvesterhci.io.management.cluster.vue +6 -2
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +4 -10
- package/edit/auth/__tests__/azuread.test.ts +217 -34
- package/edit/auth/azuread.vue +122 -14
- package/edit/auth/oidc.vue +2 -2
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
- package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
- package/edit/networking.k8s.io.ingress/index.vue +75 -20
- package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +11 -7
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
- package/edit/secret/__tests__/ssh.test.ts +5 -6
- package/edit/secret/basic.vue +31 -0
- package/edit/secret/index.vue +68 -17
- package/edit/secret/registry.vue +38 -0
- package/edit/secret/ssh.vue +29 -0
- package/edit/secret/tls.vue +30 -0
- package/edit/service.vue +4 -4
- package/edit/workload/Upgrading.vue +3 -3
- package/edit/workload/__tests__/Upgrading.test.ts +6 -9
- package/edit/workload/mixins/workload.js +2 -1
- package/list/fleet.cattle.io.bundle.vue +7 -104
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
- package/list/provisioning.cattle.io.cluster.vue +262 -180
- package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
- package/mixins/__tests__/chart.test.ts +112 -0
- package/mixins/brand.js +2 -1
- package/mixins/chart.js +12 -8
- package/mixins/resource-fetch-api-pagination.js +41 -5
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
- package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
- package/models/__tests__/management.cattle.io.node.ts +6 -5
- package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
- package/models/base-cluster.x-k8s.io.js +26 -0
- package/models/cluster.js +1 -1
- package/models/cluster.x-k8s.io.machine.js +4 -22
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
- package/models/cluster.x-k8s.io.machineset.js +2 -20
- package/models/compliance.cattle.io.clusterscan.js +130 -2
- package/models/ext.cattle.io.kubeconfig.ts +4 -7
- package/models/fleet-application.js +3 -1
- package/models/management.cattle.io.cluster.js +417 -40
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.nodepool.js +1 -1
- package/models/networking.k8s.io.ingress.js +12 -4
- package/models/provisioning.cattle.io.cluster.js +47 -330
- package/models/rke.cattle.io.etcdsnapshot.js +1 -2
- package/package.json +11 -29
- package/pages/__tests__/readme.test.ts +49 -0
- package/pages/auth/setup.vue +2 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +76 -0
- package/pages/c/_cluster/apps/charts/chart.vue +60 -8
- package/pages/c/_cluster/apps/charts/install.vue +10 -7
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
- package/pages/c/_cluster/explorer/index.vue +5 -49
- package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
- package/pages/c/_cluster/istio/index.vue +21 -6
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +719 -2
- package/pages/c/_cluster/uiplugins/index.vue +203 -197
- package/pages/diagnostic.vue +13 -17
- package/pages/fail-whale.vue +18 -0
- package/pages/home.vue +77 -260
- package/pages/readme.vue +88 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
- package/plugins/dashboard-store/actions.js +40 -18
- package/plugins/dashboard-store/resource-class.js +5 -2
- package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
- package/plugins/steve/steve-pagination-utils.ts +11 -3
- package/plugins/steve/subscribe.js +35 -5
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +10 -4
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -52
- package/rancher-components/RcButton/RcButton.test.ts +37 -1
- package/rancher-components/RcButton/RcButton.vue +38 -8
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
- package/store/__tests__/catalog.test.ts +115 -1
- package/store/__tests__/type-map.test.ts +556 -1
- package/store/action-menu.js +8 -3
- package/store/auth.js +1 -1
- package/store/aws.js +27 -16
- package/store/catalog.js +27 -3
- package/store/digitalocean.js +20 -38
- package/store/index.js +2 -0
- package/store/linode.js +25 -40
- package/store/pnap.js +1 -0
- package/store/type-map.js +111 -29
- package/tsconfig.paths.json +8 -8
- package/types/kube/kube-api.ts +14 -1
- package/types/rancher/steve.api.ts +12 -12
- package/types/resources/settings.d.ts +2 -1
- package/types/shell/index.d.ts +102 -2
- package/types/store/dashboard-store.types.ts +108 -11
- package/types/store/pagination.types.ts +6 -3
- package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
- package/utils/__tests__/async.test.ts +87 -0
- package/utils/__tests__/aws.test.ts +140 -0
- package/utils/__tests__/banners.test.ts +176 -0
- package/utils/__tests__/chart.test.ts +64 -1
- package/utils/__tests__/color.test.ts +226 -0
- package/utils/__tests__/duration.test.ts +140 -0
- package/utils/__tests__/fleet.test.ts +340 -0
- package/utils/__tests__/ingress.test.ts +553 -0
- package/utils/__tests__/kube.test.ts +68 -0
- package/utils/__tests__/namespace-filter.test.ts +109 -0
- package/utils/__tests__/pagination-utils.test.ts +361 -0
- package/utils/__tests__/parse-externalid.test.ts +137 -0
- package/utils/__tests__/perf-setting.utils.test.ts +98 -0
- package/utils/__tests__/poller-sequential.test.ts +177 -0
- package/utils/__tests__/poller.test.ts +170 -0
- package/utils/__tests__/promise.test.ts +346 -0
- package/utils/__tests__/settings.test.ts +140 -0
- package/utils/__tests__/sort-utils.test.ts +301 -0
- package/utils/__tests__/string-utils.test.ts +798 -0
- package/utils/__tests__/string.test.ts +23 -1
- package/utils/__tests__/style.test.ts +154 -0
- package/utils/__tests__/svg-filter.test.ts +184 -0
- package/utils/__tests__/units.test.ts +417 -0
- package/utils/__tests__/versions.test.ts +128 -0
- package/utils/__tests__/xccdf.test.ts +391 -0
- package/utils/chart.js +36 -0
- package/utils/fleet.ts +13 -3
- package/utils/gatekeeper/__tests__/util.test.ts +174 -0
- package/utils/gc/__tests__/gc-interval.test.ts +119 -0
- package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
- package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
- package/utils/gc/__tests__/gc.test.ts +487 -0
- package/utils/ingress.ts +9 -1
- package/utils/pagination-utils.ts +2 -1
- package/utils/string.js +25 -2
- package/utils/uiplugins.ts +5 -5
- package/utils/validators/__tests__/cluster-name.test.ts +110 -0
- package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
- package/utils/validators/__tests__/index.test.ts +481 -0
- package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
- package/utils/validators/__tests__/misc-validators.test.ts +246 -0
- package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
- package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
- package/utils/validators/__tests__/role-template.test.ts +149 -0
- package/utils/validators/__tests__/service.test.ts +283 -0
- package/utils/validators/__tests__/setting.test.js +32 -0
- package/utils/validators/formRules/__tests__/index.test.ts +50 -0
- package/utils/validators/formRules/index.ts +5 -5
- package/utils/validators/machine-pool.ts +1 -1
- package/utils/validators/setting.js +18 -3
- package/utils/xccdf.ts +418 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
|
@@ -87,21 +87,21 @@ export default {
|
|
|
87
87
|
path: 'spec.rules.http.paths.path', rules: ['absolutePath'], translationKey: 'ingress.rules.path.label'
|
|
88
88
|
},
|
|
89
89
|
{
|
|
90
|
-
path: 'spec.rules.http.paths.backend.service.port
|
|
90
|
+
path: 'spec.rules.http.paths.backend.service.port', rules: ['portRequired', 'portRange'], translationKey: 'ingress.rules.port.label'
|
|
91
91
|
},
|
|
92
92
|
{
|
|
93
93
|
path: 'spec.rules.http.paths.backend.service.name', rules: ['required'], translationKey: 'ingress.rules.target.label'
|
|
94
94
|
},
|
|
95
95
|
{ path: 'spec', rules: ['backEndOrRules'] },
|
|
96
96
|
{
|
|
97
|
-
path: 'spec.defaultBackend.service.name', rules: ['
|
|
97
|
+
path: 'spec.defaultBackend.service.name', rules: ['defaultBackendNameRequired'], translationKey: 'ingress.defaultBackend.targetService.label'
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
|
-
path: 'spec.defaultBackend.service.port
|
|
100
|
+
path: 'spec.defaultBackend.service.port', rules: ['defaultBackendPortRequired', 'portRange'], translationKey: 'ingress.defaultBackend.port.label'
|
|
101
101
|
},
|
|
102
102
|
{ path: 'spec.tls.hosts', rules: ['required', 'wildcardHostname'] }
|
|
103
103
|
],
|
|
104
|
-
fvReportedValidationPaths: ['spec.rules.http.paths.backend.service.port
|
|
104
|
+
fvReportedValidationPaths: ['spec.rules.http.paths.backend.service.port', 'spec.rules.http.paths.path', 'spec.rules.http.paths.backend.service.name']
|
|
105
105
|
};
|
|
106
106
|
},
|
|
107
107
|
|
|
@@ -125,35 +125,89 @@ export default {
|
|
|
125
125
|
}
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
const portLabel = this.t('ingress.rules.port.label');
|
|
129
|
+
|
|
130
|
+
// Built-in `required` won't work: it passes for empty objects like {} or { name: '' }.
|
|
131
|
+
const portRequired = (port) => {
|
|
132
|
+
if (typeof port === 'string' || typeof port === 'number') {
|
|
133
|
+
if (!port) {
|
|
134
|
+
return this.t('validation.required', { key: portLabel });
|
|
135
|
+
}
|
|
136
|
+
} else if (!port || (!port.number && !port.name)) {
|
|
137
|
+
return this.t('validation.required', { key: portLabel });
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const portRange = (port) => {
|
|
142
|
+
let num;
|
|
143
|
+
|
|
144
|
+
if (typeof port === 'number') {
|
|
145
|
+
num = port;
|
|
146
|
+
} else if (typeof port === 'string') {
|
|
147
|
+
num = Number.parseInt(port);
|
|
148
|
+
} else if (port?.number) {
|
|
149
|
+
num = port.number;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (num !== undefined && !Number.isNaN(num) && (num < 1 || num > 65535)) {
|
|
153
|
+
return this.t('validation.number.between', {
|
|
154
|
+
key: portLabel, min: '1', max: '65535'
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const hasDefaultBackendService = () => {
|
|
160
|
+
const backend = get(this.value?.spec, this.value.defaultBackendPath);
|
|
161
|
+
|
|
162
|
+
return !!get(backend, this.value.serviceNamePath);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const nameLabel = this.t('ingress.defaultBackend.targetService.label');
|
|
166
|
+
|
|
167
|
+
// Only enforce required on the default backend when a service is selected.
|
|
168
|
+
// Selecting "None" means the user wants to remove the backend; willSave() handles cleanup.
|
|
169
|
+
const defaultBackendNameRequired = (name) => {
|
|
170
|
+
if (hasDefaultBackendService() && !name) {
|
|
171
|
+
return this.t('validation.required', { key: nameLabel });
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const defaultBackendPortRequired = (port) => {
|
|
176
|
+
if (!hasDefaultBackendService()) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return portRequired(port);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
backEndOrRules,
|
|
185
|
+
portRequired,
|
|
186
|
+
portRange,
|
|
187
|
+
defaultBackendNameRequired,
|
|
188
|
+
defaultBackendPortRequired,
|
|
189
|
+
};
|
|
129
190
|
},
|
|
130
191
|
tabErrors() {
|
|
131
192
|
return {
|
|
132
|
-
rules: this.fvGetPathErrors(['spec.rules.host', 'spec.rules.http.paths.path', 'spec.rules.http.paths.backend.service.port
|
|
133
|
-
defaultBackend: this.fvGetPathErrors(['spec.defaultBackend.service.name', 'spec.defaultBackend.service.port
|
|
193
|
+
rules: this.fvGetPathErrors(['spec.rules.host', 'spec.rules.http.paths.path', 'spec.rules.http.paths.backend.service.port', 'spec.rules.http.paths.backend.service.name'])?.length > 0,
|
|
194
|
+
defaultBackend: this.fvGetPathErrors(['spec.defaultBackend.service.name', 'spec.defaultBackend.service.port'])?.length > 0
|
|
134
195
|
};
|
|
135
196
|
},
|
|
136
197
|
rulesPathRules() {
|
|
137
198
|
return {
|
|
138
199
|
requestHost: this.fvGetAndReportPathRules('spec.rules.host'),
|
|
139
200
|
path: this.fvGetAndReportPathRules('spec.rules.http.paths.path'),
|
|
140
|
-
port: this.fvGetAndReportPathRules('spec.rules.http.paths.backend.service.port
|
|
201
|
+
port: this.fvGetAndReportPathRules('spec.rules.http.paths.backend.service.port'),
|
|
141
202
|
target: this.fvGetAndReportPathRules('spec.rules.http.paths.backend.service.name'),
|
|
142
203
|
|
|
143
204
|
};
|
|
144
205
|
},
|
|
145
206
|
defaultBackendPathRules() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
name: this.fvGetAndReportPathRules('spec.defaultBackend.service.name'),
|
|
152
|
-
port: this.fvGetAndReportPathRules('spec.defaultBackend.service.port.number'),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return { name: [], port: [] };
|
|
207
|
+
return {
|
|
208
|
+
name: this.fvGetAndReportPathRules('spec.defaultBackend.service.name'),
|
|
209
|
+
port: this.fvGetAndReportPathRules('spec.defaultBackend.service.port'),
|
|
210
|
+
};
|
|
157
211
|
},
|
|
158
212
|
serviceTargets() {
|
|
159
213
|
return this.ingressHelper.findAndMapServiceTargets(this.services);
|
|
@@ -188,7 +242,8 @@ export default {
|
|
|
188
242
|
willSave() {
|
|
189
243
|
const backend = get(this.value.spec, this.value.defaultBackendPath);
|
|
190
244
|
const serviceName = get(backend, this.value.serviceNamePath);
|
|
191
|
-
const servicePort = get(backend, this.value.servicePortPath)
|
|
245
|
+
const servicePort = get(backend, this.value.servicePortPath) ||
|
|
246
|
+
get(backend, this.value.servicePortNamePath);
|
|
192
247
|
|
|
193
248
|
if (backend && (!serviceName || !servicePort)) {
|
|
194
249
|
const path = this.value.defaultBackendPath;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
import MachinePool from '@shell/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue';
|
|
3
|
+
|
|
4
|
+
const TRANSLATION_KEY = '%cluster.machinePool.name.unique%';
|
|
5
|
+
|
|
6
|
+
function createPool(name: string, { remove = false } = {}) {
|
|
7
|
+
return {
|
|
8
|
+
id: `pool-${ name }`,
|
|
9
|
+
remove,
|
|
10
|
+
create: false,
|
|
11
|
+
update: true,
|
|
12
|
+
pool: {
|
|
13
|
+
name,
|
|
14
|
+
etcdRole: false,
|
|
15
|
+
controlPlaneRole: false,
|
|
16
|
+
workerRole: true,
|
|
17
|
+
quantity: 1,
|
|
18
|
+
},
|
|
19
|
+
config: null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mountMachinePool(currentPool: ReturnType<typeof createPool>, allPools: ReturnType<typeof createPool>[]) {
|
|
24
|
+
return shallowMount(MachinePool, {
|
|
25
|
+
props: {
|
|
26
|
+
value: currentPool,
|
|
27
|
+
mode: 'create',
|
|
28
|
+
provider: 'custom',
|
|
29
|
+
idx: 0,
|
|
30
|
+
machinePools: allPools,
|
|
31
|
+
poolId: currentPool.id,
|
|
32
|
+
poolCreateMode: true,
|
|
33
|
+
},
|
|
34
|
+
global: {
|
|
35
|
+
mocks: {
|
|
36
|
+
$store: {
|
|
37
|
+
getters: {
|
|
38
|
+
'i18n/t': (key: string) => key,
|
|
39
|
+
'i18n/exists': () => false,
|
|
40
|
+
'type-map/hasCustomMachineConfigComponent': () => false,
|
|
41
|
+
'type-map/importMachineConfig': () => null,
|
|
42
|
+
'features/get': () => false,
|
|
43
|
+
},
|
|
44
|
+
dispatch: jest.fn(),
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
stubs: {
|
|
48
|
+
LabeledInput: true,
|
|
49
|
+
Checkbox: true,
|
|
50
|
+
Taints: true,
|
|
51
|
+
KeyValue: true,
|
|
52
|
+
AdvancedSection: true,
|
|
53
|
+
Banner: true,
|
|
54
|
+
UnitInput: true,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
describe('component: MachinePool', () => {
|
|
61
|
+
describe('uniquePoolName validation', () => {
|
|
62
|
+
it('should return undefined when the name is empty', () => {
|
|
63
|
+
const pool = createPool('');
|
|
64
|
+
const wrapper = mountMachinePool(pool, [pool]);
|
|
65
|
+
|
|
66
|
+
expect(wrapper.vm.fvExtraRules.uniquePoolName('')).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should return undefined when the pool name is unique', () => {
|
|
70
|
+
const pool1 = createPool('pool1');
|
|
71
|
+
const pool2 = createPool('pool2');
|
|
72
|
+
const wrapper = mountMachinePool(pool1, [pool1, pool2]);
|
|
73
|
+
|
|
74
|
+
expect(wrapper.vm.fvExtraRules.uniquePoolName('pool1')).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return an error message when the pool name is duplicated', () => {
|
|
78
|
+
const pool1 = createPool('same-name');
|
|
79
|
+
const pool2 = createPool('same-name');
|
|
80
|
+
const wrapper = mountMachinePool(pool1, [pool1, pool2]);
|
|
81
|
+
|
|
82
|
+
expect(wrapper.vm.fvExtraRules.uniquePoolName('same-name')).toStrictEqual(TRANSLATION_KEY);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should ignore pools marked for removal', () => {
|
|
86
|
+
const pool1 = createPool('same-name');
|
|
87
|
+
const pool2 = createPool('same-name', { remove: true });
|
|
88
|
+
const wrapper = mountMachinePool(pool1, [pool1, pool2]);
|
|
89
|
+
|
|
90
|
+
expect(wrapper.vm.fvExtraRules.uniquePoolName('same-name')).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it.each([
|
|
94
|
+
['Pool1', 'pool1'],
|
|
95
|
+
['POOL', 'pool'],
|
|
96
|
+
])('should flag names that differ only by case as duplicates (%s vs %s)', (nameA, nameB) => {
|
|
97
|
+
const pool1 = createPool(nameA);
|
|
98
|
+
const pool2 = createPool(nameB);
|
|
99
|
+
const wrapper = mountMachinePool(pool1, [pool1, pool2]);
|
|
100
|
+
|
|
101
|
+
expect(wrapper.vm.fvExtraRules.uniquePoolName(nameA)).toStrictEqual(TRANSLATION_KEY);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -5,7 +5,7 @@ import { Banner } from '@components/Banner';
|
|
|
5
5
|
import CruResource from '@shell/components/CruResource';
|
|
6
6
|
import SelectIconGrid from '@shell/components/SelectIconGrid';
|
|
7
7
|
import {
|
|
8
|
-
CHART, FROM_CLUSTER, SUB_TYPE, RKE_TYPE, _EDIT, _IMPORT, _CONFIG, _VIEW
|
|
8
|
+
CHART, FROM_CLUSTER, SUB_TYPE, RKE_TYPE, _EDIT, _IMPORT, _CONFIG, _VIEW, _CREATE
|
|
9
9
|
} from '@shell/config/query-params';
|
|
10
10
|
import { mapGetters } from 'vuex';
|
|
11
11
|
import { sortBy } from '@shell/utils/sort';
|
|
@@ -80,16 +80,20 @@ export default {
|
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
async fetch() {
|
|
83
|
-
const hash = {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
const hash = {};
|
|
84
|
+
|
|
85
|
+
if (this.mode === _CREATE) {
|
|
86
|
+
// After we create we wait for these to exist, so start watching
|
|
87
|
+
await this.$store.dispatch('management/watch', { type: MANAGEMENT.CLUSTER, registerType: true });
|
|
88
|
+
await this.$store.dispatch('management/watch', { type: CAPI.RANCHER_CLUSTER, registerType: true });
|
|
89
|
+
} else {
|
|
90
|
+
hash.mgmtClusters = this.value.waitForMgmt();
|
|
91
|
+
}
|
|
88
92
|
|
|
89
93
|
// No need to fetch charts when editing an RKE1 cluster
|
|
90
94
|
// The computed property `isRke1` in this file is based on the RKE1/RKE2 toggle, which is not applicable in this case
|
|
91
95
|
// Instead, we should rely on the value from the model: `this.value.isRke1`
|
|
92
|
-
if (!this.value.isRke1 || (this.value.isRke1 && this.mode !==
|
|
96
|
+
if (!this.value.isRke1 || (this.value.isRke1 && this.mode !== _EDIT)) {
|
|
93
97
|
hash['catalog'] = this.$store.dispatch('catalog/load');
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -174,7 +174,7 @@ export default {
|
|
|
174
174
|
Object.entries(this.chartValues).forEach(([name, value]) => {
|
|
175
175
|
const key = this.chartVersionKey(name);
|
|
176
176
|
|
|
177
|
-
this.
|
|
177
|
+
this.userChartValues[key] = value;
|
|
178
178
|
});
|
|
179
179
|
this.setAgentConfiguration();
|
|
180
180
|
},
|
|
@@ -298,6 +298,7 @@ export default {
|
|
|
298
298
|
isEmpty,
|
|
299
299
|
AGENT_CONFIGURATION_TYPES,
|
|
300
300
|
basicsValid: true,
|
|
301
|
+
registryConfigValid: true,
|
|
301
302
|
originalIngressController: this.value.spec.rkeConfig.machineGlobalConfig?.[INGRESS_CONTROLLER] || INGRESS_NONE,
|
|
302
303
|
};
|
|
303
304
|
},
|
|
@@ -906,7 +907,8 @@ export default {
|
|
|
906
907
|
return this.validationPassed &&
|
|
907
908
|
this.fvFormIsValid &&
|
|
908
909
|
this.etcdConfigValid &&
|
|
909
|
-
this.basicsValid
|
|
910
|
+
this.basicsValid &&
|
|
911
|
+
this.registryConfigValid;
|
|
910
912
|
},
|
|
911
913
|
nginxSupported() {
|
|
912
914
|
if (this.serverArgs?.disable?.options.includes(RKE2_INGRESS_NGINX)) {
|
|
@@ -2518,14 +2520,14 @@ export default {
|
|
|
2518
2520
|
>
|
|
2519
2521
|
<template
|
|
2520
2522
|
v-for="(obj, idx) in machinePools"
|
|
2521
|
-
:key="
|
|
2523
|
+
:key="obj.id"
|
|
2522
2524
|
>
|
|
2523
2525
|
<Tab
|
|
2524
2526
|
v-if="!obj.remove"
|
|
2525
2527
|
:key="obj.id"
|
|
2526
2528
|
:weight="-1 * idx"
|
|
2527
2529
|
:name="obj.id"
|
|
2528
|
-
:label="obj.pool.name || '
|
|
2530
|
+
:label="obj.pool.name || t('cluster.machinePool.name.notNamed')"
|
|
2529
2531
|
:show-header="false"
|
|
2530
2532
|
:error="!machinePoolValidation[obj.id]"
|
|
2531
2533
|
>
|
|
@@ -2688,6 +2690,7 @@ export default {
|
|
|
2688
2690
|
<Tab
|
|
2689
2691
|
:name="REGISTRIES_TAB_NAME"
|
|
2690
2692
|
label-key="cluster.tabs.registry"
|
|
2693
|
+
:error="!registryConfigValid"
|
|
2691
2694
|
>
|
|
2692
2695
|
<Registries
|
|
2693
2696
|
v-if="isActiveTabRegistries"
|
|
@@ -2703,6 +2706,7 @@ export default {
|
|
|
2703
2706
|
@custom-registry-changed="toggleCustomRegistry"
|
|
2704
2707
|
@registry-host-changed="handleRegistryHostChanged"
|
|
2705
2708
|
@registry-secret-changed="handleRegistrySecretChanged"
|
|
2709
|
+
@registry-validation-changed="(val) => registryConfigValid = val"
|
|
2706
2710
|
/>
|
|
2707
2711
|
</Tab>
|
|
2708
2712
|
|
|
@@ -106,6 +106,16 @@ export default {
|
|
|
106
106
|
const max = this.value?.pool?.autoscalingMaxSize || 0;
|
|
107
107
|
|
|
108
108
|
return max - min >= 0 ? undefined : this.t('cluster.machinePool.autoscaler.validation.isAutoscalerMaxGreaterThanMin');
|
|
109
|
+
},
|
|
110
|
+
uniquePoolName: (name) => {
|
|
111
|
+
if (!name) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const otherPools = (this.machinePools || []).filter((p) => !p.remove && p !== this.value);
|
|
116
|
+
const isDuplicate = otherPools.some((p) => p.pool.name?.toLowerCase() === name.toLowerCase());
|
|
117
|
+
|
|
118
|
+
return isDuplicate ? this.t('cluster.machinePool.name.unique') : undefined;
|
|
109
119
|
}
|
|
110
120
|
}
|
|
111
121
|
};
|
|
@@ -295,6 +305,7 @@ export default {
|
|
|
295
305
|
:label="t('cluster.machinePool.name.label')"
|
|
296
306
|
:required="true"
|
|
297
307
|
:disabled="!value.config || !!value.config.id || busy"
|
|
308
|
+
:require-dirty="false"
|
|
298
309
|
:rules="fvGetAndReportPathRules(MACHINE_POOL_VALIDATION.FIELDS.NAME)"
|
|
299
310
|
data-testid="machine-pool-name-input"
|
|
300
311
|
/>
|
|
@@ -7,11 +7,11 @@ import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthS
|
|
|
7
7
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
8
8
|
import SecretSelector from '@shell/components/form/SecretSelector';
|
|
9
9
|
import { SECRET_TYPES as TYPES } from '@shell/config/secret';
|
|
10
|
-
import {
|
|
10
|
+
import { isBase64EncodedCert } from '@shell/utils/string';
|
|
11
11
|
import { base64Decode, base64Encode } from '@shell/utils/crypto';
|
|
12
12
|
|
|
13
13
|
export default {
|
|
14
|
-
emits: ['updateConfigs'],
|
|
14
|
+
emits: ['updateConfigs', 'validation-changed'],
|
|
15
15
|
|
|
16
16
|
components: {
|
|
17
17
|
ArrayListGrouped,
|
|
@@ -62,6 +62,34 @@ export default {
|
|
|
62
62
|
return TYPES.TLS;
|
|
63
63
|
},
|
|
64
64
|
},
|
|
65
|
+
|
|
66
|
+
caBundleRules() {
|
|
67
|
+
return [
|
|
68
|
+
(value) => {
|
|
69
|
+
if (!value) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const isPem = value.trimStart().startsWith('-----BEGIN ');
|
|
74
|
+
const isValidBase64 = isBase64EncodedCert(value);
|
|
75
|
+
|
|
76
|
+
return (!isPem && !isValidBase64) ? this.t('registryConfig.caBundle.validationError') : undefined;
|
|
77
|
+
}
|
|
78
|
+
];
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// Derives validation state from entries directly, so it auto-updates when entries are added or removed
|
|
82
|
+
allCaBundlesValid() {
|
|
83
|
+
return this.entries.every((entry) => {
|
|
84
|
+
return this.caBundleRules.every((rule) => !rule(entry.caBundle));
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
watch: {
|
|
90
|
+
allCaBundlesValid(valid) {
|
|
91
|
+
this.$emit('validation-changed', valid);
|
|
92
|
+
},
|
|
65
93
|
},
|
|
66
94
|
|
|
67
95
|
mounted() {
|
|
@@ -78,7 +106,8 @@ export default {
|
|
|
78
106
|
|
|
79
107
|
const caBundle = configMap[hostname].caBundle ?? this.defaultAddValue.caBundle;
|
|
80
108
|
|
|
81
|
-
|
|
109
|
+
// Decode base64 for display so the user sees readable PEM text
|
|
110
|
+
configMap[hostname].caBundle = isBase64EncodedCert(caBundle) ? base64Decode(caBundle) : caBundle;
|
|
82
111
|
|
|
83
112
|
configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? this.defaultAddValue.tlsSecretName;
|
|
84
113
|
}
|
|
@@ -103,7 +132,8 @@ export default {
|
|
|
103
132
|
|
|
104
133
|
configs[h] = {
|
|
105
134
|
...entry,
|
|
106
|
-
|
|
135
|
+
// If the value is already base64, use as-is to avoid double-encoding
|
|
136
|
+
caBundle: entry.caBundle ? (isBase64EncodedCert(entry.caBundle) ? entry.caBundle : base64Encode(entry.caBundle)) : null
|
|
107
137
|
};
|
|
108
138
|
|
|
109
139
|
delete configs[h].hostname;
|
|
@@ -192,6 +222,9 @@ export default {
|
|
|
192
222
|
type="multiline"
|
|
193
223
|
label="CA Cert Bundle"
|
|
194
224
|
:mode="mode"
|
|
225
|
+
:rules="caBundleRules"
|
|
226
|
+
:require-dirty="false"
|
|
227
|
+
:tooltip="t('registryConfig.caBundle.tooltip')"
|
|
195
228
|
/>
|
|
196
229
|
|
|
197
230
|
<div>
|
package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts
CHANGED
|
@@ -4,6 +4,9 @@ import { _EDIT } from '@shell/config/query-params';
|
|
|
4
4
|
import { PROV_CLUSTER } from '@shell/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster';
|
|
5
5
|
import RegistryConfigs from '@shell/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue';
|
|
6
6
|
|
|
7
|
+
const VALID_BASE64_CERT = 'LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t';
|
|
8
|
+
const VALID_PEM_TEXT = '-----BEGIN CERTIFICATE-----\nMIIBkTCB+wIJA';
|
|
9
|
+
|
|
7
10
|
describe('component: RegistryConfigs', () => {
|
|
8
11
|
let wrapper: Wrapper<InstanceType<typeof RegistryConfigs> & { [key: string]: any }>;
|
|
9
12
|
|
|
@@ -18,14 +21,14 @@ describe('component: RegistryConfigs', () => {
|
|
|
18
21
|
SelectOrCreateAuthSecret: true,
|
|
19
22
|
SecretSelector: true,
|
|
20
23
|
},
|
|
21
|
-
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
24
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn((key: string) => key) } } }
|
|
22
25
|
}
|
|
23
26
|
};
|
|
24
27
|
|
|
25
28
|
describe('key CA Cert Bundle', () => {
|
|
26
29
|
it.each([
|
|
27
|
-
['source is
|
|
28
|
-
['source is
|
|
30
|
+
['source is base64', VALID_BASE64_CERT, '-----BEGIN CERTIFICATE-----'],
|
|
31
|
+
['source is plain text', 'foobar', 'foobar'],
|
|
29
32
|
])('should display key, %p', (_, sourceCaBundle, displayedCaBundle) => {
|
|
30
33
|
const value = clone(PROV_CLUSTER);
|
|
31
34
|
|
|
@@ -43,10 +46,10 @@ describe('component: RegistryConfigs', () => {
|
|
|
43
46
|
expect(registry.props().value).toBe(displayedCaBundle);
|
|
44
47
|
});
|
|
45
48
|
|
|
46
|
-
it('should
|
|
49
|
+
it('should base64 encode plain PEM text on save', async() => {
|
|
47
50
|
const value = clone(PROV_CLUSTER);
|
|
48
51
|
|
|
49
|
-
value.spec.rkeConfig.registries.configs = { foo: { caBundle:
|
|
52
|
+
value.spec.rkeConfig.registries.configs = { foo: { caBundle: VALID_BASE64_CERT } };
|
|
50
53
|
|
|
51
54
|
mountOptions.propsData.value = value;
|
|
52
55
|
|
|
@@ -57,10 +60,132 @@ describe('component: RegistryConfigs', () => {
|
|
|
57
60
|
|
|
58
61
|
const registry = wrapper.findComponent('[data-testid^="registry-caBundle"]');
|
|
59
62
|
|
|
60
|
-
registry.vm.$emit('update:value',
|
|
63
|
+
registry.vm.$emit('update:value', VALID_PEM_TEXT);
|
|
61
64
|
wrapper.vm.update();
|
|
62
65
|
|
|
63
|
-
expect(wrapper.emitted('updateConfigs')[0][0]['foo']['caBundle']).toBe('
|
|
66
|
+
expect(wrapper.emitted('updateConfigs')[0][0]['foo']['caBundle']).toBe('LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENCK3dJSkE=');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should keep base64 value as-is on save', async() => {
|
|
70
|
+
const value = clone(PROV_CLUSTER);
|
|
71
|
+
|
|
72
|
+
value.spec.rkeConfig.registries.configs = { foo: { caBundle: VALID_BASE64_CERT } };
|
|
73
|
+
|
|
74
|
+
mountOptions.propsData.value = value;
|
|
75
|
+
|
|
76
|
+
wrapper = mount(
|
|
77
|
+
RegistryConfigs,
|
|
78
|
+
mountOptions
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const registry = wrapper.findComponent('[data-testid^="registry-caBundle"]');
|
|
82
|
+
|
|
83
|
+
registry.vm.$emit('update:value', VALID_BASE64_CERT);
|
|
84
|
+
wrapper.vm.update();
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted('updateConfigs')[0][0]['foo']['caBundle']).toBe(VALID_BASE64_CERT);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('cA Cert Bundle validation', () => {
|
|
91
|
+
it('should pass validation for valid base64 value', () => {
|
|
92
|
+
const value = clone(PROV_CLUSTER);
|
|
93
|
+
|
|
94
|
+
value.spec.rkeConfig.registries.configs = {};
|
|
95
|
+
mountOptions.propsData.value = value;
|
|
96
|
+
|
|
97
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
98
|
+
|
|
99
|
+
const rule = wrapper.vm.caBundleRules[0];
|
|
100
|
+
|
|
101
|
+
expect(rule(VALID_BASE64_CERT)).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should pass validation for PEM text', () => {
|
|
105
|
+
const value = clone(PROV_CLUSTER);
|
|
106
|
+
|
|
107
|
+
value.spec.rkeConfig.registries.configs = {};
|
|
108
|
+
mountOptions.propsData.value = value;
|
|
109
|
+
|
|
110
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
111
|
+
|
|
112
|
+
const rule = wrapper.vm.caBundleRules[0];
|
|
113
|
+
|
|
114
|
+
expect(rule(VALID_PEM_TEXT)).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should pass validation for empty value', () => {
|
|
118
|
+
const value = clone(PROV_CLUSTER);
|
|
119
|
+
|
|
120
|
+
value.spec.rkeConfig.registries.configs = {};
|
|
121
|
+
mountOptions.propsData.value = value;
|
|
122
|
+
|
|
123
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
124
|
+
|
|
125
|
+
const rule = wrapper.vm.caBundleRules[0];
|
|
126
|
+
|
|
127
|
+
expect(rule('')).toBeUndefined();
|
|
128
|
+
expect(rule(null)).toBeUndefined();
|
|
129
|
+
expect(rule(undefined)).toBeUndefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should fail validation for invalid value', () => {
|
|
133
|
+
const value = clone(PROV_CLUSTER);
|
|
134
|
+
|
|
135
|
+
value.spec.rkeConfig.registries.configs = {};
|
|
136
|
+
mountOptions.propsData.value = value;
|
|
137
|
+
|
|
138
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
139
|
+
|
|
140
|
+
const rule = wrapper.vm.caBundleRules[0];
|
|
141
|
+
|
|
142
|
+
expect(rule('not-valid-base64!')).toContain('registryConfig.caBundle.validationError');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should report allCaBundlesValid as true when all entries have valid caBundles', () => {
|
|
146
|
+
const value = clone(PROV_CLUSTER);
|
|
147
|
+
|
|
148
|
+
value.spec.rkeConfig.registries.configs = {
|
|
149
|
+
'reg1.example.com': { caBundle: VALID_BASE64_CERT },
|
|
150
|
+
'reg2.example.com': { caBundle: VALID_BASE64_CERT },
|
|
151
|
+
};
|
|
152
|
+
mountOptions.propsData.value = value;
|
|
153
|
+
|
|
154
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
155
|
+
|
|
156
|
+
expect(wrapper.vm.allCaBundlesValid).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('should report allCaBundlesValid as false when any entry has an invalid caBundle', () => {
|
|
160
|
+
const value = clone(PROV_CLUSTER);
|
|
161
|
+
|
|
162
|
+
value.spec.rkeConfig.registries.configs = {
|
|
163
|
+
'reg1.example.com': { caBundle: VALID_BASE64_CERT },
|
|
164
|
+
'reg2.example.com': { caBundle: 'not-valid!' },
|
|
165
|
+
};
|
|
166
|
+
mountOptions.propsData.value = value;
|
|
167
|
+
|
|
168
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
169
|
+
|
|
170
|
+
expect(wrapper.vm.allCaBundlesValid).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should update validation when an invalid entry is removed', () => {
|
|
174
|
+
const value = clone(PROV_CLUSTER);
|
|
175
|
+
|
|
176
|
+
value.spec.rkeConfig.registries.configs = {
|
|
177
|
+
'reg1.example.com': { caBundle: VALID_BASE64_CERT },
|
|
178
|
+
'reg2.example.com': { caBundle: 'not-valid!' },
|
|
179
|
+
};
|
|
180
|
+
mountOptions.propsData.value = value;
|
|
181
|
+
|
|
182
|
+
wrapper = mount(RegistryConfigs, mountOptions);
|
|
183
|
+
|
|
184
|
+
expect(wrapper.vm.allCaBundlesValid).toBe(false);
|
|
185
|
+
|
|
186
|
+
wrapper.vm.entries.splice(1, 1);
|
|
187
|
+
|
|
188
|
+
expect(wrapper.vm.allCaBundlesValid).toBe(true);
|
|
64
189
|
});
|
|
65
190
|
});
|
|
66
191
|
});
|
|
@@ -8,7 +8,7 @@ import RegistryConfigs from '@shell/edit/provisioning.cattle.io.cluster/tabs/reg
|
|
|
8
8
|
import RegistryMirrors from '@shell/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors';
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
|
-
emits: ['custom-registry-changed', 'registry-host-changed', 'registry-secret-changed', 'input', 'update-configs-changed'],
|
|
11
|
+
emits: ['custom-registry-changed', 'registry-host-changed', 'registry-secret-changed', 'input', 'update-configs-changed', 'registry-validation-changed'],
|
|
12
12
|
components: {
|
|
13
13
|
LabeledInput,
|
|
14
14
|
Banner,
|
|
@@ -142,6 +142,7 @@ export default {
|
|
|
142
142
|
:cluster-register-before-hook="registerBeforeHook"
|
|
143
143
|
@update:value="$emit('input', $event)"
|
|
144
144
|
@updateConfigs="$emit('update-configs-changed', $event)"
|
|
145
|
+
@validation-changed="$emit('registry-validation-changed', $event)"
|
|
145
146
|
/>
|
|
146
147
|
</AdvancedSection>
|
|
147
148
|
</div>
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { createStore } from 'vuex';
|
|
2
3
|
import { _VIEW, _EDIT, _CREATE } from '@shell/config/query-params';
|
|
3
4
|
import Ssh from '@shell/edit/secret/ssh.vue';
|
|
4
5
|
|
|
5
|
-
const mockedStore = () => {
|
|
6
|
-
return { getters: { 'i18n/t': jest.fn() } };
|
|
7
|
-
};
|
|
8
|
-
|
|
9
6
|
const mockedRoute = { query: {} };
|
|
10
7
|
|
|
11
8
|
const requiredSetup = () => {
|
|
9
|
+
const store = createStore({ getters: { 'i18n/t': () => jest.fn() } });
|
|
10
|
+
|
|
12
11
|
return {
|
|
13
12
|
global: {
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
plugins: [store],
|
|
14
|
+
mocks: {
|
|
16
15
|
$route: mockedRoute,
|
|
17
16
|
$fetchState: {},
|
|
18
17
|
}
|