@rancher/shell 0.3.12 → 0.3.14

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.
@@ -4844,6 +4844,12 @@ servicesPage:
4844
4844
  title: Add-on Config
4845
4845
  ipam:
4846
4846
  label: IPAM
4847
+ useShareIP:
4848
+ label: Use Share IP
4849
+ useIpam:
4850
+ label: Use IPAM
4851
+ shareIP:
4852
+ label: Share IP LB
4847
4853
 
4848
4854
  ips:
4849
4855
  define: Service Ports
@@ -1,13 +1,22 @@
1
1
  <script>
2
+ import { mapGetters } from 'vuex';
3
+ import semver from 'semver';
4
+
2
5
  import LabeledSelect from '@shell/components/form/LabeledSelect';
3
6
  import { _CREATE } from '@shell/config/query-params';
4
7
  import { get } from '@shell/utils/object';
5
8
  import { HCI as HCI_LABELS_ANNOTATIONS } from '@shell/config/labels-annotations';
9
+ import { SERVICE } from '@shell/config/types';
10
+ import { allHash } from '@shell/utils/promise';
6
11
 
7
12
  const HARVESTER_ADD_ON_CONFIG = [{
8
13
  variableName: 'ipam',
9
14
  key: HCI_LABELS_ANNOTATIONS.CLOUD_PROVIDER_IPAM,
10
15
  default: 'dhcp'
16
+ }, {
17
+ variableName: 'sharedService',
18
+ key: HCI_LABELS_ANNOTATIONS.PRIMARY_SERVICE,
19
+ default: ''
11
20
  }];
12
21
 
13
22
  export default {
@@ -38,6 +47,19 @@ export default {
38
47
  }
39
48
  },
40
49
 
50
+ async fetch() {
51
+ const inStore = this.$store.getters['currentProduct'].inStore;
52
+
53
+ const hash = {
54
+ rke2Versions: this.$store.dispatch('management/request', { url: '/v1-rke2-release/releases' }),
55
+ services: this.$store.dispatch(`${ inStore }/findAll`, { type: SERVICE }),
56
+ };
57
+
58
+ const res = await allHash(hash);
59
+
60
+ this.rke2Versions = res.rke2Versions;
61
+ },
62
+
41
63
  data() {
42
64
  const harvesterAddOnConfig = {};
43
65
 
@@ -45,10 +67,24 @@ export default {
45
67
  harvesterAddOnConfig[c.variableName] = this.value.metadata.annotations[c.key] || c.default;
46
68
  });
47
69
 
48
- return { ...harvesterAddOnConfig };
70
+ let showShareIP;
71
+
72
+ if (this.value.metadata.annotations[HCI_LABELS_ANNOTATIONS.PRIMARY_SERVICE]) {
73
+ showShareIP = true;
74
+ } else {
75
+ showShareIP = false;
76
+ }
77
+
78
+ return {
79
+ ...harvesterAddOnConfig,
80
+ showShareIP,
81
+ rke2Versions: {},
82
+ };
49
83
  },
50
84
 
