@rancher/shell 1.2.2 → 1.2.5-rc.1

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.
Files changed (49) hide show
  1. package/assets/translations/en-us.yaml +8 -0
  2. package/assets/translations/zh-hans.yaml +1 -0
  3. package/cloud-credential/__tests__/harvester.test.ts +18 -0
  4. package/cloud-credential/harvester.vue +9 -1
  5. package/components/AlertTable.vue +17 -7
  6. package/components/GrafanaDashboard.vue +6 -4
  7. package/components/nav/TopLevelMenu.vue +1 -4
  8. package/config/product/explorer.js +11 -4
  9. package/config/settings.ts +9 -2
  10. package/config/types.js +1 -1
  11. package/config/uiplugins.js +2 -2
  12. package/edit/management.cattle.io.setting.vue +15 -2
  13. package/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue +8 -2
  14. package/edit/provisioning.cattle.io.cluster/__tests__/RegistryConfigs.test.ts +61 -0
  15. package/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster.ts +386 -0
  16. package/package.json +1 -1
  17. package/plugins/dashboard-store/actions.js +3 -2
  18. package/scripts/publish-shell.sh +59 -51
  19. package/scripts/test-plugins-build.sh +111 -29
  20. package/shell/types/shell/index.d.ts +2 -0
  21. package/store/catalog.js +1 -1
  22. package/store/type-map.utils.ts +44 -0
  23. package/types/store/dashboard-store.types.ts +23 -0
  24. package/types/store/vuex.d.ts +9 -0
  25. package/.DS_Store +0 -0
  26. package/creators/app/app.package.json +0 -13
  27. package/creators/app/files/.eslintignore +0 -18
  28. package/creators/app/files/.eslintrc.js +0 -173
  29. package/creators/app/files/.gitignore +0 -70
  30. package/creators/app/files/.vscode/settings.json +0 -22
  31. package/creators/app/files/babel.config.js +0 -1
  32. package/creators/app/files/tsconfig.json +0 -42
  33. package/creators/app/files/vue.config.js +0 -6
  34. package/creators/app/init +0 -101
  35. package/creators/app/package.json +0 -25
  36. package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -28
  37. package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -26
  38. package/creators/pkg/files/babel.config.js +0 -1
  39. package/creators/pkg/files/index.ts +0 -14
  40. package/creators/pkg/files/tsconfig.json +0 -53
  41. package/creators/pkg/files/vue.config.js +0 -1
  42. package/creators/pkg/init +0 -254
  43. package/creators/pkg/package.json +0 -19
  44. package/creators/pkg/pkg.package.json +0 -21
  45. package/creators/pkg/vue-shim.ts +0 -4
  46. package/creators/update/init +0 -56
  47. package/creators/update/package.json +0 -20
  48. package/creators/update/upgrade +0 -56
  49. package/types/shell/index.d.ts +0 -4310
@@ -1300,6 +1300,7 @@ cluster:
1300
1300
  placeholder: 'Namespace/Name'
1301
1301
  cluster: Imported Harvester Cluster
1302
1302
  installGuestAgent: Install guest agent
1303
+ tokenExpirationWarning: 'Warning: Harvester Cloud Credentials use an underlying authentication token that may have an expiry time - please see the following <a href="https://harvesterhci.io/kb/renew_harvester_cloud_credentials" target="_blank" rel="noopener nofollow">knowledge base article</a> for possible implications on management operations.'
1303
1304
  description:
1304
1305
  label: Cluster Description
1305
1306
  placeholder: Any text you want that better describes this cluster
@@ -1818,6 +1819,7 @@ cluster:
1818
1819
  oracleoke: Oracle OKE
1819
1820
  otc: Open Telekom Cloud
1820
1821
  other: Other
1822
+ ovhcloudmks: OVHcloud MKS
1821
1823
  packet: Equinix Metal
1822
1824
  pinganyunecs: Pinganyun ECS
