@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.6
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/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +49 -14
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/Security.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/Header.vue +8 -1
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +3 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/table-headers.js +5 -2
- package/config/types.js +7 -0
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +79 -5
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +25 -12
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
- package/pages/c/_cluster/apps/charts/install.vue +143 -28
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +62 -15
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/auth.test.ts +18 -0
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +5 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +120 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -3,78 +3,95 @@ import { mount } from '@vue/test-utils';
|
|
|
3
3
|
import Install from '@shell/pages/c/_cluster/apps/charts/install.vue';
|
|
4
4
|
import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
const defaultStubs = {
|
|
7
|
+
Loading: true,
|
|
8
|
+
Wizard: true,
|
|
9
|
+
Banner: true,
|
|
10
|
+
Checkbox: true,
|
|
11
|
+
LabeledInput: true,
|
|
12
|
+
LabeledSelect: true,
|
|
13
|
+
NameNsDescription: true,
|
|
14
|
+
Tabbed: true,
|
|
15
|
+
Questions: true,
|
|
16
|
+
YamlEditor: true,
|
|
17
|
+
ResourceCancelModal: true,
|
|
18
|
+
UnitInput: true,
|
|
19
|
+
TypeDescription: true,
|
|
20
|
+
LazyImage: true,
|
|
21
|
+
ChartReadme: true,
|
|
22
|
+
ButtonGroup: true,
|
|
23
|
+
PrivateRegistry: true,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const defaultGetters = {
|
|
27
|
+
'catalog/inStore': 'cluster',
|
|
28
|
+
'catalog/repo': () => ({ metadata: { name: 'test-repo' } }),
|
|
29
|
+
'features/get': () => false,
|
|
30
|
+
defaultNamespace: 'default',
|
|
31
|
+
'i18n/withFallback': (key: string) => key,
|
|
32
|
+
'type-map/hasCustomChart': () => false,
|
|
33
|
+
'cluster/all': () => [],
|
|
34
|
+
'cluster/byId': () => null,
|
|
35
|
+
'management/all': () => [],
|
|
36
|
+
'prefs/get': () => {},
|
|
37
|
+
'catalog/charts': [],
|
|
38
|
+
'wm/byId': () => null,
|
|
39
|
+
'i18n/t': (key: string) => key,
|
|
40
|
+
};
|
|
13
41
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
'
|
|
23
|
-
'cluster/all': () => [],
|
|
24
|
-
'cluster/byId': () => null,
|
|
25
|
-
'management/all': () => [],
|
|
26
|
-
'prefs/get': () => {},
|
|
27
|
-
'catalog/charts': [],
|
|
28
|
-
'wm/byId': () => null,
|
|
29
|
-
'i18n/t': (key: string) => key,
|
|
42
|
+
const mountInstall = (options: {
|
|
43
|
+
data?: () => Record<string, any>;
|
|
44
|
+
getters?: Record<string, any>;
|
|
45
|
+
mocks?: Record<string, any>;
|
|
46
|
+
} = {}) => {
|
|
47
|
+
const mockStore = {
|
|
48
|
+
dispatch: jest.fn((action) => {
|
|
49
|
+
if (action === 'cluster/create') {
|
|
50
|
+
return Promise.resolve({ metadata: { namespace: '', name: '' } });
|
|
30
51
|
}
|
|
31
|
-
};
|
|
32
52
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Tabbed: true,
|
|
50
|
-
Questions: true,
|
|
51
|
-
YamlEditor: true,
|
|
52
|
-
ResourceCancelModal: true,
|
|
53
|
-
UnitInput: true,
|
|
54
|
-
TypeDescription: true,
|
|
55
|
-
LazyImage: true,
|
|
56
|
-
ChartReadme: true,
|
|
57
|
-
ButtonGroup: true,
|
|
58
|
-
}
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
}),
|
|
55
|
+
getters: {
|
|
56
|
+
...defaultGetters,
|
|
57
|
+
...options.getters
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return mount(Install, {
|
|
62
|
+
global: {
|
|
63
|
+
mocks: {
|
|
64
|
+
$store: mockStore,
|
|
65
|
+
$route: { query: {} },
|
|
66
|
+
$fetchState: { pending: false },
|
|
67
|
+
t: (key: string) => key,
|
|
68
|
+
...options.mocks
|
|
59
69
|
},
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
70
|
+
stubs: defaultStubs
|
|
71
|
+
},
|
|
72
|
+
data: options.data
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
describe('page: Install', () => {
|
|
77
|
+
it('should use version annotations for target namespace and name', async() => {
|
|
78
|
+
const wrapper = mountInstall({
|
|
79
|
+
data: () => ({
|
|
80
|
+
version: {
|
|
81
|
+
annotations: {
|
|
82
|
+
[CATALOG_ANNOTATIONS.NAMESPACE]: 'custom-ns',
|
|
83
|
+
[CATALOG_ANNOTATIONS.RELEASE_NAME]: 'custom-name',
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
chart: {
|
|
87
|
+
targetNamespace: 'wrong-ns',
|
|
88
|
+
targetName: 'wrong-name',
|
|
89
|
+
versions: []
|
|
90
|
+
},
|
|
91
|
+
query: { versionName: '1.0.0' },
|
|
92
|
+
chartValues: { global: { imagePullSecrets: [] } },
|
|
93
|
+
repo: { spec: { clientSecret: { name: 'test-secret' } } }
|
|
94
|
+
})
|
|
78
95
|
});
|
|
79
96
|
|
|
80
97
|
// Mock methods from mixins
|
|
@@ -97,18 +114,446 @@ describe('page: Install', () => {
|
|
|
97
114
|
const mockReplace = jest.fn();
|
|
98
115
|
const expectedLocation = { name: 'app-location' };
|
|
99
116
|
|
|
100
|
-
const wrapper =
|
|
117
|
+
const wrapper = mountInstall({
|
|
118
|
+
data: () => ({
|
|
119
|
+
existing: false,
|
|
120
|
+
chart: null,
|
|
121
|
+
}),
|
|
122
|
+
mocks: { $router: { replace: mockReplace } }
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
jest.spyOn((wrapper.vm as any), 'appLocation').mockReturnValue(expectedLocation);
|
|
126
|
+
|
|
127
|
+
(wrapper.vm as any).cancel();
|
|
128
|
+
|
|
129
|
+
expect(mockReplace).toHaveBeenCalledWith(expectedLocation);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('showRegistryPullSecrets', () => {
|
|
134
|
+
it('should return true when repo has defaultImagePullSecrets', () => {
|
|
135
|
+
const wrapper = mountInstall({ data: () => ({ repo: { spec: { defaultImagePullSecrets: [{ name: 'my-secret' }] } } }) });
|
|
136
|
+
|
|
137
|
+
expect(wrapper.vm.showRegistryPullSecrets).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return false when repo has no defaultImagePullSecrets', () => {
|
|
141
|
+
const wrapper = mountInstall({ data: () => ({ repo: { spec: {} } }) });
|
|
142
|
+
|
|
143
|
+
expect(wrapper.vm.showRegistryPullSecrets).toBe(false);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should return false when defaultImagePullSecrets is empty', () => {
|
|
147
|
+
const wrapper = mountInstall({ data: () => ({ repo: { spec: { defaultImagePullSecrets: [] } } }) });
|
|
148
|
+
|
|
149
|
+
expect(wrapper.vm.showRegistryPullSecrets).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe('repoDefaultPullSecretNames', () => {
|
|
154
|
+
it('should map defaultImagePullSecrets to names', () => {
|
|
155
|
+
const wrapper = mountInstall({
|
|
156
|
+
data: () => ({
|
|
157
|
+
repo: {
|
|
158
|
+
spec: {
|
|
159
|
+
defaultImagePullSecrets: [
|
|
160
|
+
{ name: 'secret-a' },
|
|
161
|
+
{ name: 'secret-b' }
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(wrapper.vm.repoDefaultPullSecretNames).toStrictEqual(['secret-a', 'secret-b']);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should filter out entries without a name', () => {
|
|
172
|
+
const wrapper = mountInstall({
|
|
173
|
+
data: () => ({
|
|
174
|
+
repo: {
|
|
175
|
+
spec: {
|
|
176
|
+
defaultImagePullSecrets: [
|
|
177
|
+
{ name: 'valid' },
|
|
178
|
+
{ name: '' },
|
|
179
|
+
{}
|
|
180
|
+
]
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(wrapper.vm.repoDefaultPullSecretNames).toStrictEqual(['valid']);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should return empty array when no defaultImagePullSecrets', () => {
|
|
190
|
+
const wrapper = mountInstall({ data: () => ({ repo: { spec: {} } }) });
|
|
191
|
+
|
|
192
|
+
expect(wrapper.vm.repoDefaultPullSecretNames).toStrictEqual([]);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('existingValuesPullSecrets', () => {
|
|
197
|
+
it('should return empty array on fresh install', () => {
|
|
198
|
+
const wrapper = mountInstall({
|
|
199
|
+
data: () => ({
|
|
200
|
+
existing: false,
|
|
201
|
+
chartValues: { global: { imagePullSecrets: ['secret-1', 'secret-2'] } }
|
|
202
|
+
})
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
expect(wrapper.vm.existingValuesPullSecrets).toStrictEqual([]);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should return imagePullSecrets from chart values on upgrade', () => {
|
|
209
|
+
const wrapper = mountInstall({
|
|
210
|
+
data: () => ({
|
|
211
|
+
existing: { metadata: { name: 'existing-release' } },
|
|
212
|
+
chartValues: { global: { imagePullSecrets: ['secret-1', 'secret-2'] } }
|
|
213
|
+
})
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
expect(wrapper.vm.existingValuesPullSecrets).toStrictEqual(['secret-1', 'secret-2']);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should filter out falsy entries', () => {
|
|
220
|
+
const wrapper = mountInstall({
|
|
221
|
+
data: () => ({
|
|
222
|
+
existing: { metadata: { name: 'existing-release' } },
|
|
223
|
+
chartValues: { global: { imagePullSecrets: ['secret-1', null, '', 'secret-2'] } }
|
|
224
|
+
})
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
expect(wrapper.vm.existingValuesPullSecrets).toStrictEqual(['secret-1', 'secret-2']);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('should return empty array when imagePullSecrets is not an array', () => {
|
|
231
|
+
const wrapper = mountInstall({
|
|
232
|
+
data: () => ({
|
|
233
|
+
existing: { metadata: { name: 'existing-release' } },
|
|
234
|
+
chartValues: { global: { imagePullSecrets: 'not-an-array' } }
|
|
235
|
+
})
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(wrapper.vm.existingValuesPullSecrets).toStrictEqual([]);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should return empty array when global has no imagePullSecrets', () => {
|
|
242
|
+
const wrapper = mountInstall({
|
|
243
|
+
data: () => ({
|
|
244
|
+
existing: { metadata: { name: 'existing-release' } },
|
|
245
|
+
chartValues: { global: {} }
|
|
246
|
+
})
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
expect(wrapper.vm.existingValuesPullSecrets).toStrictEqual([]);
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe('addGlobalValuesTo', () => {
|
|
254
|
+
it('should set imagePullSecrets when registryPullSecret is selected', () => {
|
|
255
|
+
const wrapper = mountInstall({
|
|
256
|
+
data: () => ({
|
|
257
|
+
repo: { spec: { defaultImagePullSecrets: [{ name: 'default-secret' }] } },
|
|
258
|
+
registryPullSecret: 'my-selected-secret',
|
|
259
|
+
customRegistrySetting: null,
|
|
260
|
+
currentCluster: null,
|
|
261
|
+
serverUrlSetting: { value: '' },
|
|
262
|
+
})
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
const values = { global: {} };
|
|
266
|
+
|
|
267
|
+
wrapper.vm.addGlobalValuesTo(values);
|
|
268
|
+
|
|
269
|
+
expect(values.global.imagePullSecrets).toStrictEqual(['my-selected-secret']);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should delete imagePullSecrets when showRegistryPullSecrets is true but no pullSecret selected', () => {
|
|
273
|
+
const wrapper = mountInstall({
|
|
274
|
+
data: () => ({
|
|
275
|
+
repo: { spec: { defaultImagePullSecrets: [{ name: 'default-secret' }] } },
|
|
276
|
+
registryPullSecret: null,
|
|
277
|
+
customRegistrySetting: null,
|
|
278
|
+
currentCluster: null,
|
|
279
|
+
serverUrlSetting: { value: '' },
|
|
280
|
+
})
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const values = { global: { imagePullSecrets: ['old-secret'] } };
|
|
284
|
+
|
|
285
|
+
wrapper.vm.addGlobalValuesTo(values);
|
|
286
|
+
|
|
287
|
+
expect(values.global.imagePullSecrets).toBeUndefined();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should not modify imagePullSecrets when showRegistryPullSecrets is false', () => {
|
|
291
|
+
const wrapper = mountInstall({
|
|
292
|
+
data: () => ({
|
|
293
|
+
repo: { spec: {} },
|
|
294
|
+
registryPullSecret: null,
|
|
295
|
+
customRegistrySetting: null,
|
|
296
|
+
currentCluster: null,
|
|
297
|
+
serverUrlSetting: { value: '' },
|
|
298
|
+
})
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const values = { global: { imagePullSecrets: ['existing-secret'] } };
|
|
302
|
+
|
|
303
|
+
wrapper.vm.addGlobalValuesTo(values);
|
|
304
|
+
|
|
305
|
+
expect(values.global.imagePullSecrets).toStrictEqual(['existing-secret']);
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe('finish()', () => {
|
|
310
|
+
const createFinishWrapper = (overrides: Record<string, any> = {}) => {
|
|
311
|
+
const mockDoAction = jest.fn().mockResolvedValue({
|
|
312
|
+
operationNamespace: 'ns',
|
|
313
|
+
operationName: 'op'
|
|
314
|
+
});
|
|
315
|
+
const mockActionLinkFor = jest.fn().mockReturnValue('https://rancher/v1/catalog.cattle.io.clusterrepos/rancher-charts?action=install');
|
|
316
|
+
|
|
317
|
+
const wrapper = mountInstall({
|
|
318
|
+
data: () => ({
|
|
319
|
+
repo: {
|
|
320
|
+
spec: { defaultImagePullSecrets: overrides.defaultImagePullSecrets || [] },
|
|
321
|
+
doAction: mockDoAction,
|
|
322
|
+
actionLinkFor: mockActionLinkFor,
|
|
323
|
+
waitForOperation: jest.fn().mockResolvedValue(undefined),
|
|
324
|
+
},
|
|
325
|
+
existing: overrides.existing || null,
|
|
326
|
+
skipPullSecrets: overrides.skipPullSecrets ?? false,
|
|
327
|
+
registryPullSecret: overrides.registryPullSecret ?? null,
|
|
328
|
+
customRegistrySetting: null,
|
|
329
|
+
currentCluster: null,
|
|
330
|
+
serverUrlSetting: { value: '' },
|
|
331
|
+
errors: [],
|
|
332
|
+
chart: { versions: [] },
|
|
333
|
+
version: { annotations: {} },
|
|
334
|
+
chartValues: overrides.chartValues || { global: {} },
|
|
335
|
+
value: { metadata: { namespace: 'default', name: 'test' } },
|
|
336
|
+
...overrides
|
|
337
|
+
})
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Mock mixin methods
|
|
341
|
+
jest.spyOn(wrapper.vm as any, 'createNamespaceIfNeeded').mockResolvedValue(undefined);
|
|
342
|
+
jest.spyOn(wrapper.vm as any, 'applyHooks').mockResolvedValue(overrides.hookResults || {});
|
|
343
|
+
jest.spyOn(wrapper.vm as any, 'actionInput').mockReturnValue({ errors: [], input: {} });
|
|
344
|
+
jest.spyOn(wrapper.vm as any, 'done').mockImplementation();
|
|
345
|
+
|
|
346
|
+
// Mock store dispatch for operation find
|
|
347
|
+
const store = (wrapper.vm as any).$store;
|
|
348
|
+
|
|
349
|
+
store.dispatch = jest.fn().mockResolvedValue({
|
|
350
|
+
waitForLink: jest.fn().mockResolvedValue(undefined),
|
|
351
|
+
openLogs: jest.fn(),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
wrapper, mockDoAction, mockActionLinkFor
|
|
356
|
+
};
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
it('should read created secret name from hookResults when registryPullSecret is null', async() => {
|
|
360
|
+
const { wrapper } = createFinishWrapper({
|
|
361
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
362
|
+
hookResults: { registerAuthSecret: { metadata: { name: 'hook-created-secret' } } },
|
|
363
|
+
});
|
|
364
|
+
const btnCb = jest.fn();
|
|
365
|
+
|
|
366
|
+
await wrapper.vm.finish(btnCb);
|
|
367
|
+
|
|
368
|
+
expect(wrapper.vm.registryPullSecret).toBe('hook-created-secret');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should not overwrite registryPullSecret from hookResults when already set', async() => {
|
|
372
|
+
const { wrapper } = createFinishWrapper({
|
|
373
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
374
|
+
registryPullSecret: 'already-selected',
|
|
375
|
+
hookResults: { registerAuthSecret: { metadata: { name: 'hook-created-secret' } } },
|
|
376
|
+
});
|
|
377
|
+
const btnCb = jest.fn();
|
|
378
|
+
|
|
379
|
+
await wrapper.vm.finish(btnCb);
|
|
380
|
+
|
|
381
|
+
expect(wrapper.vm.registryPullSecret).toBe('already-selected');
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('should not read hookResults when skipPullSecrets is true', async() => {
|
|
385
|
+
const { wrapper } = createFinishWrapper({
|
|
386
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
387
|
+
skipPullSecrets: true,
|
|
388
|
+
hookResults: { registerAuthSecret: { metadata: { name: 'hook-created-secret' } } },
|
|
389
|
+
});
|
|
390
|
+
const btnCb = jest.fn();
|
|
391
|
+
|
|
392
|
+
await wrapper.vm.finish(btnCb);
|
|
393
|
+
|
|
394
|
+
expect(wrapper.vm.registryPullSecret).toBeNull();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('should add skipPullSecrets query param when skipPullSecrets is true', async() => {
|
|
398
|
+
const { wrapper, mockDoAction } = createFinishWrapper({ skipPullSecrets: true });
|
|
399
|
+
const btnCb = jest.fn();
|
|
400
|
+
|
|
401
|
+
await wrapper.vm.finish(btnCb);
|
|
402
|
+
|
|
403
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
404
|
+
'install',
|
|
405
|
+
expect.anything(),
|
|
406
|
+
expect.objectContaining({ url: expect.stringContaining('skipPullSecrets=true') })
|
|
407
|
+
);
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
it('should not add skipPullSecrets query param when skipPullSecrets is false', async() => {
|
|
411
|
+
const { wrapper, mockDoAction } = createFinishWrapper({ skipPullSecrets: false });
|
|
412
|
+
const btnCb = jest.fn();
|
|
413
|
+
|
|
414
|
+
await wrapper.vm.finish(btnCb);
|
|
415
|
+
|
|
416
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
417
|
+
'install',
|
|
418
|
+
expect.anything(),
|
|
419
|
+
{}
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should use upgrade action name when existing release is present', async() => {
|
|
424
|
+
const { wrapper, mockDoAction } = createFinishWrapper({
|
|
425
|
+
existing: { metadata: { name: 'existing-release' } },
|
|
426
|
+
skipPullSecrets: true,
|
|
427
|
+
});
|
|
428
|
+
const btnCb = jest.fn();
|
|
429
|
+
|
|
430
|
+
await wrapper.vm.finish(btnCb);
|
|
431
|
+
|
|
432
|
+
expect(mockDoAction).toHaveBeenCalledWith(
|
|
433
|
+
'upgrade',
|
|
434
|
+
expect.anything(),
|
|
435
|
+
expect.objectContaining({ url: expect.stringContaining('skipPullSecrets=true') })
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
describe('fetch() pull secret pre-selection', () => {
|
|
441
|
+
const createFetchWrapper = (overrides: Record<string, any> = {}) => {
|
|
442
|
+
const existingMock = overrides.existing ? {
|
|
443
|
+
metadata: overrides.existing.metadata || { namespace: 'default', name: 'release' },
|
|
444
|
+
fetchValues: jest.fn().mockResolvedValue(undefined),
|
|
445
|
+
deployedAsLegacy: jest.fn().mockResolvedValue(false),
|
|
446
|
+
deployedAsMultiCluster: jest.fn().mockResolvedValue(false),
|
|
447
|
+
values: overrides.chartValues?.global ? overrides.chartValues : {},
|
|
448
|
+
} : null;
|
|
449
|
+
|
|
450
|
+
const wrapper = mountInstall({
|
|
451
|
+
data: () => ({
|
|
452
|
+
repo: {
|
|
453
|
+
spec: {
|
|
454
|
+
defaultImagePullSecrets: overrides.defaultImagePullSecrets || [],
|
|
455
|
+
clientSecret: { name: 'test-secret' }
|
|
456
|
+
}
|
|
457
|
+
},
|
|
458
|
+
existing: existingMock,
|
|
459
|
+
chartValues: overrides.chartValues || { global: {} },
|
|
460
|
+
chart: { versions: [] },
|
|
461
|
+
version: { annotations: {} },
|
|
462
|
+
query: { versionName: '1.0.0' },
|
|
463
|
+
})
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Mock mixin methods used during fetch
|
|
467
|
+
jest.spyOn(wrapper.vm as any, 'fetchChart').mockResolvedValue(undefined);
|
|
468
|
+
jest.spyOn(wrapper.vm as any, 'fetchAutoInstallInfo').mockResolvedValue(undefined);
|
|
469
|
+
jest.spyOn(wrapper.vm as any, 'getClusterRegistry').mockResolvedValue(undefined);
|
|
470
|
+
jest.spyOn(wrapper.vm as any, 'getGlobalRegistry').mockResolvedValue(undefined);
|
|
471
|
+
jest.spyOn(wrapper.vm as any, 'loadValuesComponent').mockResolvedValue(undefined);
|
|
472
|
+
jest.spyOn(wrapper.vm as any, 'updateStepOneReady').mockImplementation();
|
|
473
|
+
|
|
474
|
+
return wrapper;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
it('should pre-select single existing pull secret on upgrade', async() => {
|
|
478
|
+
const wrapper = createFetchWrapper({
|
|
479
|
+
existing: { metadata: { namespace: 'default', name: 'release' } },
|
|
480
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
481
|
+
chartValues: { global: { imagePullSecrets: ['existing-secret'] } },
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
await Install.fetch.call(wrapper.vm);
|
|
485
|
+
|
|
486
|
+
expect(wrapper.vm.registryPullSecret).toBe('existing-secret');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('should not pre-select when multiple existing pull secrets exist', async() => {
|
|
490
|
+
const wrapper = createFetchWrapper({
|
|
491
|
+
existing: { metadata: { namespace: 'default', name: 'release' } },
|
|
492
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
493
|
+
chartValues: { global: { imagePullSecrets: ['secret-1', 'secret-2'] } },
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await Install.fetch.call(wrapper.vm);
|
|
497
|
+
|
|
498
|
+
expect(wrapper.vm.registryPullSecret).toBeNull();
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it('should not pre-select on fresh install', async() => {
|
|
502
|
+
const wrapper = createFetchWrapper({
|
|
503
|
+
existing: null,
|
|
504
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
505
|
+
chartValues: { global: { imagePullSecrets: ['existing-secret'] } },
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
await Install.fetch.call(wrapper.vm);
|
|
509
|
+
|
|
510
|
+
expect(wrapper.vm.registryPullSecret).toBeNull();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('should not pre-select when no imagePullSecrets in chart values', async() => {
|
|
514
|
+
const wrapper = createFetchWrapper({
|
|
515
|
+
existing: { metadata: { namespace: 'default', name: 'release' } },
|
|
516
|
+
defaultImagePullSecrets: [{ name: 'repo-default' }],
|
|
517
|
+
chartValues: { global: {} },
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
await Install.fetch.call(wrapper.vm);
|
|
521
|
+
|
|
522
|
+
expect(wrapper.vm.registryPullSecret).toBeNull();
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
describe('computed properties: monitoring banners', () => {
|
|
527
|
+
const setupComponent = (existing: any, releaseName: string, componentName: string, chartName: string, installedApps: any[] = [], certified = 'rancher') => {
|
|
528
|
+
const mockStore = {
|
|
529
|
+
getters: {
|
|
530
|
+
'i18n/withFallback': () => '',
|
|
531
|
+
'catalog/inStore': 'cluster',
|
|
532
|
+
'features/get': () => false,
|
|
533
|
+
'type-map/hasCustomChart': () => false,
|
|
534
|
+
'wm/byId': () => null,
|
|
535
|
+
'i18n/t': (k: string) => k,
|
|
536
|
+
'prefs/get': () => {},
|
|
537
|
+
'management/all': () => [],
|
|
538
|
+
'cluster/all': () => [],
|
|
539
|
+
'cluster/byId': (type: string, id: string) => {
|
|
540
|
+
if (type === 'catalog.cattle.io.app') {
|
|
541
|
+
return installedApps.find((app) => app.id === id);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return null;
|
|
545
|
+
},
|
|
546
|
+
'catalog/charts': [],
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
return mount(Install as any, {
|
|
101
551
|
global: {
|
|
102
552
|
mocks: {
|
|
103
|
-
$store:
|
|
104
|
-
getters: {
|
|
105
|
-
'i18n/t': (key: string) => key,
|
|
106
|
-
'cluster/id': 'cluster-id',
|
|
107
|
-
}
|
|
108
|
-
},
|
|
553
|
+
$store: mockStore,
|
|
109
554
|
$route: { query: {} },
|
|
110
|
-
$router: { replace: mockReplace },
|
|
111
555
|
$fetchState: { pending: false },
|
|
556
|
+
t: (k: string) => k,
|
|
112
557
|
},
|
|
113
558
|
stubs: {
|
|
114
559
|
Loading: true,
|
|
@@ -131,17 +576,60 @@ describe('page: Install', () => {
|
|
|
131
576
|
},
|
|
132
577
|
data() {
|
|
133
578
|
return {
|
|
134
|
-
existing
|
|
135
|
-
|
|
579
|
+
existing,
|
|
580
|
+
version: {
|
|
581
|
+
annotations: {
|
|
582
|
+
[CATALOG_ANNOTATIONS.RELEASE_NAME]: releaseName,
|
|
583
|
+
[CATALOG_ANNOTATIONS.COMPONENT]: componentName,
|
|
584
|
+
[CATALOG_ANNOTATIONS.CERTIFIED]: certified,
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
chart: {
|
|
588
|
+
chartName,
|
|
589
|
+
versions: []
|
|
590
|
+
},
|
|
591
|
+
query: { versionName: '1.0.0' }
|
|
136
592
|
};
|
|
137
593
|
}
|
|
138
594
|
});
|
|
595
|
+
};
|
|
139
596
|
|
|
140
|
-
|
|
597
|
+
it('showMonitoringBanner should return translation key if releaseName matches rancher-monitoring (install or edit)', () => {
|
|
598
|
+
const wrapper1 = setupComponent(true, 'rancher-monitoring', '', '');
|
|
141
599
|
|
|
142
|
-
(
|
|
600
|
+
expect((wrapper1.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
143
601
|
|
|
144
|
-
|
|
602
|
+
const wrapper2 = setupComponent(true, '', 'rancher-monitoring', 'rancher-monitoring');
|
|
603
|
+
|
|
604
|
+
expect((wrapper2.vm as any).showMonitoringBanner).toBeNull();
|
|
605
|
+
|
|
606
|
+
const wrapper3 = setupComponent(false, 'rancher-monitoring', '', '');
|
|
607
|
+
|
|
608
|
+
expect((wrapper3.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.oldMonitoringChartWarning');
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
it('showMonitoringBanner should return translation key if existing is false and releaseName matches rancher-monitoring-dashboards', () => {
|
|
612
|
+
const wrapper1 = setupComponent(false, 'rancher-monitoring-dashboards', '', '');
|
|
613
|
+
|
|
614
|
+
expect((wrapper1.vm as any).showMonitoringBanner).toBe('catalog.install.steps.basics.newMonitoringChartWarning');
|
|
615
|
+
|
|
616
|
+
const wrapper2 = setupComponent(false, '', 'rancher-monitoring-dashboards', 'rancher-monitoring-dashboards');
|
|
617
|
+
|
|
618
|
+
expect((wrapper2.vm as any).showMonitoringBanner).toBeNull();
|
|
619
|
+
|
|
620
|
+
const wrapper3 = setupComponent(true, 'rancher-monitoring-dashboards', '', '');
|
|
621
|
+
|
|
622
|
+
expect((wrapper3.vm as any).showMonitoringBanner).toBeNull();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('showMonitoringBanner should return null when the chart is not Rancher-certified, even if the release name matches', () => {
|
|
626
|
+
const oldChartThirdParty = setupComponent(false, 'rancher-monitoring', '', '', [], 'partner');
|
|
627
|
+
|
|
628
|
+
expect((oldChartThirdParty.vm as any).showMonitoringBanner).toBeNull();
|
|
629
|
+
|
|
630
|
+
const newChartThirdParty = setupComponent(false, 'rancher-monitoring-dashboards', '', '', [], '');
|
|
631
|
+
|
|
632
|
+
expect((newChartThirdParty.vm as any).showMonitoringBanner).toBeNull();
|
|
145
633
|
});
|
|
146
634
|
});
|
|
147
635
|
});
|