51
85
  computed: {
86
+ ...mapGetters(['allowedNamespaces', 'namespaces', 'currentCluster']),
87
+
52
88
  ipamOptions() {
53
89
  return [{
54
90
  label: 'DHCP',
@@ -64,13 +100,71 @@ export default {
64
100
 
65
101
  return ports.filter(p => p.port && p.protocol === 'TCP').map(p => p.port) || [];
66
102
  },
103
+
104
+ serviceOptions() {
105
+ const inStore = this.$store.getters['currentProduct'].inStore;
106
+ const services = this.$store.getters[`${ inStore }/all`](SERVICE);
107
+
108
+ const namespaces = this.namespaces();
109
+
110
+ const out = services.filter((s) => {
111
+ const ingress = s?.status?.loadBalancer?.ingress || [];
112
+
113
+ return ingress.length > 0 &&
114
+ !s?.metadata?.annotations?.['cloudprovider.harvesterhci.io/primary-service'] &&
115
+ s.spec?.type === 'LoadBalancer' &&
116
+ namespaces[s.metadata.namespace];
117
+ });
118
+
119
+ return out.map(s => s.id);
120
+ },
121
+
122
+ shareIPEnabled() {
123
+ const kubernetesVersion = this.currentCluster.kubernetesVersion || '';
124
+ const kubernetesVersionExtension = this.currentCluster.kubernetesVersionExtension;
125
+
126
+ if (kubernetesVersionExtension.startsWith('+rke2')) {
127
+ const charts = ((this.rke2Versions?.data || []).find(v => v.id === kubernetesVersion) || {}).charts;
128
+ let ccmVersion = charts?.['harvester-cloud-provider']?.version || '';
129
+
130
+ if (ccmVersion.endsWith('00')) {
131
+ ccmVersion = ccmVersion.slice(0, -2);
132
+ }
133
+
134
+ return semver.satisfies(ccmVersion, '>=0.2.0');
135
+ } else {
136
+ return true;
137
+ }
138
+ },
67
139
  },
68
140
 
69
141
  methods: {
70
142
  willSave() {
143
+ const errors = [];
144
+
145
+ if (this.showShareIP) {
146
+ if (!this.sharedService) {
147
+ errors.push(this.t('validation.required', { key: this.t('servicesPage.harvester.shareIP.label') }, true));
148
+ }
149
+ }
150
+
151
+ if (errors.length > 0) {
152
+ return Promise.reject(errors);
153
+ }
154
+
71
155
  HARVESTER_ADD_ON_CONFIG.forEach((c) => {
72
156
  this.value.metadata.annotations[c.key] = String(get(this, c.variableName));
73
157
  });
158
+
159
+ if (this.showShareIP) {
160
+ delete this.value.metadata.annotations[HCI_LABELS_ANNOTATIONS.CLOUD_PROVIDER_IPAM];
161
+ } else {
162
+ delete this.value.metadata.annotations[HCI_LABELS_ANNOTATIONS.PRIMARY_SERVICE];
163
+ }
164
+ },
165
+
166
+ toggleShareIP() {
167
+ this.showShareIP = !this.showShareIP;
74
168
  },
75
169
  },
76
170
  };
@@ -81,11 +175,31 @@ export default {
81
175
  <div class="row mt-30">
82
176
  <div class="col span-6">
83
177
  <LabeledSelect
178
+ v-if="showShareIP"
179
+ v-model="sharedService"
180
+ :mode="mode"
181
+ :options="serviceOptions"
182
+ :label="t('servicesPage.harvester.shareIP.label')"
183
+ :disabled="mode === 'edit'"
184
+ />
185
+ <LabeledSelect
186
+ v-else
84
187
  v-model="ipam"
85
188
  :mode="mode"
86
189
  :options="ipamOptions"
87
190
  :label="t('servicesPage.harvester.ipam.label')"
88
191
  />
192
+ <div
193
+ v-if="mode === 'create'"
194
+ class="mt-10"
195
+ >
196
+ <a
197
+ role="button"
198
+ @click="toggleShareIP"
199
+ >
200
+ {{ showShareIP ? t('servicesPage.harvester.useIpam.label') : t('servicesPage.harvester.useShareIP.label') }}
201
+ </a>
202
+ </div>
89
203
  </div>
90
204
  </div>
91
205
  </div>
@@ -138,6 +138,7 @@ export const HCI = {
138
138
  CLOUD_PROVIDER_NAMESPACE: 'cloudprovider.harvesterhci.io/namespace',
139
139
  CLOUD_PROVIDER_NETWORK: 'cloudprovider.harvesterhci.io/network',
140
140
  CLOUD_PROVIDER_PROJECT: 'cloudprovider.harvesterhci.io/project',
141
+ PRIMARY_SERVICE: 'cloudprovider.harvesterhci.io/primary-service',
141
142
  };
142
143
 
143
144
  // Annotations that can be on management.cattle.io.cluster to configure a custom badge
@@ -59,9 +59,6 @@ jspm_packages/
59
59
  # Optional REPL history
60
60
  .node_repl_history
61
61
 
62
- # Output of 'npm pack'
63
- *.tgz
64
-
65
62
  # Yarn Integrity file
66
63
  .yarn-integrity
67
64
 
@@ -0,0 +1,46 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import Namespace from '@shell/edit/namespace.vue';
3
+ import { DefaultProps } from 'vue/types/options';
4
+ import { ExtendedVue, Vue } from 'vue/types/vue';
5
+
6
+ describe('view Namespace should', () => {
7
+ it('retrieve resource limits from project', () => {
8
+ const name = 'my project name';
9
+ const limits = 'whatever';
10
+ const project = { id: name, spec: { containerDefaultResourceLimit: limits } };
11
+ const wrapper = mount(Namespace as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>, {
12
+ propsData: {
13
+ value: {
14
+ metadata: { labels: { 'field.cattle.io/projectId': name } },
15
+ annotations: { 'field.cattle.io/containerDefaultResourceLimit': undefined },
16
+ listLocation: {},
17
+ }
18
+ },
19
+ mocks: {
20
+ $fetchState: {},
21
+ $route: {
22
+ name: 'anything',
23
+ query: { AS: 'yaml' }
24
+ },
25
+ $router: { applyQuery: {} },
26
+ $store: {
27
+ getters: {
28
+ 'i18n/t': jest.fn(),
29
+ 'management/all': () => ([project]),
30
+ currentProduct: jest.fn(),
31
+ }
32
+ }
33
+ },
34
+ stubs: {
35
+ CruResource: { template: '<div><slot></slot></div>' }, // Required to render the slot content
36
+ ContainerResourceLimit: { template: '<div data-testid="limits"></div>' }, // Ensure value to be added to component
37
+ NameNsDescription: true,
38
+ Tab: true,
39
+ }
40
+ });
41
+
42
+ const limitsUi = wrapper.find('[data-testid="limits"]');
43
+
44
+ expect(limitsUi.vm.$attrs.value).toStrictEqual(limits);
45
+ });
46
+ });
@@ -57,7 +57,7 @@ export default {
57
57
  project: null,
58
58
  projects: null,
59
59
  viewMode: _VIEW,
60
- containerResourceLimits: this.value.annotations[CONTAINER_DEFAULT_RESOURCE_LIMIT] || this.getDefaultContainerResourceLimits(projectName),
60
+ containerResourceLimits: this.value.annotations?.[CONTAINER_DEFAULT_RESOURCE_LIMIT] || this.getDefaultContainerResourceLimits(projectName),
61
61
  projectName,
62
62
  HARVESTER_TYPES,
63
63
  RANCHER_TYPES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -280,7 +280,7 @@ for d in pkg/*/ ; do
280
280
  fi
281
281
 
282
282
  # Base URL referencing assets directly from GitHub
283
- BASE_URL="assets/${pkg}"
283
+ BASE_URL="assets/"
284
284
 
285
285
  # Relative URL references assets within the container deployment
286
286
  RELATIVE_URL="plugin/"
@@ -320,6 +320,8 @@ if [ "${GITHUB_BUILD}" == "false" ]; then
320
320
 
321
321
  # Build the docker image
322
322
  ${SCRIPT_DIR}/bundle ${BASE_EXT} ${EXT_VERSION} ${REGISTRY} ${REGISTRY_ORG} ${IMAGE_PREFIX} ${PUSH}
323
+ else
324
+ rm -rf ${CHART_TEMPLATE}
323
325
  fi
324
326
 
325
327
  if [ "${GITHUB_BUILD}" == "true" ] && [ -f ${ROOT_INDEX} ]; then