1823
1825
  pnap: phoenixNAP
@@ -7128,6 +7130,9 @@ advancedSettings:
7128
7130
  'ui-default-landing': 'The default page users land on after login.'
7129
7131
  'brand': Folder name for an alternative theme defined in '/assets/brand'
7130
7132
  'hide-local-cluster': Hide the local cluster
7133
+ 'agent-tls-mode': "Rancher Certificate Verification. In `strict` mode the agents (system, cluster, fleet, etc) will only trust Rancher installations which are using a certificate signed by the CABundle in the `cacerts` setting. When the mode is system-store, the agents will trust any certificate signed by a CABundle in the operating system’s trust store."
7134
+ warnings:
7135
+ 'agent-tls-mode': 'Changing this setting will cause all agents to be redeployed.'
7131
7136
  editHelp:
7132
7137
  'ui-banners': This setting takes a JSON object containing 3 root parameters; <code>banner</code>, <code>showHeader</code>, <code>showFooter</code>. <code>banner</code> is an object containing; <code>textColor</code>, <code>background</code>, and <code>text</code>, where <code>textColor</code> and <code>background</code> are any valid CSS color value.
7133
7138
  enum:
@@ -7150,6 +7155,9 @@ advancedSettings:
7150
7155
  info: Info
7151
7156
  debug: Debug
7152
7157
  trace: Trace
7158
+ 'agent-tls-mode':
7159
+ strict: 'Strict'
7160
+ system-store: 'System Store'
7153
7161
 
7154
7162
  featureFlags:
7155
7163
  label: Feature Flags
@@ -1803,6 +1803,7 @@ cluster:
1803
1803
  oracleoke: Oracle OKE
1804
1804
  otc: Open Telekom Cloud
1805
1805
  other: 其他
1806
+ ovhcloudmks: OVHcloud MKS
1806
1807
  packet: Equinix Metal
1807
1808
  pinganyunecs: 平安云 ECS
1808
1809
  pnap: phoenixNAP
@@ -0,0 +1,18 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import HarvesterCloudCreds from '@shell/cloud-credential/harvester.vue';
3
+
4
+ const mockStore = { getters: { 'i18n/t': jest.fn() } };
5
+
6
+ describe('cloud credentials: Harvester', () => {
7
+ const wrapper = mount(HarvesterCloudCreds, {
8
+ propsData: { value: {} },
9
+ mocks: { $store: mockStore }
10
+ });
11
+
12
+ it('should display the warning banner for token expiration', async() => {
13
+ const warningBanner = wrapper.find('[data-testid="harvester-token-expiration-warning-banner"]');
14
+
15
+ expect(warningBanner.exists()).toBe(true);
16
+ expect(warningBanner.isVisible()).toBe(true);
17
+ });
18
+ });
@@ -1,12 +1,13 @@
1
1
  <script>
2
2
  import CreateEditView from '@shell/mixins/create-edit-view';
3
3
  import LabeledSelect from '@shell/components/form/LabeledSelect';
4
+ import { Banner } from '@components/Banner';
4
5
 
5
6
  import { get, set } from '@shell/utils/object';
6
7
  import { MANAGEMENT, VIRTUAL_HARVESTER_PROVIDER } from '@shell/config/types';
7
8
 
