@rancher/shell 0.5.2 → 0.5.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/assets/translations/en-us.yaml +8 -4
- package/components/ClusterIconMenu.vue +24 -9
- package/components/CodeMirror.vue +79 -18
- package/components/FixedBanner.vue +1 -0
- package/components/ResourceDetail/index.vue +1 -4
- package/components/ResourceYaml.vue +29 -5
- package/components/SideNav.vue +42 -64
- package/components/SortableTable/index.vue +1 -1
- package/components/YamlEditor.vue +1 -0
- package/components/__tests__/CodeMirror.spec.ts +99 -0
- package/components/form/BannerSettings.vue +3 -0
- package/components/form/FileSelector.vue +1 -0
- package/components/form/KeyValue.vue +1 -0
- package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
- package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
- package/components/nav/Header.vue +1 -0
- package/components/nav/Jump.vue +19 -9
- package/components/nav/TopLevelMenu.vue +37 -15
- package/components/nav/Type.vue +15 -4
- package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
- package/components/nav/__tests__/Type.test.ts +30 -0
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
- package/detail/fleet.cattle.io.bundle.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +19 -4
- package/edit/management.cattle.io.setting.vue +1 -0
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
- package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +14 -7
- package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
- package/edit/token.vue +1 -0
- package/list/catalog.cattle.io.app.vue +1 -0
- package/list/management.cattle.io.setting.vue +1 -0
- package/machine-config/amazonec2.vue +1 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
- package/models/__tests__/secret.test.ts +37 -0
- package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
- package/models/provisioning.cattle.io.cluster.js +36 -1
- package/models/secret.js +9 -0
- package/models/storage.k8s.io.storageclass.js +1 -1
- package/package.json +1 -1
- package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
- package/pages/c/_cluster/settings/brand.vue +3 -0
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
- package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
- package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
- package/plugins/dashboard-store/actions.js +4 -6
- package/plugins/dashboard-store/getters.js +60 -2
- package/plugins/dashboard-store/resource-class.js +6 -2
- package/plugins/steve/__tests__/getters.spec.ts +10 -0
- package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
- package/plugins/steve/actions.js +3 -37
- package/plugins/steve/getters.js +6 -0
- package/plugins/steve/resource-utils.ts +38 -0
- package/store/__tests__/type-map.test.ts +1122 -0
- package/store/index.js +3 -2
- package/store/type-map.js +145 -75
- package/types/shell/index.d.ts +2 -0
- package/utils/__tests__/create-yaml.test.ts +10 -0
- package/utils/create-yaml.js +5 -1
- package/utils/object.js +10 -0
|
@@ -305,7 +305,7 @@ export default {
|
|
|
305
305
|
|
|
306
306
|
const templates = this.templateOptions;
|
|
307
307
|
const vueKontainerTypes = getters['plugins/clusterDrivers'];
|
|
308
|
-
const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active')
|
|
308
|
+
const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active');
|
|
309
309
|
|
|
310
310
|
this.kontainerDrivers.filter((x) => (isImport ? x.showImport : x.showCreate)).forEach((obj) => {
|
|
311
311
|
if ( vueKontainerTypes.includes(obj.driverName) ) {
|
|
@@ -330,14 +330,18 @@ export default {
|
|
|
330
330
|
});
|
|
331
331
|
|
|
332
332
|
if (this.isRke1 ) {
|
|
333
|
-
machineTypes.forEach((
|
|
334
|
-
|
|
333
|
+
machineTypes.forEach((type) => {
|
|
334
|
+
const id = type.spec.displayName || type.id;
|
|
335
|
+
|
|
336
|
+
addType(id, _RKE1, false, `/g/clusters/add/launch/${ id }`, this.iconClasses[id], type);
|
|
335
337
|
});
|
|
336
338
|
|
|
337
339
|
addType('custom', 'custom1', false, '/g/clusters/add/launch/custom');
|
|
338
340
|
} else {
|
|
339
|
-
machineTypes.forEach((
|
|
340
|
-
|
|
341
|
+
machineTypes.forEach((type) => {
|
|
342
|
+
const id = type.spec.displayName || type.id;
|
|
343
|
+
|
|
344
|
+
addType(id, _RKE2, false, null, undefined, type);
|
|
341
345
|
});
|
|
342
346
|
|
|
343
347
|
addType('custom', 'custom2', false);
|
|
@@ -392,7 +396,7 @@ export default {
|
|
|
392
396
|
out.push(subtype);
|
|
393
397
|
}
|
|
394
398
|
|
|
395
|
-
function addType(id, group, disabled = false, emberLink = null, iconClass = undefined) {
|
|
399
|
+
function addType(id, group, disabled = false, emberLink = null, iconClass = undefined, providerConfig = undefined) {
|
|
396
400
|
const label = getters['i18n/withFallback'](`cluster.provider."${ id }"`, null, id);
|
|
397
401
|
const description = getters['i18n/withFallback'](`cluster.providerDescription."${ id }"`, null, '');
|
|
398
402
|
const tag = '';
|
|
@@ -418,7 +422,8 @@ export default {
|
|
|
418
422
|
group,
|
|
419
423
|
disabled,
|
|
420
424
|
emberLink,
|
|
421
|
-
tag
|
|
425
|
+
tag,
|
|
426
|
+
providerConfig
|
|
422
427
|
};
|
|
423
428
|
|
|
424
429
|
out.push(subtype);
|
|
@@ -638,6 +643,7 @@ export default {
|
|
|
638
643
|
:live-value="liveValue"
|
|
639
644
|
:mode="mode"
|
|
640
645
|
:provider="subType"
|
|
646
|
+
:provider-config="selectedSubType.providerConfig"
|
|
641
647
|
/>
|
|
642
648
|
<Rke2Config
|
|
643
649
|
v-else
|
|
@@ -646,6 +652,7 @@ export default {
|
|
|
646
652
|
:live-value="liveValue"
|
|
647
653
|
:mode="mode"
|
|
648
654
|
:provider="subType"
|
|
655
|
+
:provider-config="selectedSubType.providerConfig"
|
|
649
656
|
/>
|
|
650
657
|
</template>
|
|
651
658
|
|
|
@@ -126,6 +126,11 @@ export default {
|
|
|
126
126
|
type: String,
|
|
127
127
|
required: true,
|
|
128
128
|
},
|
|
129
|
+
|
|
130
|
+
providerConfig: {
|
|
131
|
+
type: Object,
|
|
132
|
+
default: () => null
|
|
133
|
+
}
|
|
129
134
|
},
|
|
130
135
|
|
|
131
136
|
async fetch() {
|
|
@@ -211,7 +216,6 @@ export default {
|
|
|
211
216
|
machinePoolValidation: {}, // map of validation states for each machine pool
|
|
212
217
|
machinePoolErrors: {},
|
|
213
218
|
allNamespaces: [],
|
|
214
|
-
initialCloudProvider: this.value?.agentConfig?.['cloud-provider-name'] || '',
|
|
215
219
|
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
|
|
216
220
|
};
|
|
217
221
|
},
|
|
@@ -326,6 +330,14 @@ export default {
|
|
|
326
330
|
|
|
327
331
|
const out = findBy(this.versionOptions, 'value', str);
|
|
328
332
|
|
|
333
|
+
// Adding the option 'none' to Container Network select (used in Basics component)
|
|
334
|
+
// https://github.com/rancher/dashboard/issues/10338
|
|
335
|
+
// there's an update loop on refresh that might include 'none'
|
|
336
|
+
// multiple times... Prevent that
|
|
337
|
+
if (out.serverArgs?.cni?.options && !out.serverArgs?.cni?.options.includes('none')) {
|
|
338
|
+
out.serverArgs.cni.options.push('none');
|
|
339
|
+
}
|
|
340
|
+
|
|
329
341
|
return out;
|
|
330
342
|
},
|
|
331
343
|
|
|
@@ -351,7 +363,7 @@ export default {
|
|
|
351
363
|
},
|
|
352
364
|
|
|
353
365
|
needCredential() {
|
|
354
|
-
if ( this.provider === 'custom' || this.provider === 'import' || this.isElementalCluster || this.mode === _VIEW ) {
|
|
366
|
+
if ( this.provider === 'custom' || this.provider === 'import' || this.isElementalCluster || this.mode === _VIEW || (this.providerConfig?.spec?.builtin === false && this.providerConfig?.spec?.addCloudCredential === false) ) {
|
|
355
367
|
return false;
|
|
356
368
|
}
|
|
357
369
|
|
|
@@ -577,30 +589,8 @@ export default {
|
|
|
577
589
|
|
|
578
590
|
const cur = this.agentConfig?.['cloud-provider-name'];
|
|
579
591
|
|
|
580
|
-
if (cur && !out.find((x) => x.value === cur)) {
|
|
581
|
-
|
|
582
|
-
// Look up cur in the localization file
|
|
583
|
-
const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ cur }".label`, null, cur);
|
|
584
|
-
|
|
585
|
-
out.unshift({
|
|
586
|
-
label: `${ label } (Current)`,
|
|
587
|
-
value: cur,
|
|
588
|
-
unsupported: true,
|
|
589
|
-
disabled: true
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const initial = this.initialCloudProvider;
|
|
594
|
-
|
|
595
|
-
if (cur !== initial && initial && !out.find((x) => x.value === initial)) {
|
|
596
|
-
const label = this.$store.getters['i18n/withFallback'](`cluster.cloudProvider."${ initial }".label`, null, initial);
|
|
597
|
-
|
|
598
|
-
out.unshift({
|
|
599
|
-
label: `${ label } (Current)`,
|
|
600
|
-
value: initial,
|
|
601
|
-
unsupported: true,
|
|
602
|
-
disabled: true
|
|
603
|
-
});
|
|
592
|
+
if ( cur && !out.find((x) => x.value === cur) ) {
|
|
593
|
+
out.unshift({ label: `${ cur } (Current)`, value: cur });
|
|
604
594
|
}
|
|
605
595
|
|
|
606
596
|
return out;
|
|
@@ -708,14 +698,6 @@ export default {
|
|
|
708
698
|
|
|
709
699
|
return validRequiredPools && base;
|
|
710
700
|
},
|
|
711
|
-
unsupportedCloudProvider() {
|
|
712
|
-
// The current cloud provider
|
|
713
|
-
const cur = this.initialCloudProvider;
|
|
714
|
-
|
|
715
|
-
const provider = cur && this.cloudProviderOptions.find((x) => x.value === cur);
|
|
716
|
-
|
|
717
|
-
return !!provider?.unsupported;
|
|
718
|
-
},
|
|
719
701
|
},
|
|
720
702
|
|
|
721
703
|
watch: {
|
|
@@ -1431,7 +1413,9 @@ export default {
|
|
|
1431
1413
|
for ( const chartName of this.addonNames ) {
|
|
1432
1414
|
const entry = this.chartVersions[chartName];
|
|
1433
1415
|
|
|
1434
|
-
|
|
1416
|
+
// prevent fetching of addon config for 'none' CNI option
|
|
1417
|
+
// https://github.com/rancher/dashboard/issues/10338
|
|
1418
|
+
if ( this.versionInfo[chartName] || chartName.includes('none')) {
|
|
1435
1419
|
continue;
|
|
1436
1420
|
}
|
|
1437
1421
|
|
|
@@ -1568,8 +1552,7 @@ export default {
|
|
|
1568
1552
|
set(regs, 'mirrors', {});
|
|
1569
1553
|
}
|
|
1570
1554
|
|
|
1571
|
-
const
|
|
1572
|
-
const config = regs.configs[hostname];
|
|
1555
|
+
const config = regs.configs[this.registryHost];
|
|
1573
1556
|
|
|
1574
1557
|
if ( config ) {
|
|
1575
1558
|
registrySecret = config.authConfigSecretName;
|
|
@@ -1866,18 +1849,6 @@ export default {
|
|
|
1866
1849
|
if (this.isHarvesterDriver && this.mode === _CREATE && this.isHarvesterIncompatible) {
|
|
1867
1850
|
this.setHarvesterDefaultCloudProvider();
|
|
1868
1851
|
}
|
|
1869
|
-
|
|
1870
|
-
// Cloud Provider check
|
|
1871
|
-
// If the cloud provider is unsupported, switch provider to 'external'
|
|
1872
|
-
if (this.unsupportedCloudProvider) {
|
|
1873
|
-
set(this.agentConfig, 'cloud-provider-name', 'external');
|
|
1874
|
-
} else {
|
|
1875
|
-
// Switch the cloud provider back to the initial value
|
|
1876
|
-
// Use changed the Kubernetes version back to a version where the initial cloud provider is valid - so switch back to this one
|
|
1877
|
-
// to undo the change to external that we may have made
|
|
1878
|
-
// Note: Cloud Provider can only be changed on edit when the initial provider is no longer supported
|
|
1879
|
-
set(this.agentConfig, 'cloud-provider-name', this.initialCloudProvider);
|
|
1880
|
-
}
|
|
1881
1852
|
}
|
|
1882
1853
|
},
|
|
1883
1854
|
|
|
@@ -2048,11 +2019,13 @@ export default {
|
|
|
2048
2019
|
:cancel="cancelCredential"
|
|
2049
2020
|
:showing-form="showForm"
|
|
2050
2021
|
:default-on-cancel="true"
|
|
2022
|
+
data-testid="select-credential"
|
|
2051
2023
|
class="mt-20"
|
|
2052
2024
|
/>
|
|
2053
2025
|
|
|
2054
2026
|
<div
|
|
2055
2027
|
v-if="showForm"
|
|
2028
|
+
data-testid="form"
|
|
2056
2029
|
class="mt-20"
|
|
2057
2030
|
>
|
|
2058
2031
|
<NameNsDescription
|
|
@@ -2182,7 +2155,6 @@ export default {
|
|
|
2182
2155
|
:have-arg-info="haveArgInfo"
|
|
2183
2156
|
:show-cni="showCni"
|
|
2184
2157
|
:show-cloud-provider="showCloudProvider"
|
|
2185
|
-
:unsupported-cloud-provider="unsupportedCloudProvider"
|
|
2186
2158
|
:cloud-provider-options="cloudProviderOptions"
|
|
2187
2159
|
@cilium-values-changed="handleCiliumValuesChanged"
|
|
2188
2160
|
@enabled-system-services-changed="handleEnabledSystemServicesChanged"
|
|
@@ -103,10 +103,6 @@ export default {
|
|
|
103
103
|
type: Boolean,
|
|
104
104
|
required: true
|
|
105
105
|
},
|
|
106
|
-
unsupportedCloudProvider: {
|
|
107
|
-
type: Boolean,
|
|
108
|
-
required: true
|
|
109
|
-
},
|
|
110
106
|
cloudProviderOptions: {
|
|
111
107
|
type: Array,
|
|
112
108
|
required: true
|
|
@@ -369,7 +365,7 @@ export default {
|
|
|
369
365
|
},
|
|
370
366
|
|
|
371
367
|
canNotEditCloudProvider() {
|
|
372
|
-
const canNotEdit = this.isEdit
|
|
368
|
+
const canNotEdit = this.isEdit;
|
|
373
369
|
|
|
374
370
|
return canNotEdit;
|
|
375
371
|
},
|
|
@@ -421,12 +417,20 @@ export default {
|
|
|
421
417
|
>
|
|
422
418
|
<span v-clean-html="t('cluster.banner.cloudProviderAddConfig', {}, true)" />
|
|
423
419
|
</Banner>
|
|
420
|
+
<Banner
|
|
421
|
+
v-if="serverConfig.cni === 'none'"
|
|
422
|
+
color="warning"
|
|
423
|
+
data-testid="clusterBasics__noneOptionSelectedForCni"
|
|
424
|
+
>
|
|
425
|
+
<span v-clean-html="t('cluster.rke2.cni.cniNoneBanner', {}, true)" />
|
|
426
|
+
</Banner>
|
|
424
427
|
<div class="row mb-10">
|
|
425
428
|
<div class="col span-6">
|
|
426
429
|
<LabeledSelect
|
|
427
430
|
v-model="value.spec.kubernetesVersion"
|
|
428
431
|
:mode="mode"
|
|
429
432
|
:options="versionOptions"
|
|
433
|
+
data-testid="clusterBasics__kubernetesVersions"
|
|
430
434
|
label-key="cluster.kubernetesVersion.label"
|
|
431
435
|
@input="$emit('kubernetes-changed', $event)"
|
|
432
436
|
/>
|
|
@@ -495,12 +499,6 @@ export default {
|
|
|
495
499
|
<div class="spacer" />
|
|
496
500
|
|
|
497
501
|
<div class="col span-12">
|
|
498
|
-
<Banner
|
|
499
|
-
v-if="unsupportedCloudProvider"
|
|
500
|
-
class="error mt-5"
|
|
501
|
-
>
|
|
502
|
-
{{ t('cluster.rke2.cloudProvider.unsupported') }}
|
|
503
|
-
</Banner>
|
|
504
502
|
<h3>
|
|
505
503
|
{{ t('cluster.rke2.cloudProvider.header') }}
|
|
506
504
|
</h3>
|
|
@@ -138,13 +138,14 @@ export default {
|
|
|
138
138
|
:mode="mode"
|
|
139
139
|
@input="update"
|
|
140
140
|
>
|
|
141
|
-
<template #default="{row}">
|
|
141
|
+
<template #default="{row, i}">
|
|
142
142
|
<div class="row">
|
|
143
143
|
<div class="col span-6">
|
|
144
144
|
<LabeledInput
|
|
145
145
|
v-model="row.value.hostname"
|
|
146
146
|
label="Registry Hostname"
|
|
147
147
|
:mode="mode"
|
|
148
|
+
:data-testid="`registry-auth-host-input-${i}`"
|
|
148
149
|
/>
|
|
149
150
|
|
|
150
151
|
<SelectOrCreateAuthSecret
|
|
@@ -158,6 +159,7 @@ export default {
|
|
|
158
159
|
:namespace="value.metadata.namespace"
|
|
159
160
|
:mode="mode"
|
|
160
161
|
generate-name="registryconfig-auth-"
|
|
162
|
+
:data-testid="`registry-auth-select-or-create-${i}`"
|
|
161
163
|
/>
|
|
162
164
|
</div>
|
|
163
165
|
<div class="col span-6">
|
|
@@ -79,6 +79,7 @@ export default {
|
|
|
79
79
|
:value="showCustomRegistryInput"
|
|
80
80
|
class="mb-20"
|
|
81
81
|
:label="t('cluster.privateRegistry.label')"
|
|
82
|
+
data-testid="registries-enable-checkbox"
|
|
82
83
|
@input="$emit('custom-registry-changed', $event)"
|
|
83
84
|
/>
|
|
84
85
|
</div>
|
|
@@ -92,6 +93,7 @@ export default {
|
|
|
92
93
|
label-key="catalog.chart.registry.custom.inputLabel"
|
|
93
94
|
placeholder-key="catalog.chart.registry.custom.placeholder"
|
|
94
95
|
:min-height="30"
|
|
96
|
+
data-testid="registry-host-input"
|
|
95
97
|
@input="$emit('registry-host-changed', $event)"
|
|
96
98
|
/>
|
|
97
99
|
<SelectOrCreateAuthSecret
|
|
@@ -118,6 +120,7 @@ export default {
|
|
|
118
120
|
class="col span-12 advanced"
|
|
119
121
|
:is-open-by-default="showCustomRegistryAdvancedInput"
|
|
120
122
|
:mode="mode"
|
|
123
|
+
data-testid="registries-advanced-section"
|
|
121
124
|
>
|
|
122
125
|
<Banner
|
|
123
126
|
:closable="false"
|
package/edit/token.vue
CHANGED
|
@@ -39,6 +39,7 @@ export default {
|
|
|
39
39
|
:loading="loading"
|
|
40
40
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
41
41
|
:force-update-live-and-delayed="forceUpdateLiveAndDelayed"
|
|
42
|
+
data-testid="installed-app-catalog-list"
|
|
42
43
|
>
|
|
43
44
|
<template #cell:upgrade="{row}">
|
|
44
45
|
<span
|
|
@@ -475,6 +475,7 @@ export default {
|
|
|
475
475
|
:disabled="disabled"
|
|
476
476
|
:placeholder="t('cluster.machineConfig.amazonEc2.selectedNetwork.placeholder')"
|
|
477
477
|
:label="t('cluster.machineConfig.amazonEc2.selectedNetwork.label')"
|
|
478
|
+
data-testid="amazonEc2__selectedNetwork"
|
|
478
479
|
option-key="value"
|
|
479
480
|
@input="updateNetwork($event)"
|
|
480
481
|
>
|
|
@@ -26,6 +26,56 @@ describe('class ProvCluster', () => {
|
|
|
26
26
|
clusterName: 'test', provisioner: 'rke2', mgmt: { isLocal: false, providerForEmberParam: 'import' }, spec: { rkeConfig: {} }
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
const gkeClusterWithPrivateEndpoint = {
|
|
30
|
+
clusterName: 'test',
|
|
31
|
+
provisioner: 'GKE',
|
|
32
|
+
spec: { rkeConfig: {} },
|
|
33
|
+
mgmt: { spec: { gkeConfig: { privateClusterConfig: { enablePrivateEndpoint: true } } } }
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const eksClusterWithPrivateEndpoint = {
|
|
37
|
+
clusterName: 'test',
|
|
38
|
+
provisioner: 'EKS',
|
|
39
|
+
spec: { rkeConfig: {} },
|
|
40
|
+
mgmt: { spec: { eksConfig: { privateAccess: true } } }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const aksClusterWithPrivateEndpoint = {
|
|
44
|
+
clusterName: 'test',
|
|
45
|
+
provisioner: 'AKS',
|
|
46
|
+
spec: { rkeConfig: {} },
|
|
47
|
+
mgmt: { spec: { aksConfig: { privateCluster: true } } }
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Related to https://github.com/rancher/dashboard/issues/9402
|
|
51
|
+
describe('isHostedKubernetesProvider + isPrivateHostedProvider', () => {
|
|
52
|
+
const testCases = [
|
|
53
|
+
[gkeClusterWithPrivateEndpoint, true],
|
|
54
|
+
[eksClusterWithPrivateEndpoint, true],
|
|
55
|
+
[aksClusterWithPrivateEndpoint, true],
|
|
56
|
+
];
|
|
57
|
+
const resetMocks = () => {
|
|
58
|
+
// Clear all mock function calls:
|
|
59
|
+
jest.clearAllMocks();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
it.each(testCases)('should return the isHostedKubernetesProvider and isPrivateHostedProvider values properly based on the props data', (clusterData: Object, expected: Boolean) => {
|
|
63
|
+
const cluster = new ProvCluster({ spec: clusterData.spec });
|
|
64
|
+
|
|
65
|
+
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(
|
|
66
|
+
clusterData.mgmt
|
|
67
|
+
);
|
|
68
|
+
jest.spyOn(cluster, 'provisioner', 'get').mockReturnValue(
|
|
69
|
+
clusterData.provisioner
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expect(cluster.isRke2).toBe(expected);
|
|
73
|
+
expect(cluster.isHostedKubernetesProvider).toBe(expected);
|
|
74
|
+
expect(cluster.isPrivateHostedProvider).toBe(expected);
|
|
75
|
+
resetMocks();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
29
79
|
describe('isImported', () => {
|
|
30
80
|
const testCases = [
|
|
31
81
|
[importedClusterInfo, true],
|
|
@@ -84,6 +134,107 @@ describe('class ProvCluster', () => {
|
|
|
84
134
|
|
|
85
135
|
expect(cluster.mgmt).toBe(expected);
|
|
86
136
|
resetMocks();
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('hasError', () => {
|
|
141
|
+
const conditionsWithoutError = [
|
|
142
|
+
{
|
|
143
|
+
error: false,
|
|
144
|
+
lastUpdateTime: '2022-10-17T23:09:15Z',
|
|
145
|
+
status: 'True',
|
|
146
|
+
transitioning: false,
|
|
147
|
+
type: 'Ready'
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
const conditionsWithoutReady = [
|
|
152
|
+
{
|
|
153
|
+
error: true,
|
|
154
|
+
lastUpdateTime: '2022-10-17T23:09:15Z',
|
|
155
|
+
status: 'False',
|
|
156
|
+
message: 'some-error-message',
|
|
157
|
+
transitioning: false,
|
|
158
|
+
type: 'Pending'
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const noConditions:[] = [];
|
|
163
|
+
|
|
164
|
+
const conditionsWithReadyLatest = [
|
|
165
|
+
{
|
|
166
|
+
error: true,
|
|
167
|
+
lastUpdateTime: '2022-10-17T23:09:15Z',
|
|
168
|
+
status: 'False',
|
|
169
|
+
message: 'some-error-message',
|
|
170
|
+
transitioning: false,
|
|
171
|
+
type: 'Pending'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
error: false,
|
|
175
|
+
lastUpdateTime: '2023-10-17T23:09:15Z',
|
|
176
|
+
status: 'True',
|
|
177
|
+
transitioning: false,
|
|
178
|
+
type: 'Ready'
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const conditionsWithErrorLatest = [
|
|
183
|
+
{
|
|
184
|
+
error: false,
|
|
185
|
+
lastUpdateTime: '2022-10-17T23:09:15Z',
|
|
186
|
+
status: 'True',
|
|
187
|
+
transitioning: false,
|
|
188
|
+
type: 'Ready'
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
error: true,
|
|
192
|
+
lastUpdateTime: '2023-10-17T23:09:15Z',
|
|
193
|
+
status: 'False',
|
|
194
|
+
message: 'some-error-message',
|
|
195
|
+
transitioning: false,
|
|
196
|
+
type: 'Pending'
|
|
197
|
+
}
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
const conditionsWithProblemInLastUpdateTimeProp = [
|
|
201
|
+
{
|
|
202
|
+
error: true,
|
|
203
|
+
lastUpdateTime: '',
|
|
204
|
+
status: 'False',
|
|
205
|
+
message: 'some-error-message',
|
|
206
|
+
transitioning: false,
|
|
207
|
+
type: 'Pending'
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
error: false,
|
|
211
|
+
lastUpdateTime: '2023-10-17T23:09:15Z',
|
|
212
|
+
status: 'True',
|
|
213
|
+
transitioning: false,
|
|
214
|
+
type: 'Ready'
|
|
215
|
+
}
|
|
216
|
+
];
|
|
217
|
+
|
|
218
|
+
const testCases = [
|
|
219
|
+
['conditionsWithoutError', conditionsWithoutError, false],
|
|
220
|
+
['conditionsWithoutReady', conditionsWithoutReady, true],
|
|
221
|
+
['noConditions', noConditions, false],
|
|
222
|
+
['conditionsWithReadyLatest', conditionsWithReadyLatest, false],
|
|
223
|
+
['conditionsWithErrorLatest', conditionsWithErrorLatest, true],
|
|
224
|
+
['conditionsWithProblemInLastUpdateTimeProp', conditionsWithProblemInLastUpdateTimeProp, false],
|
|
225
|
+
];
|
|
226
|
+
|
|
227
|
+
const resetMocks = () => {
|
|
228
|
+
// Clear all mock function calls
|
|
229
|
+
jest.clearAllMocks();
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
it.each(testCases)('should return the hasError value properly based on the "status.conditions" props data for testcase %p', (testName: string, conditions: Array, expected: Boolean) => {
|
|
233
|
+
const ctx = { rootGetters: { 'management/byId': jest.fn() } };
|
|
234
|
+
const cluster = new ProvCluster({ status: { conditions } }, ctx);
|
|
235
|
+
|
|
236
|
+
expect(cluster.hasError).toBe(expected);
|
|
237
|
+
resetMocks();
|
|
87
238
|
}
|
|
88
239
|
);
|
|
89
240
|
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import Secret from '@shell/models/secret';
|
|
2
|
+
|
|
3
|
+
describe('class Secret', () => {
|
|
4
|
+
it('should contains the type attribute if cleanForDownload', async() => {
|
|
5
|
+
const secret = new Secret({});
|
|
6
|
+
const yaml = `apiVersion: v1
|
|
7
|
+
kind: Secret
|
|
8
|
+
metadata:
|
|
9
|
+
name: my-secret
|
|
10
|
+
type: Opaque
|
|
11
|
+
`;
|
|
12
|
+
const cleanYaml = await secret.cleanForDownload(yaml);
|
|
13
|
+
|
|
14
|
+
expect(cleanYaml).toBe(yaml);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should remove id, links and actions keys if cleanForDownload', async() => {
|
|
18
|
+
const secret = new Secret({});
|
|
19
|
+
const expectedYamlStr = `apiVersion: v1
|
|
20
|
+
kind: Secret
|
|
21
|
+
metadata:
|
|
22
|
+
name: my-secret
|
|
23
|
+
namespace: default
|
|
24
|
+
type: Opaque
|
|
25
|
+
`;
|
|
26
|
+
const part = `id: test_id
|
|
27
|
+
links:
|
|
28
|
+
view: https://example.com
|
|
29
|
+
actions:
|
|
30
|
+
remove: https://example.com`;
|
|
31
|
+
const yaml = `${ expectedYamlStr }
|
|
32
|
+
${ part }`;
|
|
33
|
+
const cleanYaml = await secret.cleanForDownload(yaml);
|
|
34
|
+
|
|
35
|
+
expect(cleanYaml).toBe(expectedYamlStr);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import StorageClass, { PROVISIONER_OPTIONS } from '@shell/models/storage.k8s.io.storageclass';
|
|
2
|
+
|
|
3
|
+
describe('class StorageClass', () => {
|
|
4
|
+
describe('checking if provisionerDisplay', () => {
|
|
5
|
+
it.each([
|
|
6
|
+
['kubernetes.io/azure-disk', true],
|
|
7
|
+
['kubernetes.io/portworx-volume', true],
|
|
8
|
+
['rancher.io/local-path', false],
|
|
9
|
+
['some-random-string-as-provisioner', false],
|
|
10
|
+
])('should NOT show a suffix IF they are built-in (on the PROVISIONER_OPTIONS list)', (provisioner, expectation) => {
|
|
11
|
+
const storageClass = new StorageClass({
|
|
12
|
+
metadata: {},
|
|
13
|
+
spec: {},
|
|
14
|
+
provisioner
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
jest.spyOn(storageClass, '$rootGetters', 'get').mockReturnValue({ 'i18n/t': jest.fn() });
|
|
18
|
+
|
|
19
|
+
expect(!!PROVISIONER_OPTIONS.find((opt) => opt.value === provisioner)).toBe(expectation);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -249,6 +249,21 @@ export default class ProvCluster extends SteveModel {
|
|
|
249
249
|
return providers.includes(this.provisioner);
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
+
get isPrivateHostedProvider() {
|
|
253
|
+
if (this.isHostedKubernetesProvider && this.mgmt && this.provisioner) {
|
|
254
|
+
switch (this.provisioner.toLowerCase()) {
|
|
255
|
+
case 'gke':
|
|
256
|
+
return this.mgmt.spec?.gkeConfig?.privateClusterConfig?.enablePrivateEndpoint;
|
|
257
|
+
case 'eks':
|
|
258
|
+
return this.mgmt.spec?.eksConfig?.privateAccess;
|
|
259
|
+
case 'aks':
|
|
260
|
+
return this.mgmt.spec?.aksConfig?.privateCluster;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
252
267
|
get isLocal() {
|
|
253
268
|
return this.mgmt?.isLocal;
|
|
254
269
|
}
|
|
@@ -891,7 +906,27 @@ export default class ProvCluster extends SteveModel {
|
|
|
891
906
|
}
|
|
892
907
|
|
|
893
908
|
get hasError() {
|
|
894
|
-
|
|
909
|
+
// Before we were just checking for this.status?.conditions?.some((condition) => condition.error === true)
|
|
910
|
+
// but this is wrong as an error might exist but it might not be meaningful in the context of readiness of a cluster
|
|
911
|
+
// which is what this 'hasError' is used for.
|
|
912
|
+
// We now check if there's a ready condition after an error, which helps dictate the readiness of a cluster
|
|
913
|
+
// Based on the findings in https://github.com/rancher/dashboard/issues/10043
|
|
914
|
+
if (this.status?.conditions && this.status?.conditions.length) {
|
|
915
|
+
// if there are errors, we compare with how recent the "Ready" condition is compared to that error, otherwise we just move on
|
|
916
|
+
if (this.status?.conditions.some((c) => c.error === true)) {
|
|
917
|
+
// there's no ready condition and has an error, mark it
|
|
918
|
+
if (!this.status?.conditions.some((c) => c.type === 'Ready')) {
|
|
919
|
+
return true;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const filteredConditions = this.status?.conditions.filter((c) => c.error === true || c.type === 'Ready');
|
|
923
|
+
const mostRecentCondition = filteredConditions.reduce((a, b) => ((a.lastUpdateTime > b.lastUpdateTime) ? a : b));
|
|
924
|
+
|
|
925
|
+
return mostRecentCondition.error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
return false;
|
|
895
930
|
}
|
|
896
931
|
|
|
897
932
|
get namespaceLocation() {
|
package/models/secret.js
CHANGED
|
@@ -9,6 +9,7 @@ import SteveModel from '@shell/plugins/steve/steve-class';
|
|
|
9
9
|
import { colorForState, stateDisplay, STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
|
|
10
10
|
import { diffFrom } from '@shell/utils/time';
|
|
11
11
|
import day from 'dayjs';
|
|
12
|
+
import { steveCleanForDownload } from '@shell/plugins/steve/resource-utils';
|
|
12
13
|
|
|
13
14
|
export const TYPES = {
|
|
14
15
|
OPAQUE: 'Opaque',
|
|
@@ -456,4 +457,12 @@ export default class Secret extends SteveModel {
|
|
|
456
457
|
|
|
457
458
|
return val;
|
|
458
459
|
}
|
|
460
|
+
|
|
461
|
+
async cleanForDownload(yaml) {
|
|
462
|
+
// secret resource contains the type attribute
|
|
463
|
+
// ref: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/secret-v1/
|
|
464
|
+
// ref: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types
|
|
465
|
+
|
|
466
|
+
return steveCleanForDownload(yaml, { rootKeys: ['id', 'links', 'actions'] });
|
|
467
|
+
}
|
|
459
468
|
}
|
|
@@ -84,7 +84,7 @@ export const PROVISIONER_OPTIONS = [
|
|
|
84
84
|
export default class extends SteveModel {
|
|
85
85
|
get provisionerDisplay() {
|
|
86
86
|
const option = PROVISIONER_OPTIONS.find((o) => o.value === this.provisioner);
|
|
87
|
-
const fallback = `${ this.provisioner } ${ this.t('persistentVolume.csi.
|
|
87
|
+
const fallback = `${ this.provisioner } ${ this.t('persistentVolume.csi.suffix') }`;
|
|
88
88
|
|
|
89
89
|
return option ? this.t(option.labelKey) : this.$rootGetters['i18n/withFallback'](`persistentVolume.csi.drivers.${ this.provisioner.replaceAll('.', '-') }`, null, fallback);
|
|
90
90
|
}
|