@rancher/shell 2.0.2-rc.1 → 2.0.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 +53 -31
- package/components/PromptRemove.vue +8 -3
- package/components/ResourceDetail/Masthead.vue +1 -0
- package/components/ResourceDetail/index.vue +2 -1
- package/components/SideNav.vue +1 -1
- package/components/TableDataUserIcon.vue +1 -1
- package/components/fleet/FleetClusters.vue +0 -3
- package/components/fleet/FleetRepos.vue +0 -7
- package/components/formatter/CloudCredExpired.vue +69 -0
- package/components/formatter/ClusterProvider.vue +3 -3
- package/components/formatter/Date.vue +1 -1
- package/components/nav/Header.vue +9 -5
- package/components/nav/TopLevelMenu.vue +127 -63
- package/components/nav/__tests__/TopLevelMenu.test.ts +53 -27
- package/config/labels-annotations.js +3 -0
- package/core/types-provisioning.ts +5 -0
- package/core/types.ts +26 -1
- package/detail/catalog.cattle.io.app.vue +17 -4
- package/detail/fleet.cattle.io.bundle.vue +5 -68
- package/detail/fleet.cattle.io.cluster.vue +11 -9
- package/detail/fleet.cattle.io.gitrepo.vue +3 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +109 -24
- package/edit/provisioning.cattle.io.cluster/index.vue +10 -4
- package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
- package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +177 -26
- package/list/provisioning.cattle.io.cluster.vue +56 -5
- package/mixins/chart.js +6 -2
- package/models/__tests__/management.cattle.io.cluster.test.ts +3 -3
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +0 -86
- package/models/catalog.cattle.io.app.js +108 -21
- package/models/cloudcredential.js +159 -2
- package/models/fleet.cattle.io.bundle.js +3 -1
- package/models/fleet.cattle.io.gitrepo.js +50 -61
- package/models/management.cattle.io.cluster.js +15 -7
- package/models/provisioning.cattle.io.cluster.js +62 -15
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +2 -1
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/explorer/index.vue +1 -2
- package/pages/c/_cluster/fleet/index.vue +12 -5
- package/pages/c/_cluster/manager/cloudCredential/index.vue +68 -4
- package/pages/c/_cluster/uiplugins/index.vue +4 -2
- package/pages/home.vue +1 -0
- package/scripts/extension/bundle +1 -1
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
- package/scripts/extension/parse-tag-name +21 -12
- package/scripts/publish-shell.sh +10 -4
- package/scripts/typegen.sh +27 -22
- package/store/features.js +1 -0
- package/types/resources/fleet.d.ts +40 -0
- package/types/shell/index.d.ts +4692 -0
- package/utils/auth.js +1 -1
- package/utils/cluster.js +1 -1
- package/utils/fleet.ts +159 -0
- package/utils/string.js +9 -0
- package/utils/v-sphere.ts +282 -0
- package/vue.config.js +3 -3
- package/shell/types/shell/index.d.ts +0 -2
|
@@ -1,63 +1,214 @@
|
|
|
1
1
|
|
|
2
2
|
<script>
|
|
3
3
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
4
|
-
import { _CREATE } from '@shell/config/query-params';
|
|
4
|
+
import { _CREATE, _EDIT } from '@shell/config/query-params';
|
|
5
|
+
import RadioGroup from '@components/Form/Radio/RadioGroup.vue';
|
|
6
|
+
import { Banner } from '@components/Banner';
|
|
7
|
+
|
|
8
|
+
export const DATA_DIR_RADIO_OPTIONS = {
|
|
9
|
+
DEFAULT: 'defaultDataDir',
|
|
10
|
+
COMMON: 'commonBaseDataDir',
|
|
11
|
+
CUSTOM: 'customDataDir',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const DEFAULT_COMMON_BASE_PATH = '/var/lib/rancher';
|
|
15
|
+
|
|
16
|
+
export const DEFAULT_SUBDIRS = {
|
|
17
|
+
AGENT: 'agent',
|
|
18
|
+
PROVISIONING: 'provisioning',
|
|
19
|
+
K8S_DISTRO_RKE2: 'rke2',
|
|
20
|
+
K8S_DISTRO_K3S: 'k3s',
|
|
21
|
+
};
|
|
5
22
|
|
|
6
23
|
export default {
|
|
7
24
|
name: 'DirectoryConfig',
|
|
8
|
-
components: {
|
|
9
|
-
|
|
25
|
+
components: {
|
|
26
|
+
LabeledInput,
|
|
27
|
+
RadioGroup,
|
|
28
|
+
Banner
|
|
29
|
+
},
|
|
30
|
+
props: {
|
|
10
31
|
mode: {
|
|
11
32
|
type: String,
|
|
12
33
|
required: true,
|
|
13
34
|
},
|
|
14
35
|
|
|
36
|
+
k8sVersion: {
|
|
37
|
+
type: String,
|
|
38
|
+
required: true,
|
|
39
|
+
},
|
|
40
|
+
|
|
15
41
|
value: {
|
|
16
42
|
type: Object,
|
|
17
43
|
required: true,
|
|
18
44
|
},
|
|
19
45
|
},
|
|
46
|
+
data() {
|
|
47
|
+
let dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
|
|
48
|
+
let k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
|
|
49
|
+
|
|
50
|
+
if (this.k8sVersion && this.k8sVersion.includes('k3s')) {
|
|
51
|
+
k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_K3S;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (this.mode !== _CREATE) {
|
|
55
|
+
dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
DATA_DIR_RADIO_OPTIONS,
|
|
60
|
+
dataConfigRadioValue,
|
|
61
|
+
k8sDistroSubDir,
|
|
62
|
+
commonConfig: '',
|
|
63
|
+
};
|
|
64
|
+
},
|
|
65
|
+
watch: {
|
|
66
|
+
commonConfig(neu) {
|
|
67
|
+
if (neu && neu.length && this.dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON) {
|
|
68
|
+
this.value.systemAgent = `${ neu }/${ DEFAULT_SUBDIRS.AGENT }`;
|
|
69
|
+
this.value.provisioning = `${ neu }/${ DEFAULT_SUBDIRS.PROVISIONING }`;
|
|
70
|
+
this.value.k8sDistro = `${ neu }/${ this.k8sDistroSubDir }`;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
k8sVersion: {
|
|
74
|
+
handler(neu) {
|
|
75
|
+
if (neu && neu.includes('k3s')) {
|
|
76
|
+
this.k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_K3S;
|
|
77
|
+
} else {
|
|
78
|
+
this.k8sDistroSubDir = DEFAULT_SUBDIRS.K8S_DISTRO_RKE2;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (this.value.k8sDistro) {
|
|
82
|
+
this.value.k8sDistro = `${ neu }/${ this.k8sDistroSubDir }`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
},
|
|
20
87
|
computed: {
|
|
21
|
-
|
|
22
|
-
return this.mode
|
|
88
|
+
isDisabled() {
|
|
89
|
+
return this.mode === _EDIT;
|
|
90
|
+
},
|
|
91
|
+
dataConfigRadioOptions() {
|
|
92
|
+
const defaultDataDirOption = {
|
|
93
|
+
value: DATA_DIR_RADIO_OPTIONS.DEFAULT,
|
|
94
|
+
label: this.t('cluster.directoryConfig.radioInput.defaultLabel')
|
|
95
|
+
};
|
|
96
|
+
const customDataDirOption = {
|
|
97
|
+
value: DATA_DIR_RADIO_OPTIONS.CUSTOM,
|
|
98
|
+
label: this.t('cluster.directoryConfig.radioInput.customLabel')
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (this.mode === _CREATE) {
|
|
102
|
+
return [
|
|
103
|
+
defaultDataDirOption,
|
|
104
|
+
{ value: DATA_DIR_RADIO_OPTIONS.COMMON, label: this.t('cluster.directoryConfig.radioInput.commonLabel') },
|
|
105
|
+
customDataDirOption
|
|
106
|
+
];
|
|
107
|
+
} else {
|
|
108
|
+
return [
|
|
109
|
+
defaultDataDirOption,
|
|
110
|
+
customDataDirOption
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
methods: {
|
|
116
|
+
handleRadioInput(val) {
|
|
117
|
+
switch (val) {
|
|
118
|
+
case DATA_DIR_RADIO_OPTIONS.DEFAULT:
|
|
119
|
+
if (this.mode === _CREATE) {
|
|
120
|
+
this.commonConfig = '';
|
|
121
|
+
}
|
|
122
|
+
this.value.systemAgent = '';
|
|
123
|
+
this.value.provisioning = '';
|
|
124
|
+
this.value.k8sDistro = '';
|
|
125
|
+
|
|
126
|
+
this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.DEFAULT;
|
|
127
|
+
break;
|
|
128
|
+
case DATA_DIR_RADIO_OPTIONS.COMMON:
|
|
129
|
+
this.commonConfig = DEFAULT_COMMON_BASE_PATH;
|
|
130
|
+
|
|
131
|
+
this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.COMMON;
|
|
132
|
+
break;
|
|
133
|
+
// default is custom config
|
|
134
|
+
default:
|
|
135
|
+
if (this.mode === _CREATE) {
|
|
136
|
+
this.commonConfig = '';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
this.value.systemAgent = '';
|
|
140
|
+
this.value.provisioning = '';
|
|
141
|
+
this.value.k8sDistro = '';
|
|
142
|
+
|
|
143
|
+
this.dataConfigRadioValue = DATA_DIR_RADIO_OPTIONS.CUSTOM;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
23
146
|
}
|
|
24
|
-
}
|
|
147
|
+
},
|
|
25
148
|
};
|
|
26
149
|
</script>
|
|
27
150
|
|
|
28
151
|
<template>
|
|
29
152
|
<div class="row">
|
|
30
153
|
<div class="col span-8">
|
|
31
|
-
<h3
|
|
154
|
+
<h3>
|
|
32
155
|
{{ t('cluster.directoryConfig.title') }}
|
|
33
156
|
</h3>
|
|
34
|
-
<
|
|
35
|
-
v-model="value.systemAgent"
|
|
157
|
+
<Banner
|
|
36
158
|
class="mb-20"
|
|
37
|
-
:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
:disabled="disableEditInput"
|
|
41
|
-
data-testid="rke2-directory-config-systemAgent-data-dir"
|
|
159
|
+
:closable="false"
|
|
160
|
+
color="info"
|
|
161
|
+
label-key="cluster.directoryConfig.banner"
|
|
42
162
|
/>
|
|
43
|
-
<
|
|
44
|
-
v-
|
|
45
|
-
|
|
163
|
+
<RadioGroup
|
|
164
|
+
v-show="!isDisabled"
|
|
165
|
+
:value="dataConfigRadioValue"
|
|
166
|
+
class="mb-10"
|
|
46
167
|
:mode="mode"
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
168
|
+
:options="dataConfigRadioOptions"
|
|
169
|
+
name="directory-config-radio"
|
|
170
|
+
data-testid="rke2-directory-config-radio-input"
|
|
171
|
+
@input="handleRadioInput"
|
|
51
172
|
/>
|
|
52
173
|
<LabeledInput
|
|
53
|
-
v-
|
|
174
|
+
v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.COMMON"
|
|
175
|
+
v-model="commonConfig"
|
|
54
176
|
class="mb-20"
|
|
55
177
|
:mode="mode"
|
|
56
|
-
:label="t('cluster.directoryConfig.
|
|
57
|
-
:tooltip="t('cluster.directoryConfig.
|
|
58
|
-
:disabled="
|
|
59
|
-
data-testid="rke2-directory-config-
|
|
178
|
+
:label="t('cluster.directoryConfig.common.label')"
|
|
179
|
+
:tooltip="t('cluster.directoryConfig.common.tooltip')"
|
|
180
|
+
:disabled="isDisabled"
|
|
181
|
+
data-testid="rke2-directory-config-common-data-dir"
|
|
60
182
|
/>
|
|
183
|
+
<div v-if="dataConfigRadioValue === DATA_DIR_RADIO_OPTIONS.CUSTOM">
|
|
184
|
+
<LabeledInput
|
|
185
|
+
v-model="value.systemAgent"
|
|
186
|
+
class="mb-20"
|
|
187
|
+
:mode="mode"
|
|
188
|
+
:label="t('cluster.directoryConfig.systemAgent.label')"
|
|
189
|
+
:tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
|
|
190
|
+
:disabled="isDisabled"
|
|
191
|
+
data-testid="rke2-directory-config-systemAgent-data-dir"
|
|
192
|
+
/>
|
|
193
|
+
<LabeledInput
|
|
194
|
+
v-model="value.provisioning"
|
|
195
|
+
class="mb-20"
|
|
196
|
+
:mode="mode"
|
|
197
|
+
:label="t('cluster.directoryConfig.provisioning.label')"
|
|
198
|
+
:tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
|
|
199
|
+
:disabled="isDisabled"
|
|
200
|
+
data-testid="rke2-directory-config-provisioning-data-dir"
|
|
201
|
+
/>
|
|
202
|
+
<LabeledInput
|
|
203
|
+
v-model="value.k8sDistro"
|
|
204
|
+
class="mb-20"
|
|
205
|
+
:mode="mode"
|
|
206
|
+
:label="t('cluster.directoryConfig.k8sDistro.label')"
|
|
207
|
+
:tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
|
|
208
|
+
:disabled="isDisabled"
|
|
209
|
+
data-testid="rke2-directory-config-k8sDistro-data-dir"
|
|
210
|
+
/>
|
|
211
|
+
</div>
|
|
61
212
|
<div class="mb-40" />
|
|
62
213
|
</div>
|
|
63
214
|
</div>
|
|
@@ -10,10 +10,11 @@ import { mapFeature, HARVESTER as HARVESTER_FEATURE } from '@shell/store/feature
|
|
|
10
10
|
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
11
11
|
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
12
12
|
import { BadgeState } from '@components/BadgeState';
|
|
13
|
+
import CloudCredExpired from '@shell/components/formatter/CloudCredExpired';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
components: {
|
|
16
|
-
Banner, ResourceTable, Masthead, BadgeState
|
|
17
|
+
Banner, ResourceTable, Masthead, BadgeState, CloudCredExpired
|
|
17
18
|
},
|
|
18
19
|
mixins: [ResourceFetch],
|
|
19
20
|
props: {
|
|
@@ -41,6 +42,8 @@ export default {
|
|
|
41
42
|
mgmtClusters: this.$fetchType(MANAGEMENT.CLUSTER),
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
this.$store.dispatch('rancher/findAll', { type: NORMAN.CLOUD_CREDENTIAL });
|
|
46
|
+
|
|
44
47
|
if ( this.$store.getters['management/canList'](SNAPSHOT) ) {
|
|
45
48
|
hash.etcdSnapshots = this.$fetchType(SNAPSHOT);
|
|
46
49
|
}
|
|
@@ -141,6 +144,29 @@ export default {
|
|
|
141
144
|
// This will be used when there's clusters from extension based provisioners
|
|
142
145
|
// We should re-visit this for scaling reasons
|
|
143
146
|
return this.filteredRows.some((c) => c.metadata.namespace !== 'fleet-local' && c.metadata.namespace !== 'fleet-default');
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
tokenExpiredData() {
|
|
150
|
+
const counts = this.rows.reduce((res, provCluster) => {
|
|
151
|
+
const expireData = provCluster.cloudCredential?.expireData;
|
|
152
|
+
|
|
153
|
+
if (expireData?.expiring) {
|
|
154
|
+
res.expiring++;
|
|
155
|
+
}
|
|
156
|
+
if (expireData?.expired) {
|
|
157
|
+
res.expired++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return res;
|
|
161
|
+
}, {
|
|
162
|
+
expiring: 0,
|
|
163
|
+
expired: 0
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
expiring: counts.expiring ? this.t('cluster.cloudCredentials.banners.expiring', { count: counts.expiring }) : '',
|
|
168
|
+
expired: counts.expired ? this.t('cluster.cloudCredentials.banners.expired', { count: counts.expired }) : '',
|
|
169
|
+
};
|
|
144
170
|
}
|
|
145
171
|
},
|
|
146
172
|
|
|
@@ -186,6 +212,17 @@ export default {
|
|
|
186
212
|
</template>
|
|
187
213
|
</Masthead>
|
|
188
214
|
|
|
215
|
+
<Banner
|
|
216
|
+
v-if="tokenExpiredData.expiring"
|
|
217
|
+
color="warning"
|
|
218
|
+
:label="tokenExpiredData.expiring"
|
|
219
|
+
/>
|
|
220
|
+
<Banner
|
|
221
|
+
v-if="tokenExpiredData.expired"
|
|
222
|
+
color="error"
|
|
223
|
+
:label="tokenExpiredData.expired"
|
|
224
|
+
/>
|
|
225
|
+
|
|
189
226
|
<ResourceTable
|
|
190
227
|
:schema="schema"
|
|
191
228
|
:rows="filteredRows"
|
|
@@ -194,6 +231,7 @@ export default {
|
|
|
194
231
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
195
232
|
:data-testid="'cluster-list'"
|
|
196
233
|
:force-update-live-and-delayed="forceUpdateLiveAndDelayed"
|
|
234
|
+
:sub-rows="true"
|
|
197
235
|
>
|
|
198
236
|
<!-- Why are state column and subrow overwritten here? -->
|
|
199
237
|
<!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
|
|
@@ -207,19 +245,32 @@ export default {
|
|
|
207
245
|
</template>
|
|
208
246
|
<template #sub-row="{fullColspan, row, keyField, componentTestid, i, onRowMouseEnter, onRowMouseLeave}">
|
|
209
247
|
<tr
|
|
210
|
-
v-if="row.stateDescription"
|
|
211
248
|
:key="row[keyField] + '-description'"
|
|
212
249
|
:data-testid="componentTestid + '-' + i + '-row-description'"
|
|
213
250
|
class="state-description sub-row"
|
|
214
251
|
@mouseenter="onRowMouseEnter"
|
|
215
252
|
@mouseleave="onRowMouseLeave"
|
|
216
253
|
>
|
|
217
|
-
<td
|
|
254
|
+
<td v-if="row.cloudCredentialWarning || row.stateDescription">
|
|
255
|
+
|
|
256
|
+
</td>
|
|
218
257
|
<td
|
|
258
|
+
v-if="row.cloudCredentialWarning || row.stateDescription"
|
|
219
259
|
:colspan="fullColspan - 1"
|
|
220
|
-
:class="{ 'text-error' : row.stateObj.error }"
|
|
221
260
|
>
|
|
222
|
-
|
|
261
|
+
<CloudCredExpired
|
|
262
|
+
v-if="row.cloudCredentialWarning"
|
|
263
|
+
:value="row.cloudCredential.expires"
|
|
264
|
+
:row="row.cloudCredential"
|
|
265
|
+
:verbose="true"
|
|
266
|
+
:class="{'mb-10': row.stateDescription}"
|
|
267
|
+
/>
|
|
268
|
+
<div
|
|
269
|
+
v-if="row.stateDescription"
|
|
270
|
+
:class="{ 'text-error' : row.stateObj.error }"
|
|
271
|
+
>
|
|
272
|
+
{{ row.stateDescription }}
|
|
273
|
+
</div>
|
|
223
274
|
</td>
|
|
224
275
|
</tr>
|
|
225
276
|
</template>
|
package/mixins/chart.js
CHANGED
|
@@ -291,6 +291,8 @@ export default {
|
|
|
291
291
|
id: `${ this.query.appNamespace }/${ this.query.appName }`,
|
|
292
292
|
});
|
|
293
293
|
|
|
294
|
+
await this.existing?.fetchValues(true);
|
|
295
|
+
|
|
294
296
|
this.mode = _EDIT;
|
|
295
297
|
} catch (e) {
|
|
296
298
|
this.mode = _CREATE;
|
|
@@ -450,10 +452,12 @@ export default {
|
|
|
450
452
|
}
|
|
451
453
|
}
|
|
452
454
|
if (existingCRDApp) {
|
|
455
|
+
await existingCRDApp.fetchValues(true);
|
|
456
|
+
|
|
453
457
|
// spec.values are any non-default values the user configured
|
|
454
458
|
// the installation form should show these, as well as any default values from the chart
|
|
455
|
-
const existingValues = clone(existingCRDApp.
|
|
456
|
-
const defaultValues = clone(existingCRDApp.
|
|
459
|
+
const existingValues = clone(existingCRDApp.values || {});
|
|
460
|
+
const defaultValues = clone(existingCRDApp.chartValues || {});
|
|
457
461
|
|
|
458
462
|
crdVersionInfo.existingValues = existingValues;
|
|
459
463
|
crdVersionInfo.allValues = merge(defaultValues, existingValues);
|
|
@@ -7,9 +7,9 @@ jest.mock('@shell/utils/clipboard', () => {
|
|
|
7
7
|
describe('class MgmtCluster', () => {
|
|
8
8
|
describe('provisioner', () => {
|
|
9
9
|
const testCases = [
|
|
10
|
-
[{ provider: 'rke', driver: 'imported' }, '
|
|
11
|
-
[{ provider: 'k3s', driver: 'K3S' }, '
|
|
12
|
-
[{ provider: 'aks', driver: 'AKS' }, '
|
|
10
|
+
[{ provider: 'rke', driver: 'imported' }, 'imported'],
|
|
11
|
+
[{ provider: 'k3s', driver: 'K3S' }, 'K3S'],
|
|
12
|
+
[{ provider: 'aks', driver: 'AKS' }, 'AKS'],
|
|
13
13
|
[{}, 'imported'],
|
|
14
14
|
];
|
|
15
15
|
|
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
|
|
2
2
|
|
|
3
3
|
describe('class ProvCluster', () => {
|
|
4
|
-
const importedClusterInfo = {
|
|
5
|
-
clusterName: 'test', provisioner: 'imported', mgmt: { spec: { gkeConfig: {} } }, spec: {}
|
|
6
|
-
};
|
|
7
|
-
const importedGkeClusterInfo = {
|
|
8
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: true } } }
|
|
9
|
-
};
|
|
10
|
-
const importedAksClusterInfo = {
|
|
11
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { aksConfig: { imported: true } } }
|
|
12
|
-
};
|
|
13
|
-
const importedEksClusterInfo = {
|
|
14
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { eksConfig: { imported: true } } }
|
|
15
|
-
};
|
|
16
|
-
const notImportedGkeClusterInfo = {
|
|
17
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: false } }, rkeConfig: {} }
|
|
18
|
-
};
|
|
19
|
-
const importedClusterInfoWithProviderForEmberParam = {
|
|
20
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { providerForEmberParam: 'import' }
|
|
21
|
-
};
|
|
22
|
-
const localClusterInfo = {
|
|
23
|
-
clusterName: 'test', provisioner: 'imported', mgmt: { isLocal: true, spec: { gkeConfig: {} } }, spec: {}
|
|
24
|
-
};
|
|
25
|
-
const doRke2Info = {
|
|
26
|
-
clusterName: 'test', provisioner: 'rke2', mgmt: { isLocal: false, providerForEmberParam: 'import' }, spec: { rkeConfig: {} }
|
|
27
|
-
};
|
|
28
|
-
|
|
29
4
|
const gkeClusterWithPrivateEndpoint = {
|
|
30
5
|
clusterName: 'test',
|
|
31
6
|
provisioner: 'GKE',
|
|
@@ -76,67 +51,6 @@ describe('class ProvCluster', () => {
|
|
|
76
51
|
});
|
|
77
52
|
});
|
|
78
53
|
|
|
79
|
-
describe('isImported', () => {
|
|
80
|
-
const testCases = [
|
|
81
|
-
[importedClusterInfo, true],
|
|
82
|
-
[importedGkeClusterInfo, true],
|
|
83
|
-
[importedAksClusterInfo, true],
|
|
84
|
-
[importedEksClusterInfo, true],
|
|
85
|
-
[notImportedGkeClusterInfo, false],
|
|
86
|
-
[importedClusterInfoWithProviderForEmberParam, true],
|
|
87
|
-
[localClusterInfo, false],
|
|
88
|
-
[doRke2Info, false],
|
|
89
|
-
[{}, false],
|
|
90
|
-
];
|
|
91
|
-
const resetMocks = () => {
|
|
92
|
-
// Clear all mock function calls:
|
|
93
|
-
jest.clearAllMocks();
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Boolean) => {
|
|
97
|
-
const cluster = new ProvCluster({ spec: clusterData.spec });
|
|
98
|
-
|
|
99
|
-
jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(
|
|
100
|
-
clusterData.mgmt
|
|
101
|
-
);
|
|
102
|
-
jest.spyOn(cluster, 'provisioner', 'get').mockReturnValue(
|
|
103
|
-
clusterData.provisioner
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
expect(cluster.isImported).toBe(expected);
|
|
107
|
-
resetMocks();
|
|
108
|
-
}
|
|
109
|
-
);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
describe('mgmt', () => {
|
|
113
|
-
const testCases = [
|
|
114
|
-
[importedClusterInfo, importedClusterInfo.mgmt],
|
|
115
|
-
[importedGkeClusterInfo, importedGkeClusterInfo.mgmt],
|
|
116
|
-
[importedAksClusterInfo, importedAksClusterInfo.mgmt],
|
|
117
|
-
[importedEksClusterInfo, importedEksClusterInfo.mgmt],
|
|
118
|
-
[notImportedGkeClusterInfo, notImportedGkeClusterInfo.mgmt],
|
|
119
|
-
[importedClusterInfoWithProviderForEmberParam, importedClusterInfoWithProviderForEmberParam.mgmt],
|
|
120
|
-
[localClusterInfo, localClusterInfo.mgmt],
|
|
121
|
-
[doRke2Info, doRke2Info.mgmt],
|
|
122
|
-
[{}, null],
|
|
123
|
-
];
|
|
124
|
-
|
|
125
|
-
const resetMocks = () => {
|
|
126
|
-
// Clear all mock function calls:
|
|
127
|
-
jest.clearAllMocks();
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Object) => {
|
|
131
|
-
const clusterMock = jest.fn(() => clusterData.mgmt);
|
|
132
|
-
const ctx = { rootGetters: { 'management/byId': clusterMock } };
|
|
133
|
-
const cluster = new ProvCluster({ status: { clusterName: clusterData.clusterName } }, ctx);
|
|
134
|
-
|
|
135
|
-
expect(cluster.mgmt).toBe(expected);
|
|
136
|
-
resetMocks();
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
|
|
140
54
|
describe('hasError', () => {
|
|
141
55
|
const conditionsWithoutError = [
|
|
142
56
|
{
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
import { CATALOG as CATALOG_ANNOTATIONS, FLEET } from '@shell/config/labels-annotations';
|
|
5
5
|
import { compare, isPrerelease, sortable } from '@shell/utils/version';
|
|
6
6
|
import { filterBy } from '@shell/utils/array';
|
|
7
|
-
import { CATALOG, MANAGEMENT, NORMAN } from '@shell/config/types';
|
|
7
|
+
import { CATALOG, MANAGEMENT, NORMAN, SECRET } from '@shell/config/types';
|
|
8
8
|
import { SHOW_PRE_RELEASE } from '@shell/store/prefs';
|
|
9
9
|
import { set } from '@shell/utils/object';
|
|
10
10
|
|
|
@@ -279,28 +279,115 @@ export default class CatalogApp extends SteveModel {
|
|
|
279
279
|
};
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
}
|
|
282
|
+
async deployedAsLegacy() {
|
|
283
|
+
await this.fetchValues();
|
|
284
|
+
|
|
285
|
+
if (this.values?.global) {
|
|
286
|
+
const { clusterName, projectName } = this.values.global;
|
|
287
|
+
|
|
288
|
+
if (clusterName && projectName) {
|
|
289
|
+
try {
|
|
290
|
+
const legacyApp = await this.$dispatch('rancher/find', {
|
|
291
|
+
type: NORMAN.APP,
|
|
292
|
+
id: `${ projectName }:${ this.metadata?.name }`,
|
|
293
|
+
opt: { url: `/v3/project/${ clusterName }:${ projectName }/apps/${ projectName }:${ this.metadata?.name }` }
|
|
294
|
+
}, { root: true });
|
|
295
|
+
|
|
296
|
+
if (legacyApp) {
|
|
297
|
+
return legacyApp;
|
|
298
|
+
}
|
|
299
|
+
} catch (e) {}
|
|
300
300
|
}
|
|
301
|
+
}
|
|
301
302
|
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* User and Chart values live in a helm secret, so fetch it (with special param)
|
|
308
|
+
*/
|
|
309
|
+
async fetchValues(force = false) {
|
|
310
|
+
if (!this.secretId) {
|
|
311
|
+
// If there's no secret id this isn't ever going to work, no need to carry on
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const haveValues = !!this._values && !!this._chartValues;
|
|
316
|
+
|
|
317
|
+
if (haveValues && !force) {
|
|
318
|
+
// If we already have the required values and we're not forced to re-fetch, no need to carry on
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await this.$dispatch('find', {
|
|
324
|
+
type: SECRET,
|
|
325
|
+
id: this.secretId,
|
|
326
|
+
opt: {
|
|
327
|
+
force: force || (!!this._secret && !haveValues), // force if explicitly requested or there's ean existing secret without the required values we have a secret without the values in (Secret has been fetched another way)
|
|
328
|
+
watch: false, // Cannot watch with custom params (they are dropped on calls made when resyncing over socket)
|
|
329
|
+
params: { includeHelmData: true }
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.error(`Cannot find values for ${ this.id } (unable to fetch)`, e); // eslint-disable-line no-console
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
get secretId() {
|
|
338
|
+
const metadata = this.metadata;
|
|
339
|
+
const secretReference = metadata.ownerReferences?.find((ow) => ow.kind.toLowerCase() === SECRET);
|
|
340
|
+
|
|
341
|
+
const secretId = secretReference?.name;
|
|
342
|
+
const secretNamespace = metadata.namespace;
|
|
343
|
+
|
|
344
|
+
if (!secretNamespace || !secretId) {
|
|
345
|
+
console.warn(`Cannot find values for ${ this.id } (cannot find related secret namespace or id)`); // eslint-disable-line no-console
|
|
346
|
+
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return `${ secretNamespace }/${ secretId }`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
get _secret() {
|
|
354
|
+
return this.secretId ? this.$getters['byId'](SECRET, this.secretId) : null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
_validateSecret(noun) {
|
|
358
|
+
if (this._secret === undefined) {
|
|
359
|
+
throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret has not been fetched via app \`fetchValues\`)`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (this._secret === null) {
|
|
363
|
+
throw new Error(`Cannot find ${ noun } for ${ this.id } (chart secret cannot or has failed to fetch) `);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The user's helm values
|
|
369
|
+
*/
|
|
370
|
+
get values() {
|
|
371
|
+
this._validateSecret('values');
|
|
372
|
+
|
|
373
|
+
return this._values;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
get _values() {
|
|
377
|
+
return this._secret?.data?.release?.config;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* The Charts default helm values
|
|
382
|
+
*/
|
|
383
|
+
get chartValues() {
|
|
384
|
+
this._validateSecret('chartValues');
|
|
385
|
+
|
|
386
|
+
return this._chartValues;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
get _chartValues() {
|
|
390
|
+
return this._secret?.data?.release?.chart?.values;
|
|
304
391
|
}
|
|
305
392
|
}
|
|
306
393
|
|