@rancher/shell 3.0.8 → 3.0.9-rc.2
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/modal.ts +38 -0
- package/apis/intf/slide-in.ts +3 -1
- package/apis/shell/__tests__/slide-in.test.ts +36 -0
- package/apis/shell/slide-in.ts +5 -1
- package/assets/styles/base/_color.scss +1 -0
- package/assets/styles/base/_typography.scss +14 -5
- package/assets/styles/themes/_light.scss +1 -1
- package/assets/styles/themes/_modern.scss +1 -1
- package/assets/translations/en-us.yaml +94 -33
- package/assets/translations/zh-hans.yaml +0 -2
- package/components/ActionMenuShell.vue +4 -4
- package/components/CodeMirror.vue +4 -3
- package/components/DetailText.vue +54 -7
- package/components/Drawer/Chrome.vue +11 -4
- package/components/Drawer/DrawerCard.vue +19 -0
- package/components/Drawer/ResourceDetailDrawer/ConfigTab.vue +3 -11
- package/components/Drawer/ResourceDetailDrawer/__tests__/ConfigTab.test.ts +2 -2
- package/components/Drawer/ResourceDetailDrawer/index.vue +3 -20
- package/components/Drawer/types.ts +1 -0
- package/components/DynamicContent/DynamicContentCloseButton.vue +2 -2
- package/components/LocaleSelector.vue +1 -1
- package/components/Markdown.vue +1 -1
- package/components/PopoverCard.vue +3 -3
- package/components/Resource/Detail/Card/ExtrasCard.vue +39 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +142 -0
- package/components/Resource/Detail/Card/StateCard/composables.ts +41 -11
- package/components/Resource/Detail/Card/StateCard/index.vue +3 -9
- package/components/Resource/Detail/Card/StateCard/types.ts +6 -0
- package/components/Resource/Detail/Card/{PodsCard → StatusCard}/index.vue +11 -10
- package/components/Resource/Detail/Card/__tests__/PodsCard.test.ts +24 -25
- package/components/Resource/Detail/Cards.vue +27 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +70 -0
- package/components/Resource/Detail/Masthead/index.vue +5 -0
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +4 -2
- package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +2 -2
- package/components/Resource/Detail/ResourceRow.types.ts +14 -0
- package/components/Resource/Detail/ResourceRow.vue +23 -35
- package/components/Resource/Detail/StatusRow.vue +5 -2
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +38 -7
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +106 -2
- package/components/Resource/Detail/TitleBar/composables.ts +2 -1
- package/components/Resource/Detail/TitleBar/index.vue +41 -6
- package/components/ResourceDetail/Masthead/__tests__/index.test.ts +49 -1
- package/components/ResourceDetail/Masthead/__tests__/latest.test.ts +85 -0
- package/components/ResourceDetail/Masthead/index.vue +1 -0
- package/components/ResourceDetail/Masthead/latest.vue +8 -1
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/Setting.vue +1 -1
- package/components/SortableTable/index.vue +25 -0
- package/components/SortableTable/selection.js +25 -12
- package/components/SortableTable/sorting.js +1 -1
- package/components/Tabbed/Tab.vue +1 -0
- package/components/Tabbed/index.vue +29 -6
- package/components/Window/ContainerShell.vue +10 -13
- package/components/fleet/FleetClusterTargets/TargetsList.vue +47 -29
- package/components/fleet/FleetClusterTargets/index.vue +82 -29
- package/components/fleet/FleetClusters.vue +26 -12
- package/components/fleet/FleetGitRepoPaths.vue +2 -2
- package/components/fleet/FleetResources.vue +14 -0
- package/components/fleet/FleetValuesFrom.vue +2 -2
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +531 -0
- package/components/fleet/__tests__/FleetClusters.test.ts +576 -0
- package/components/fleet/dashboard/ResourceDetails.vue +96 -123
- package/components/form/Conditions.vue +1 -15
- package/components/form/HookOption.vue +5 -0
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/LifecycleHooks.vue +2 -6
- package/components/form/ResourceLabeledSelect.vue +12 -1
- package/components/form/SeccompProfile.vue +113 -0
- package/components/form/Security.vue +244 -133
- package/components/form/__tests__/LabeledSelect.test.ts +1 -1
- package/components/form/__tests__/SeccompProfile.test.js +124 -0
- package/components/form/__tests__/Security.test.ts +125 -37
- package/components/formatter/Autoscaler.vue +2 -2
- package/components/formatter/FleetSummaryGraph.vue +4 -1
- package/components/nav/Group.vue +5 -0
- package/components/nav/Header.vue +3 -3
- package/components/nav/HeaderPageActionMenu.vue +1 -1
- package/components/nav/NamespaceFilter.vue +6 -6
- package/components/nav/NotificationCenter/index.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +41 -16
- package/components/nav/TopLevelMenu.vue +45 -25
- package/components/nav/WorkspaceSwitcher.vue +1 -1
- package/components/nav/__tests__/TopLevelMenu.helper.test.ts +277 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +160 -4
- package/components/templates/default.vue +0 -3
- package/components/templates/home.vue +0 -3
- package/components/templates/plain.vue +0 -3
- package/composables/useClickOutside.ts +1 -1
- package/config/product/explorer.js +1 -2
- package/config/types.js +41 -8
- package/detail/__tests__/workload.test.ts +8 -16
- package/detail/catalog.cattle.io.app.vue +6 -0
- package/detail/fleet.cattle.io.cluster.vue +6 -0
- package/detail/workload/index.vue +7 -109
- package/edit/__tests__/projectsecret.test.ts +42 -0
- package/edit/auth/__tests__/oidc.test.ts +50 -0
- package/edit/auth/oidc.vue +68 -44
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +140 -59
- package/edit/autoscaling.horizontalpodautoscaler/metrics-row.vue +41 -5
- package/edit/projectsecret.vue +29 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +89 -200
- package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +58 -17
- package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +3 -63
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +82 -14
- package/edit/workload/__tests__/index.test.ts +122 -85
- package/edit/workload/index.vue +48 -29
- package/edit/workload/mixins/workload.js +85 -32
- package/list/catalog.cattle.io.clusterrepo.vue +1 -1
- package/list/projectsecret.vue +2 -2
- package/machine-config/__tests__/vmwarevsphere.test.ts +64 -0
- package/machine-config/amazonec2.vue +2 -2
- package/machine-config/vmwarevsphere.vue +58 -4
- package/mixins/__tests__/brand.spec.ts +18 -13
- package/mixins/__tests__/chart.test.ts +63 -0
- package/mixins/chart.js +56 -51
- package/models/__tests__/catalog.cattle.io.app.test.ts +33 -0
- package/models/__tests__/workload.test.ts +333 -0
- package/models/catalog.cattle.io.app.js +8 -0
- package/models/pod.js +14 -0
- package/models/secret.js +1 -1
- package/models/workload.js +93 -27
- package/package.json +4 -4
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +91 -0
- package/pages/c/_cluster/apps/charts/install.vue +4 -4
- package/pages/c/_cluster/explorer/EventsTable.vue +2 -2
- package/pages/c/_cluster/fleet/index.vue +18 -12
- package/pages/c/_cluster/manager/hostedprovider/index.vue +1 -19
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
- package/pages/c/_cluster/uiplugins/index.vue +1 -1
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +234 -0
- package/plugins/dashboard-store/actions.js +9 -8
- package/plugins/dashboard-store/resource-class.js +97 -1
- package/plugins/steve/__tests__/revision.test.ts +84 -0
- package/plugins/steve/__tests__/steve-pagination-utils.test.ts +30 -0
- package/plugins/steve/__tests__/subscribe.spec.ts +134 -0
- package/plugins/steve/mutations.js +9 -0
- package/plugins/steve/revision.ts +26 -0
- package/plugins/steve/steve-pagination-utils.ts +6 -5
- package/plugins/steve/subscribe.js +211 -51
- package/plugins/subscribe-events.ts +2 -2
- package/rancher-components/Form/Checkbox/Checkbox.vue +13 -0
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -1
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +1 -1
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +3 -1
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -1
- package/rancher-components/Pill/RcTag/RcTag.vue +1 -1
- package/rancher-components/Pill/index.ts +4 -0
- package/rancher-components/RcButton/RcButton.test.ts +53 -9
- package/rancher-components/RcButton/RcButton.vue +217 -25
- package/rancher-components/RcButton/types.ts +27 -1
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -4
- package/rancher-components/RcDropdown/types.ts +3 -3
- package/rancher-components/RcIcon/RcIcon.test.ts +42 -0
- package/rancher-components/RcIcon/RcIcon.vue +9 -6
- package/rancher-components/RcIcon/types.ts +13 -9
- package/rancher-components/utils/status.test.ts +10 -15
- package/rancher-components/utils/status.ts +5 -6
- package/store/aws.js +18 -12
- package/store/index.js +4 -8
- package/store/type-map.utils.ts +1 -1
- package/types/kube/kube-api.ts +29 -3
- package/types/rancher/steve.api.ts +40 -0
- package/types/shell/index.d.ts +99 -0
- package/types/store/dashboard-store.types.ts +29 -7
- package/types/store/pagination.types.ts +1 -0
- package/types/store/subscribe-events.types.ts +1 -0
- package/utils/__tests__/azure.test.ts +56 -0
- package/utils/__tests__/back-off.test.ts +364 -245
- package/utils/__tests__/error.test.ts +44 -0
- package/utils/__tests__/fleet.test.ts +8 -1
- package/utils/__tests__/pagination-wrapper.test.ts +167 -0
- package/utils/__tests__/version.test.ts +55 -1
- package/utils/azure.js +12 -0
- package/utils/back-off.ts +302 -69
- package/utils/cspAdaptor.ts +32 -14
- package/utils/dynamic-content/__tests__/index.test.ts +1 -1
- package/utils/dynamic-content/__tests__/new-release.test.ts +48 -7
- package/utils/dynamic-content/__tests__/support-notice.test.ts +1 -4
- package/utils/dynamic-content/index.ts +1 -6
- package/utils/dynamic-content/new-release.ts +5 -3
- package/utils/dynamic-content/types.d.ts +0 -1
- package/utils/error.js +9 -0
- package/utils/fleet.ts +2 -2
- package/utils/inactivity.ts +2 -3
- package/utils/pagination-wrapper.ts +101 -17
- package/utils/validators/formRules/index.ts +3 -0
- package/utils/version.js +38 -0
- package/components/auth/AzureWarning.vue +0 -77
- /package/components/Resource/Detail/{Card/PodsCard/Bubble.vue → Bubble.vue} +0 -0
- /package/components/Resource/Detail/Card/{PodsCard → StatusCard}/composable.ts +0 -0
package/utils/back-off.ts
CHANGED
|
@@ -1,11 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { randomStr } from '@shell/utils/string';
|
|
2
|
+
|
|
3
|
+
type BackOffEntry<MetadataType = any> = {
|
|
3
4
|
try: number,
|
|
4
5
|
retries: number,
|
|
5
6
|
description: string,
|
|
6
|
-
metadata:
|
|
7
|
+
metadata: MetadataType,
|
|
8
|
+
execute?: {
|
|
9
|
+
timeoutId?: NodeJS.Timeout,
|
|
10
|
+
},
|
|
11
|
+
recurse?: {
|
|
12
|
+
id: string,
|
|
13
|
+
}
|
|
7
14
|
}
|
|
8
15
|
|
|
16
|
+
interface BackOffArgs<MetadataType = any> {
|
|
17
|
+
/**
|
|
18
|
+
* Unique id for the execution of this function.
|
|
19
|
+
*
|
|
20
|
+
* This will be used to delay further executions, and also to cancel it
|
|
21
|
+
*/
|
|
22
|
+
id: string,
|
|
23
|
+
/**
|
|
24
|
+
* Basic text description to use in logging
|
|
25
|
+
*/
|
|
26
|
+
description: string,
|
|
27
|
+
/**
|
|
28
|
+
* Number of executions allowed before flatly refusing to call more. Defaults to 10
|
|
29
|
+
*/
|
|
30
|
+
retries?: number,
|
|
31
|
+
/**
|
|
32
|
+
* Before calling delayedFn check if it can still run
|
|
33
|
+
*
|
|
34
|
+
* Useful for checking state after a looong delay
|
|
35
|
+
*/
|
|
36
|
+
canFn?: () => Promise<boolean>,
|
|
37
|
+
/**
|
|
38
|
+
* Call this function
|
|
39
|
+
* - if it's not already waiting to run
|
|
40
|
+
* - if it's passed canFn
|
|
41
|
+
* - if it hasn't been tried over `retries` amount
|
|
42
|
+
*
|
|
43
|
+
* The function will be increasingly (exponentially) delayed if it has previously been called
|
|
44
|
+
*/
|
|
45
|
+
delayedFn: () => Promise<any>,
|
|
46
|
+
/**
|
|
47
|
+
* Anything that might be important outside of this file (used with `getBackOff`)
|
|
48
|
+
*/
|
|
49
|
+
metadata?: MetadataType,
|
|
50
|
+
/**
|
|
51
|
+
*
|
|
52
|
+
* RESET_ON_SUCCESS
|
|
53
|
+
*/
|
|
54
|
+
mode?: ''
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const metadataToString = (metadata: any) => {
|
|
58
|
+
if (!metadata) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return JSON.stringify(metadata, (_, value) => {
|
|
63
|
+
return value === undefined ? '' : value;
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type BackOffExecuteArgs<MetadataType> = BackOffArgs<MetadataType>
|
|
68
|
+
|
|
69
|
+
export interface BackOffRecurseArgs<MetadataType> extends BackOffArgs<MetadataType> {
|
|
70
|
+
/**
|
|
71
|
+
* Should we continue to to try even if the previous attempt failed?
|
|
72
|
+
*/
|
|
73
|
+
continueOnError: (arg: any) => Promise<boolean>,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const logStyle = 'font-weight: bold; font-style: italic;';
|
|
77
|
+
const logStyleReset = 'font-weight: normal; font-style: normal;';
|
|
78
|
+
|
|
79
|
+
enum LOG_TYPE { // eslint-disable-line no-unused-vars
|
|
80
|
+
/** Aligns with `execute` method */
|
|
81
|
+
EXECUTE = 'delay', // eslint-disable-line no-unused-vars
|
|
82
|
+
/** Aligns with `recurse` method */
|
|
83
|
+
RECURSE = 'recurse', // eslint-disable-line no-unused-vars
|
|
84
|
+
}
|
|
85
|
+
type LogLevel = 'error' | 'info' | 'debug' | 'warn' | undefined;
|
|
86
|
+
type LogArgs = { id: string, status: string, description: string, metadata?: any, type: string }
|
|
87
|
+
|
|
88
|
+
const logInitialBackOffRequest = false;
|
|
89
|
+
const calcLogLevel = (iteration: number): LogLevel => {
|
|
90
|
+
if (!logInitialBackOffRequest && iteration === 0) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return 'info';
|
|
95
|
+
};
|
|
96
|
+
|
|
9
97
|
/**
|
|
10
98
|
* Helper class which handles backing off making the supplied request
|
|
11
99
|
*
|
|
@@ -16,21 +104,65 @@ class BackOff {
|
|
|
16
104
|
[id: string]: BackOffEntry
|
|
17
105
|
} = {};
|
|
18
106
|
|
|
19
|
-
private
|
|
20
|
-
|
|
107
|
+
private getLogTypeFromMap(id: string): string {
|
|
108
|
+
const entry = this.getBackOff(id);
|
|
109
|
+
|
|
110
|
+
let safeType = '';
|
|
111
|
+
|
|
112
|
+
if (!!entry?.execute) {
|
|
113
|
+
safeType = LOG_TYPE.EXECUTE;
|
|
114
|
+
} else if (!!entry?.recurse) {
|
|
115
|
+
safeType = LOG_TYPE.RECURSE;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return safeType;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private log(level: LogLevel, {
|
|
122
|
+
id, status, description, metadata, type
|
|
123
|
+
}: LogArgs, ...args: any[]) {
|
|
124
|
+
if (!level) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let safeType = type || this.getLogTypeFromMap(id);
|
|
129
|
+
|
|
130
|
+
safeType = safeType ? ` (${ safeType })` : '';
|
|
131
|
+
|
|
132
|
+
// eslint-disable-next-line no-console
|
|
133
|
+
console[level](
|
|
134
|
+
`%cBackOff${ safeType }%c... \n%cId%c: ${ id }\n%cDescription%c: ${ description }\n%cStatus%c: ${ status }\n%cMetadata%c: ${ metadataToString(metadata) }\n%cCache %c: ${ Object.keys(this.map).map((e) => `"${ e }"`).join(' + ') }`,
|
|
135
|
+
logStyle, logStyleReset,
|
|
136
|
+
logStyle, logStyleReset,
|
|
137
|
+
logStyle, logStyleReset,
|
|
138
|
+
logStyle, logStyleReset,
|
|
139
|
+
logStyle, logStyleReset,
|
|
140
|
+
logStyle, logStyleReset,
|
|
141
|
+
...args
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private logAndError(level: LogLevel, {
|
|
146
|
+
id, status, description, metadata, type
|
|
147
|
+
}: LogArgs, ...args: any[]): Promise<undefined> {
|
|
148
|
+
this.log(level, {
|
|
149
|
+
id, status, description, metadata, type
|
|
150
|
+
}, ...args);
|
|
151
|
+
|
|
152
|
+
return Promise.reject(new Error(status));
|
|
21
153
|
}
|
|
22
154
|
|
|
23
155
|
/**
|
|
24
156
|
* Get a specific back off process
|
|
25
157
|
*/
|
|
26
|
-
getBackOff(id: string): BackOffEntry {
|
|
158
|
+
public getBackOff(id: string): BackOffEntry {
|
|
27
159
|
return this.map[id];
|
|
28
160
|
}
|
|
29
161
|
|
|
30
162
|
/**
|
|
31
163
|
* Stop ALL back off processes started since the ui was loaded
|
|
32
164
|
*/
|
|
33
|
-
resetAll() {
|
|
165
|
+
public resetAll() {
|
|
34
166
|
Object.keys(this.map).forEach((id) => {
|
|
35
167
|
this.reset(id);
|
|
36
168
|
});
|
|
@@ -39,7 +171,7 @@ class BackOff {
|
|
|
39
171
|
/**
|
|
40
172
|
* Stop all back off process with a specific prefix
|
|
41
173
|
*/
|
|
42
|
-
resetPrefix(prefix:string) {
|
|
174
|
+
public resetPrefix(prefix:string) {
|
|
43
175
|
Object.keys(this.map).forEach((id) => {
|
|
44
176
|
if (id.startsWith(prefix)) {
|
|
45
177
|
this.reset(id);
|
|
@@ -50,23 +182,148 @@ class BackOff {
|
|
|
50
182
|
/**
|
|
51
183
|
* Stop a back off process with a specific id
|
|
52
184
|
*/
|
|
53
|
-
reset(id: string) {
|
|
185
|
+
public reset(id: string) {
|
|
54
186
|
const backOff: BackOffEntry = this.map[id];
|
|
55
187
|
|
|
56
|
-
if (backOff) {
|
|
57
|
-
|
|
58
|
-
|
|
188
|
+
if (!backOff) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const logType = this.getLogTypeFromMap(id);
|
|
193
|
+
|
|
194
|
+
if (backOff?.execute?.timeoutId) {
|
|
195
|
+
this.log('info', {
|
|
196
|
+
id, status: 'Stopping (cancelling active back-off)', description: backOff.description, metadata: backOff.metadata, type: logType
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
clearTimeout(backOff.execute.timeoutId);
|
|
200
|
+
}
|
|
201
|
+
const backOffTry = backOff?.try || 0;
|
|
202
|
+
const logLevel = backOffTry <= 1 ? undefined : 'debug';
|
|
59
203
|
|
|
60
|
-
|
|
204
|
+
delete this.map[id];
|
|
205
|
+
|
|
206
|
+
this.log(logLevel, {
|
|
207
|
+
id, status: 'Reset', description: backOff.description, metadata: backOff.metadata, type: logType
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
212
|
+
|
|
213
|
+
private calcDelay = (iteration: number) => {
|
|
214
|
+
// First step is immediate (0.001s)
|
|
215
|
+
// Second and others are exponential
|
|
216
|
+
// Iteration: 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
217
|
+
// Delay: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
|
|
218
|
+
return iteration === 0 ? 1 : Math.pow(iteration, 2) * 250;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
private canRecurse = async(backOffEntry: BackOffEntry, {
|
|
222
|
+
id, description, metadata, canFn = async() => true
|
|
223
|
+
}: BackOffRecurseArgs<any>) => {
|
|
224
|
+
if (!this.map[id]) {
|
|
225
|
+
// was reset, don't care now, abort
|
|
226
|
+
// could be a pagination-wrapper request with a stale revision, which can be safely ignored
|
|
227
|
+
return this.logAndError('info', {
|
|
228
|
+
id, status: 'Aborting (backoff was reset, do not continue to process)', description, metadata, type: LOG_TYPE.RECURSE
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (this.map[id].recurse?.id !== backOffEntry.recurse?.id) {
|
|
233
|
+
return this.logAndError('info', {
|
|
234
|
+
id, status: 'Aborting (stale backoff, a new one exists)', description, metadata, type: LOG_TYPE.RECURSE
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const cont = await canFn();
|
|
239
|
+
|
|
240
|
+
if (!cont) {
|
|
241
|
+
return this.logAndError('info', {
|
|
242
|
+
id, status: 'Skipping (canFn test failed)', description, metadata, type: LOG_TYPE.RECURSE
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Call a function, if it fails keep trying but with a delay (aka back off)
|
|
249
|
+
*
|
|
250
|
+
* Return the successful result, or error if reached the max number of retries
|
|
251
|
+
*
|
|
252
|
+
* @template MetadataType - Type of configuration that can be internally stored with the backoff record
|
|
253
|
+
*/
|
|
254
|
+
public async recurse<MetadataType = any, ResponseType = any>(args: BackOffRecurseArgs<MetadataType>): Promise<ResponseType | undefined> {
|
|
255
|
+
const {
|
|
256
|
+
id, description, retries = 10, delayedFn, continueOnError, metadata
|
|
257
|
+
} = args;
|
|
258
|
+
|
|
259
|
+
if (this.map[id]) {
|
|
260
|
+
return this.logAndError('info', {
|
|
261
|
+
id, status: 'Skipping (previous recurse back off process still running)', description, metadata, type: 'recurse',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
this.map[id] = {
|
|
266
|
+
try: 1,
|
|
267
|
+
retries,
|
|
268
|
+
description,
|
|
269
|
+
metadata,
|
|
270
|
+
recurse: { id: randomStr() }
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
for (let i = 0; i < retries; i++) {
|
|
274
|
+
await this.canRecurse(this.map[id], args); // Check that we can start the process
|
|
275
|
+
|
|
276
|
+
this.map[id].try = i + 1;
|
|
277
|
+
|
|
278
|
+
const delay = this.calcDelay(i);
|
|
279
|
+
const logLevel = calcLogLevel(i);
|
|
280
|
+
|
|
281
|
+
this.log(logLevel, {
|
|
282
|
+
id, status: `Delaying call (attempt ${ i + 1 }, delayed by ${ delay }ms)`, description, metadata, type: LOG_TYPE.RECURSE
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await this.sleep(delay);
|
|
286
|
+
|
|
287
|
+
await this.canRecurse(this.map[id], args); // Check that we can call the function (things could have changed after delay...)
|
|
288
|
+
|
|
289
|
+
this.log(logLevel, {
|
|
290
|
+
id, status: `Executing call`, description, metadata, type: LOG_TYPE.RECURSE
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
let res: ResponseType | undefined;
|
|
294
|
+
|
|
295
|
+
try {
|
|
296
|
+
res = await delayedFn();
|
|
297
|
+
} catch (e) {
|
|
298
|
+
const cont = await continueOnError(e);
|
|
299
|
+
|
|
300
|
+
if (!cont) {
|
|
301
|
+
this.reset(id); // Allow future calls to execute
|
|
302
|
+
|
|
303
|
+
const errorMessage = 'Failed call';
|
|
304
|
+
|
|
305
|
+
return this.logAndError('error', {
|
|
306
|
+
id, status: errorMessage, description, metadata, type: LOG_TYPE.RECURSE
|
|
307
|
+
}, e);
|
|
308
|
+
}
|
|
61
309
|
}
|
|
62
|
-
this.log('debug', id, 'Reset', backOff.description);
|
|
63
310
|
|
|
64
|
-
|
|
311
|
+
if (res) {
|
|
312
|
+
await this.canRecurse(this.map[id], args); // Check that we can return a result (things could have changed after delayedFn...)
|
|
313
|
+
|
|
314
|
+
this.reset(id); // Allow future calls to execute
|
|
315
|
+
|
|
316
|
+
this.log(logLevel, {
|
|
317
|
+
id, status: 'Successful call', description, metadata, type: LOG_TYPE.RECURSE
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
return res;
|
|
321
|
+
}
|
|
65
322
|
}
|
|
66
323
|
}
|
|
67
324
|
|
|
68
325
|
/**
|
|
69
|
-
* Call a function, but if it's recently been called delay execution aka back off
|
|
326
|
+
* Call a function, but if it's recently been called delay execution (aka back off)
|
|
70
327
|
*
|
|
71
328
|
* This can be used in a totally disjoined asynchronous way
|
|
72
329
|
*
|
|
@@ -76,91 +333,67 @@ class BackOff {
|
|
|
76
333
|
* 4. Repeat steps 2 and 3, with an exponential increasing delay
|
|
77
334
|
*
|
|
78
335
|
* This can be called repeatedly, if the previous delay is still running new requests will be ignored
|
|
336
|
+
*
|
|
337
|
+
* @template MetadataType - Type of configuration that can be internally stored with the backoff record
|
|
79
338
|
*/
|
|
80
|
-
async execute<
|
|
339
|
+
public async execute<MetadataType = any>({
|
|
81
340
|
id, description, retries = 10, delayedFn, canFn = async() => true, metadata
|
|
82
|
-
}: {
|
|
83
|
-
/**
|
|
84
|
-
* Unique id for the execution of this function.
|
|
85
|
-
*
|
|
86
|
-
* This will be used to delay further executions, and also to cancel it
|
|
87
|
-
*/
|
|
88
|
-
id: string,
|
|
89
|
-
/**
|
|
90
|
-
* Basic text description to use in logging
|
|
91
|
-
*/
|
|
92
|
-
description: string,
|
|
93
|
-
/**
|
|
94
|
-
* Number of executions allowed before flatly refusing to call more. Defaults to 10
|
|
95
|
-
*/
|
|
96
|
-
retries?: number,
|
|
97
|
-
/**
|
|
98
|
-
* Before calling delayedFn check if it can still run
|
|
99
|
-
*
|
|
100
|
-
* Useful for checking state after a looong delay
|
|
101
|
-
*/
|
|
102
|
-
canFn?: () => Promise<boolean>,
|
|
103
|
-
/**
|
|
104
|
-
* Call this function
|
|
105
|
-
* - if it's not already waiting to run
|
|
106
|
-
* - if it's passed canFn
|
|
107
|
-
* - if it hasn't been tried over `retries` amount
|
|
108
|
-
*
|
|
109
|
-
* The function will be increasingly (exponentially) delayed if it has previously been called
|
|
110
|
-
*/
|
|
111
|
-
delayedFn: () => Promise<any>,
|
|
112
|
-
/**
|
|
113
|
-
* Anything that might be important outside of this file (used with `getBackOff`)
|
|
114
|
-
*/
|
|
115
|
-
metadata?: T,
|
|
116
|
-
}): Promise<NodeJS.Timeout | undefined> {
|
|
341
|
+
}: BackOffExecuteArgs<MetadataType>): Promise<NodeJS.Timeout | undefined> {
|
|
117
342
|
const backOff: BackOffEntry = this.map[id];
|
|
118
343
|
|
|
119
344
|
const cont = await canFn();
|
|
120
345
|
|
|
121
346
|
if (!cont) {
|
|
122
|
-
this.log('info',
|
|
347
|
+
this.log('info', {
|
|
348
|
+
id, status: 'Skipping (canExecute test failed)', description, metadata, type: LOG_TYPE.EXECUTE
|
|
349
|
+
});
|
|
123
350
|
|
|
124
351
|
return undefined;
|
|
125
|
-
} else if (backOff?.timeoutId) {
|
|
126
|
-
this.log('info',
|
|
352
|
+
} else if (backOff?.execute?.timeoutId) {
|
|
353
|
+
this.log('info', {
|
|
354
|
+
id, status: 'Skipping (previous back off process still running)', description, metadata, type: LOG_TYPE.EXECUTE
|
|
355
|
+
});
|
|
127
356
|
|
|
128
|
-
return backOff
|
|
357
|
+
return backOff?.execute?.timeoutId;
|
|
129
358
|
} else {
|
|
130
359
|
const backOffTry = backOff?.try || 0;
|
|
131
360
|
|
|
132
361
|
if (backOffTry + 1 > retries) {
|
|
133
|
-
this.log('error',
|
|
362
|
+
this.log('error', {
|
|
363
|
+
id, status: 'Aborting (too many retries)', description, metadata, type: LOG_TYPE.EXECUTE
|
|
364
|
+
});
|
|
134
365
|
|
|
135
366
|
return undefined;
|
|
136
367
|
}
|
|
137
368
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// Try: 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
141
|
-
// Multiple: 1, 4, 9, 16, 25, 36, 49, 64, 81
|
|
142
|
-
// Actual Time: 0.25s, 1s, 2.25s, 4s, 6.25s, 9s, 12.25s, 16s, 20.25s
|
|
143
|
-
const delay = backOffTry === 0 ? 1 : Math.pow(backOffTry, 2) * 250;
|
|
369
|
+
const delay = this.calcDelay(backOffTry);
|
|
370
|
+
const logLevel = calcLogLevel(backOffTry);
|
|
144
371
|
|
|
145
|
-
this.log(
|
|
372
|
+
this.log(logLevel, {
|
|
373
|
+
id, status: `Delaying call (attempt ${ backOffTry + 1 }, delayed by ${ delay }ms)`, description, metadata, type: LOG_TYPE.EXECUTE
|
|
374
|
+
});
|
|
146
375
|
|
|
147
376
|
const timeout = setTimeout(async() => {
|
|
148
377
|
try {
|
|
149
|
-
this.log(
|
|
378
|
+
this.log(logLevel, {
|
|
379
|
+
id, status: `Executing call`, description, metadata, type: LOG_TYPE.EXECUTE
|
|
380
|
+
});
|
|
150
381
|
|
|
151
382
|
await delayedFn();
|
|
152
383
|
} catch (e) {
|
|
153
384
|
// Error occurred. Don't clear the map. Next time this is called we'll back off before trying ...
|
|
154
|
-
this.log('error',
|
|
385
|
+
this.log('error', {
|
|
386
|
+
id, status: 'Failed call', description, metadata, type: LOG_TYPE.EXECUTE
|
|
387
|
+
});
|
|
155
388
|
}
|
|
156
389
|
|
|
157
390
|
// Unblock future calls
|
|
158
|
-
delete this.map[id]?.timeoutId;
|
|
391
|
+
delete this.map[id]?.execute?.timeoutId;
|
|
159
392
|
}, delay);
|
|
160
393
|
|
|
161
394
|
this.map[id] = {
|
|
162
|
-
timeoutId: timeout,
|
|
163
|
-
try:
|
|
395
|
+
execute: { timeoutId: timeout },
|
|
396
|
+
try: backOff?.try ? backOff.try + 1 : 1,
|
|
164
397
|
retries,
|
|
165
398
|
description,
|
|
166
399
|
metadata
|
package/utils/cspAdaptor.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// For testing these could be changed to something like...
|
|
2
2
|
|
|
3
3
|
import { CATALOG } from '@shell/config/types';
|
|
4
|
+
import { ActionFindPageArgs, ActionFindPageTransientResponse } from '@shell/types/store/dashboard-store.types';
|
|
4
5
|
import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
|
|
5
6
|
import { VuexStore } from '@shell/types/store/vuex';
|
|
6
7
|
|
|
7
8
|
const CSP_ADAPTER_APPS = ['rancher-csp-adapter', 'rancher-csp-billing-adapter'];
|
|
8
9
|
// For testing above line could be replaced with below line...
|
|
9
|
-
// const
|
|
10
|
+
// const CSP_ADAPTER_APPS = ['rancher-webhooka', 'rancher-webhook'];
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Helpers in order to
|
|
@@ -16,27 +17,44 @@ class CspAdapterUtils {
|
|
|
16
17
|
return $store.getters[`management/paginationEnabled`]({ id: CATALOG.APP, context: 'branding' });
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
static
|
|
20
|
+
private static apps?: any[] = undefined;
|
|
21
|
+
public static resetState() {
|
|
22
|
+
this.apps = undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async fetchCspAdaptorApp($store: VuexStore): Promise<any> {
|
|
26
|
+
if (this.apps) {
|
|
27
|
+
return this.apps;
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
// For the login page, the schemas won't be loaded - we don't need the apps in this case
|
|
21
31
|
if ($store.getters['management/canList'](CATALOG.APP)) {
|
|
22
32
|
if (CspAdapterUtils.canPagination($store)) {
|
|
23
33
|
// Restrict the amount of apps we need to fetch
|
|
24
|
-
|
|
34
|
+
const opt: ActionFindPageArgs = {
|
|
35
|
+
pagination: new FilterArgs({
|
|
36
|
+
filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
|
|
37
|
+
(t) => new PaginationFilterField({
|
|
38
|
+
field: 'metadata.name',
|
|
39
|
+
value: t,
|
|
40
|
+
})
|
|
41
|
+
)),
|
|
42
|
+
}),
|
|
43
|
+
watch: false,
|
|
44
|
+
transient: true
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const resp: ActionFindPageTransientResponse = await $store.dispatch('management/findPage', {
|
|
25
48
|
type: CATALOG.APP,
|
|
26
|
-
opt
|
|
27
|
-
pagination: new FilterArgs({
|
|
28
|
-
filters: PaginationParamFilter.createMultipleFields(CSP_ADAPTER_APPS.map(
|
|
29
|
-
(t) => new PaginationFilterField({
|
|
30
|
-
field: 'metadata.name',
|
|
31
|
-
value: t,
|
|
32
|
-
})
|
|
33
|
-
)),
|
|
34
|
-
})
|
|
35
|
-
}
|
|
49
|
+
opt
|
|
36
50
|
});
|
|
51
|
+
|
|
52
|
+
this.apps = resp.data;
|
|
53
|
+
} else {
|
|
54
|
+
this.apps = await $store.dispatch('management/findAll', { type: CATALOG.APP });
|
|
37
55
|
}
|
|
38
56
|
|
|
39
|
-
return
|
|
57
|
+
return this.apps;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
return Promise.resolve([]);
|
|
@@ -7,6 +7,7 @@ import semver from 'semver';
|
|
|
7
7
|
|
|
8
8
|
describe('processReleaseVersion', () => {
|
|
9
9
|
let mockContext: Context;
|
|
10
|
+
let mockContextPrime: Context;
|
|
10
11
|
let mockDispatch: jest.Mock;
|
|
11
12
|
let mockGetters: any;
|
|
12
13
|
let mockLogger: any;
|
|
@@ -50,10 +51,24 @@ describe('processReleaseVersion', () => {
|
|
|
50
51
|
prime: false,
|
|
51
52
|
distribution: 'community',
|
|
52
53
|
},
|
|
53
|
-
settings: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
settings: { suseExtensions: [] },
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
mockContextPrime = {
|
|
58
|
+
dispatch: mockDispatch,
|
|
59
|
+
getters: mockGetters,
|
|
60
|
+
axios: {},
|
|
61
|
+
logger: mockLogger,
|
|
62
|
+
isAdmin: true,
|
|
63
|
+
config: {
|
|
64
|
+
enabled: true,
|
|
65
|
+
debug: false,
|
|
66
|
+
log: false,
|
|
67
|
+
endpoint: '',
|
|
68
|
+
prime: true,
|
|
69
|
+
distribution: 'prime',
|
|
56
70
|
},
|
|
71
|
+
settings: { suseExtensions: [] },
|
|
57
72
|
};
|
|
58
73
|
|
|
59
74
|
// Mock the utility function. Default: notification does not exist, so add it.
|
|
@@ -119,7 +134,7 @@ describe('processReleaseVersion', () => {
|
|
|
119
134
|
},
|
|
120
135
|
primaryAction: {
|
|
121
136
|
label: 'More Info',
|
|
122
|
-
target: 'https://
|
|
137
|
+
target: 'https://github.com/rancher/rancher/releases/tag/v2.12.1',
|
|
123
138
|
},
|
|
124
139
|
});
|
|
125
140
|
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1');
|
|
@@ -145,12 +160,38 @@ describe('processReleaseVersion', () => {
|
|
|
145
160
|
},
|
|
146
161
|
primaryAction: {
|
|
147
162
|
label: 'More Info',
|
|
148
|
-
target: 'https://
|
|
163
|
+
target: 'https://github.com/rancher/rancher/releases/tag/v2.13.0',
|
|
149
164
|
},
|
|
150
165
|
});
|
|
151
166
|
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.13.0');
|
|
152
167
|
});
|
|
153
168
|
|
|
169
|
+
it('should add a single new release notification for a newer major/minor version (no patch) (Prime)', async() => {
|
|
170
|
+
const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.0' }];
|
|
171
|
+
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: true };
|
|
172
|
+
|
|
173
|
+
await processReleaseVersion(mockContextPrime, releaseInfo, versionInfo);
|
|
174
|
+
|
|
175
|
+
expect(mockLogger.info).toHaveBeenCalledWith('Found a newer release: 2.13.0');
|
|
176
|
+
expect(mockLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('Also found a newer patch release'));
|
|
177
|
+
expect(mockDispatch).toHaveBeenCalledTimes(1);
|
|
178
|
+
expect(mockDispatch).toHaveBeenCalledWith('notifications/add', {
|
|
179
|
+
id: 'new-release-2.13.0',
|
|
180
|
+
level: NotificationLevel.Announcement,
|
|
181
|
+
title: 'A new Rancher release is available',
|
|
182
|
+
message: 'Rancher 2.13.0 has been released',
|
|
183
|
+
preference: {
|
|
184
|
+
key: READ_NEW_RELEASE,
|
|
185
|
+
value: '2.13.0',
|
|
186
|
+
},
|
|
187
|
+
primaryAction: {
|
|
188
|
+
label: 'More Info',
|
|
189
|
+
target: 'https://documentation.suse.com/cloudnative/rancher-manager/v2.13/en/release-notes/v2.13.0.html',
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContextPrime, 'new-release-', '2.13.0');
|
|
193
|
+
});
|
|
194
|
+
|
|
154
195
|
it('should add a multiple new releases notification when both newer patch and newer major/minor exist', async() => {
|
|
155
196
|
const releaseInfo = [{ name: '2.13.0' }, { name: '2.12.1' }, { name: '2.12.0' }];
|
|
156
197
|
const versionInfo: VersionInfo = { version: semver.coerce('2.12.0')!, isPrime: false };
|
|
@@ -171,11 +212,11 @@ describe('processReleaseVersion', () => {
|
|
|
171
212
|
},
|
|
172
213
|
primaryAction: {
|
|
173
214
|
label: 'More Info for 2.12.1',
|
|
174
|
-
target: 'https://
|
|
215
|
+
target: 'https://github.com/rancher/rancher/releases/tag/v2.12.1',
|
|
175
216
|
},
|
|
176
217
|
secondaryAction: {
|
|
177
218
|
label: 'More Info for 2.13.0',
|
|
178
|
-
target: 'https://
|
|
219
|
+
target: 'https://github.com/rancher/rancher/releases/tag/v2.13.0',
|
|
179
220
|
},
|
|
180
221
|
});
|
|
181
222
|
expect(mockRemoveMatchingNotifications).toHaveBeenCalledWith(mockContext, 'new-release-', '2.12.1-2.13.0');
|
|
@@ -52,10 +52,7 @@ describe('processSupportNotices', () => {
|
|
|
52
52
|
prime: false,
|
|
53
53
|
distribution: 'community',
|
|
54
54
|
},
|
|
55
|
-
settings: {
|
|
56
|
-
releaseNotesUrl: '',
|
|
57
|
-
suseExtensions: [],
|
|
58
|
-
},
|
|
55
|
+
settings: { suseExtensions: [] },
|
|
59
56
|
};
|
|
60
57
|
|
|
61
58
|
// Mock the utility function. Default: notification does not exist, so add it.
|
|
@@ -30,8 +30,6 @@ const LOCAL_STORAGE_UPDATE_FETCHING = 'rancher-updates-fetching'; // Local stora
|
|
|
30
30
|
|
|
31
31
|
const BACKOFFS = [1, 1, 1, 2, 2, 3, 5]; // Backoff in days for the contiguous number of errors (i.e. after 1 errors, we wait 1 day, after 3 errors, we wait 2 days, etc.)
|
|
32
32
|
|
|
33
|
-
const DEFAULT_RELEASE_NOTES_URL = 'https://github.com/rancher/rancher/releases/tag/v$version'; // Default release notes URL
|
|
34
|
-
|
|
35
33
|
/**
|
|
36
34
|
* Fetch dynamic content if needed and process it if it has changed since we last checked
|
|
37
35
|
*/
|
|
@@ -63,10 +61,7 @@ export async function fetchAndProcessDynamicContent(dispatch: Function, getters:
|
|
|
63
61
|
logger,
|
|
64
62
|
config,
|
|
65
63
|
isAdmin: isAdminUser(getters),
|
|
66
|
-
settings: {
|
|
67
|
-
releaseNotesUrl: DEFAULT_RELEASE_NOTES_URL,
|
|
68
|
-
suseExtensions: [],
|
|
69
|
-
}
|
|
64
|
+
settings: { suseExtensions: [] }
|
|
70
65
|
};
|
|
71
66
|
|
|
72
67
|
logger.debug('Read configuration', context.config);
|