8
9
  export default {
9
- components: { LabeledSelect },
10
+ components: { LabeledSelect, Banner },
10
11
  mixins: [CreateEditView],
11
12
 
12
13
  async fetch() {
@@ -97,6 +98,13 @@ export default {
97
98
 
98
99
  <template>
99
100
  <div>
101
+ <div class="row mb-10">
102
+ <Banner
103
+ color="warning"
104
+ label-key="cluster.credential.harvester.tokenExpirationWarning"
105
+ data-testid="harvester-token-expiration-warning-banner"
106
+ />
107
+ </div>
100
108
  <div class="row mb-10">
101
109
  <div
102
110
  class="col span-6"
@@ -49,6 +49,7 @@ export default {
49
49
  ];
50
50
 
51
51
  return {
52
+ inStore: this.$store.getters['currentProduct'].inStore,
52
53
  alertManagerPoller: new Poller(
53
54
  this.loadAlertManagerEvents,
54
55
  ALERTMANAGER_POLL_RATE_MS,
@@ -69,15 +70,24 @@ export default {
69
70
  },
70
71
 
71
72
  methods: {
72
- async loadAlertManagerEvents() {
73
- const inStore = this.$store.getters['currentProduct'].inStore;
74
- const alertsEvents = await this.$store.dispatch(
75
- `${ inStore }/request`,
76
- { url: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/${ this.monitoringNamespace }/services/http:${ this.alertServiceEndpoint }:9093/proxy/api/v1/alerts` }
73
+ async fetchAlertManagerEvents(version) {
74
+ return await this.$store.dispatch(
75
+ `${ this.inStore }/request`,
76
+ { url: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/${ this.monitoringNamespace }/services/http:${ this.alertServiceEndpoint }:9093/proxy/api/${ version }/alerts` }
77
77
  );
78
+ },
79
+
80
+ async loadAlertManagerEvents() {
81
+ let alertEvents;
82
+
83
+ try {
84
+ alertEvents = await this.fetchAlertManagerEvents('v2');
85
+ } catch (err) {
86
+ alertEvents = await this.fetchAlertManagerEvents('v1').then((res) => res?.data);
87
+ }
78
88
 
79
- if (alertsEvents.data) {
80
- this.allAlerts = alertsEvents.data;
89
+ if (alertEvents) {
90
+ this.allAlerts = alertEvents;
81
91
  }
82
92
  },
83
93
 
@@ -114,10 +114,12 @@ export default {
114
114
  this.interval = setInterval(() => {
115
115
  try {
116
116
  const graphWindow = this.$refs.frame?.contentWindow;
117
- const errorElements = graphWindow.document.getElementsByClassName('alert-error');
118
- const errorCornerElements = graphWindow.document.getElementsByClassName('panel-info-corner--error');
119
- const panelInFullScreenElements = graphWindow.document.getElementsByClassName('panel-in-fullscreen');
120
- const panelContainerElements = graphWindow.document.getElementsByClassName('panel-container');
117
+
118
+ // Note. getElementsByClassName won't work, following a grafana bump class names are now unique - for example css-2qng6u-panel-container
119
+ const errorElements = graphWindow.document.querySelectorAll('[class$="alert-error');
120
+ const errorCornerElements = graphWindow.document.querySelectorAll('[class$="panel-info-corner--error');
121
+ const panelInFullScreenElements = graphWindow.document.querySelectorAll('[class$="panel-in-fullscreen');
122
+ const panelContainerElements = graphWindow.document.querySelectorAll('[class$="panel-container');
121
123
  const error = errorElements.length > 0 || errorCornerElements.length > 0;
122
124
  const loaded = panelInFullScreenElements.length > 0 || panelContainerElements.length > 0;
123
125
  const errorMessageElms = graphWindow.document.getElementsByTagName('pre');
@@ -855,7 +855,7 @@ export default {
855
855
  }
856
856
  }
857
857
 
858
- > i {
858
+ > i, > img {
859
859
  display: block;
860
860
  width: 42px;
861
861
  font-size: $icon-size;
@@ -867,9 +867,6 @@ export default {
867
867
  margin-right: 16px;
868
868
  fill: var(--link);
869
869
  }
870
- img {
871
- margin-right: 16px;
872
- }
873
870
 
874
871
  &.nuxt-link-active {
875
872
  background: var(--primary-hover-bg);
@@ -22,6 +22,7 @@ import {
22
22
  } from '@shell/config/table-headers';
23
23
 
24
24
  import { DSL } from '@shell/store/type-map';
25
+ import { configureConditionalDepaginate } from '@shell/store/type-map.utils';
25
26
 
26
27
  export const NAME = 'explorer';
27
28
 
@@ -49,7 +50,9 @@ export function init(store) {
49
50
  typeStoreMap: {
50
51
  [MANAGEMENT.PROJECT]: 'management',
51
52
  [MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING]: 'management',
52
- [MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING]: 'management'
53
+ [MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING]: 'management',
54
+ [NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING]: 'rancher',
55
+ [NORMAN.PROJECT_ROLE_TEMPLATE_BINDING]: 'rancher',
53
56
  }
54
57
  });
55
58
 
@@ -156,12 +159,16 @@ export function init(store) {
156
159
  mapGroup(/^(.*\.)?cluster\.x-k8s\.io$/, 'clusterProvisioning');
157
160
  mapGroup(/^(aks|eks|gke|rke|rke-machine-config|rke-machine|provisioning)\.cattle\.io$/, 'clusterProvisioning');
158
161
 
162
+ const dePaginateBindings = configureConditionalDepaginate({ maxResourceCount: 5000 });
163
+ const dePaginateNormanBindings = configureConditionalDepaginate({ maxResourceCount: 5000, isNorman: true }) ;
164
+
159
165
  configureType(NODE, { isCreatable: false, isEditable: true });
160
166
  configureType(WORKLOAD_TYPES.JOB, { isEditable: false, match: WORKLOAD_TYPES.JOB });
161
- configureType(MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING, { isEditable: false });
162
- configureType(MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING, { isEditable: false, depaginate: true });
167
+ configureType(MANAGEMENT.CLUSTER_ROLE_TEMPLATE_BINDING, { isEditable: false, depaginate: dePaginateBindings });
168
+ configureType(MANAGEMENT.PROJECT_ROLE_TEMPLATE_BINDING, { isEditable: false, depaginate: dePaginateBindings });
163
169
  configureType(MANAGEMENT.PROJECT, { displayName: store.getters['i18n/t']('namespace.project.label') });
164
- configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: true });
170
+ configureType(NORMAN.CLUSTER_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
171
+ configureType(NORMAN.PROJECT_ROLE_TEMPLATE_BINDING, { depaginate: dePaginateNormanBindings });
165
172
 
166
173
  configureType(EVENT, { limit: 500 });
167
174
  weightType(EVENT, -1, true);
@@ -19,7 +19,8 @@ interface GlobalSetting {
19
19
  /**
20
20
  * Function used from the form validation
21
21
  */
22
- ruleSet?: GlobalSettingRuleset[],
22
+ ruleSet?: GlobalSettingRuleset[],
23
+ warning?: string
23
24
  };
24
25
  }
25
26
 
@@ -90,8 +91,9 @@ export const SETTING = {
90
91
  FLEET_AGENT_DEFAULT_AFFINITY: 'fleet-agent-default-affinity',
91
92
  /**
92
93
  * manage rancher repositories in extensions (official, partners repos)
93
- */
94
+ */
94
95
  ADD_EXTENSION_REPOS_BANNER_DISPLAY: 'display-add-extension-repos-banner',
96
+ AGENT_TLS_MODE: 'agent-tls-mode',
95
97
  /**
96
98
  * User retention settings
97
99
  */
@@ -152,6 +154,11 @@ export const ALLOWED_SETTINGS: GlobalSetting = {
152
154
  options: ['prompt', 'in', 'out']
153
155
  },
154
156
  [SETTING.HIDE_LOCAL_CLUSTER]: { kind: 'boolean' },
157
+ [SETTING.AGENT_TLS_MODE]: {
158
+ kind: 'enum',
159
+ options: ['strict', 'system-store'],
160
+ warning: 'agent-tls-mode'
161
+ },
155
162
  };
156
163
 
157
164
  /**
package/config/types.js CHANGED
@@ -14,7 +14,7 @@ export const NORMAN = {
14
14
  ETCD_BACKUP: 'etcdbackup',
15
15
  CLUSTER: 'cluster',
16
16
  CLUSTER_TOKEN: 'clusterregistrationtoken',
17
- CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterRoleTemplateBinding',
17
+ CLUSTER_ROLE_TEMPLATE_BINDING: 'clusterroletemplatebinding',
18
18
  CLOUD_CREDENTIAL: 'cloudcredential',
19
19
  FLEET_WORKSPACES: 'fleetworkspace',
20
20
  GLOBAL_ROLE: 'globalRole',
@@ -178,7 +178,7 @@ export function isChartVersionAvailableForInstall(versionsData, returnObj = fals
178
178
 
179
179
  const parsedRancherVersion = rancherVersion.split('-')?.[0];
180
180
  const regexHashString = new RegExp('^[A-Za-z0-9]{9}$');
181
- const isRancherVersionHashString = regexHashString.test(rancherVersion);
181
+ const isRancherVersionHashStringOrHead = regexHashString.test(rancherVersion) || rancherVersion.includes('head');
182
182
  const requiredUiVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.UI_VERSION];
183
183
  const requiredKubeVersion = version.annotations?.[UI_PLUGIN_CHART_ANNOTATIONS.KUBE_VERSION];
184
184
  const versionObj = { ...version };
@@ -187,7 +187,7 @@ export function isChartVersionAvailableForInstall(versionsData, returnObj = fals
187
187
  versionObj.isCompatibleWithKubeVersion = true;
188
188
 
189
189
  // if it's a head version of Rancher, then we skip the validation and enable them all
190
- if (!isRancherVersionHashString && requiredUiVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
190
+ if (!isRancherVersionHashStringOrHead && requiredUiVersion && !semver.satisfies(parsedRancherVersion, requiredUiVersion)) {
191
191
  if (!returnObj) {
192
192
  return false;
193
193
  }
@@ -4,6 +4,7 @@ import { LabeledInput } from '@components/Form/LabeledInput';
4
4
  import LabeledSelect from '@shell/components/form/LabeledSelect';
5
5
  import CreateEditView from '@shell/mixins/create-edit-view';
6
6
  import { TextAreaAutoGrow } from '@components/Form/TextArea';
7
+ import { Banner } from '@components/Banner';
7
8
  import formRulesGenerator from '@shell/utils/validators/formRules/index';
8
9
 
9
10
  import { ALLOWED_SETTINGS, SETTING } from '@shell/config/settings';
@@ -18,7 +19,8 @@ export default {
18
19
  LabeledInput,
19
20
  LabeledSelect,
20
21
  RadioGroup,
21
- TextAreaAutoGrow
22
+ TextAreaAutoGrow,
23
+ Banner,
22
24
  },
23
25
 
24
26
  mixins: [CreateEditView, FormValidation],
@@ -63,7 +65,11 @@ export default {
63
65
 
64
66
  return factoryArg ? rule(factoryArg) : rule;
65
67
  }) : {};
66
- }
68
+ },
69
+
70
+ showWarningBanner() {
71
+ return this.setting?.warning;
72
+ },
67
73
  },
68
74
 
69
75
  methods: {
@@ -118,6 +124,13 @@ export default {
118
124
  @finish="saveSettings"
119
125
  @cancel="done"
120
126
  >
127
+ <Banner
128
+ v-if="showWarningBanner"
129
+ color="warning"
130
+ :label="t(`advancedSettings.warnings.${ setting.warning }`)"
131
+ data-testid="advanced_settings_warning_banner"
132
+ />
133
+
121
134
  <h4>{{ description }}</h4>
122
135
 
123
136
  <h5
@@ -7,6 +7,7 @@ import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthS
7
7
  import CreateEditView from '@shell/mixins/create-edit-view';
8
8
  import SecretSelector from '@shell/components/form/SecretSelector';
9
9
  import { SECRET_TYPES as TYPES } from '@shell/config/secret';
10
+ import { base64Decode, base64Encode } from '@shell/utils/crypto';
10
11
 
11
12
  export default {
12
13
  components: {
@@ -55,7 +56,7 @@ export default {
55
56
  if (configMap[hostname]) {
56
57
  configMap[hostname].insecureSkipVerify = configMap[hostname].insecureSkipVerify ?? defaultAddValue.insecureSkipVerify;
57
58
  configMap[hostname].authConfigSecretName = configMap[hostname].authConfigSecretName ?? defaultAddValue.authConfigSecretName;
58
- configMap[hostname].caBundle = configMap[hostname].caBundle ?? defaultAddValue.caBundle;
59
+ configMap[hostname].caBundle = base64Decode(configMap[hostname].caBundle ?? defaultAddValue.caBundle);
59
60
  configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? defaultAddValue.tlsSecretName;
60
61
  }
61
62
  entries.push({
@@ -94,7 +95,11 @@ export default {
94
95
  continue;
95
96
  }
96
97
 
97
- configs[h] = { ...entry };
98
+ configs[h] = {
99
+ ...entry,
100
+ caBundle: base64Encode(entry.caBundle)
101
+ };
102
+
98
103
  delete configs[h].hostname;
99
104
  }
100
105
 
@@ -174,6 +179,7 @@ export default {
174
179
 
175
180
  <LabeledInput
176
181
  v-model="row.value.caBundle"
182
+ :data-testid="`registry-caBundle-${i}`"
177
183
  class="mt-20"
178
184
  type="multiline"
179
185
  label="CA Cert Bundle"
@@ -0,0 +1,61 @@
1
+ import { mount, Wrapper } from '@vue/test-utils';
2
+ import { clone } from '@shell/utils/object';
3
+ import { _EDIT } from '@shell/config/query-params';
4
+ import { PROV_CLUSTER } from '@shell/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster';
5
+ import RegistryConfigs from '@shell/edit/provisioning.cattle.io.cluster/RegistryConfigs.vue';
6
+
7
+ describe('component: RegistryConfigs', () => {
8
+ let wrapper: Wrapper<InstanceType<typeof RegistryConfigs> & { [key: string]: any }>;
9
+
10
+ const mountOptions = {
11
+ propsData: {
12
+ value: {},
13
+ mode: _EDIT,
14
+ clusterRegisterBeforeHook: () => {}
15
+ },
16
+ stubs: {
17
+ SelectOrCreateAuthSecret: true,
18
+ SecretSelector: true,
19
+ },
20
+ mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
21
+ };
22
+
23
+ describe('key CA Cert Bundle', () => {
24
+ it('should display default key', () => {
25
+ const value = clone(PROV_CLUSTER);
26
+
27
+ value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
28
+
29
+ mountOptions.propsData.value = value;
30
+
31
+ wrapper = mount(
32
+ RegistryConfigs,
33
+ mountOptions
34
+ );
35
+
36
+ const registry = wrapper.find('[data-testid^="registry-caBundle"]').element as HTMLTextAreaElement;
37
+
38
+ expect(registry.value).toBe('foobar');
39
+ });
40
+
41
+ it('should update key in base64 format', async() => {
42
+ const value = clone(PROV_CLUSTER);
43
+
44
+ value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
45
+
46
+ mountOptions.propsData.value = value;
47
+
48
+ wrapper = mount(
49
+ RegistryConfigs,
50
+ mountOptions
51
+ );
52
+
53
+ const registry = wrapper.find('[data-testid^="registry-caBundle"]');
54
+
55
+ await registry.setValue('ssh key');
56
+ wrapper.vm.update();
57
+
58
+ expect(wrapper.emitted('updateConfigs')![0][0]['foo']['caBundle']).toBe('c3NoIGtleQ==');
59
+ });
60
+ });
61
+ });