Skip to content

Commit 23c0cdb

Browse files
authoredJan 9, 2025··
Fix rke2 addon validation (#13017)
* add codemirror error reporting wire in codemirror validation to rke2 addon config update addon config validation status when list of available addons changes * added e2e test for addon config validation; * account for codemirror being used with no options
1 parent 4085c86 commit 23c0cdb

File tree

8 files changed

+99
-6
lines changed

8 files changed

+99
-6
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import ComponentPo from '@/cypress/e2e/po/components/component.po';
2+
import YamlEditorPo from '~/cypress/e2e/po/components/yaml-editor.po';
3+
4+
export default class AddonConfigPo extends ComponentPo {
5+
constructor(selector = '.dashboard-root') {
6+
super(selector);
7+
}
8+
9+
yamlEditor() :YamlEditorPo {
10+
return new YamlEditorPo(this.self().find('[data-testid="addon-yaml-editor"]'));
11+
}
12+
}

‎cypress/e2e/po/components/create-edit-view.po.ts

+4
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ export default class CreateEditViewPo extends ComponentPo {
1717
nextPage() {
1818
return new AsyncButtonPo(this.self().find('.cru-resource-footer .role-primary')).click();
1919
}
20+
21+
saveButtonPo() :AsyncButtonPo {
22+
return new AsyncButtonPo(this.self().find('.cru-resource-footer .role-primary'));
23+
}
2024
}

‎cypress/e2e/po/edit/provisioning.cattle.io.cluster/create/cluster-create-rke2-custom.po.ts

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import ClusterManagerCreatePagePo from '@/cypress/e2e/po/edit/provisioning.cattl
44
import TabbedPo from '@/cypress/e2e/po/components/tabbed.po';
55
import RegistriesTabPo from '@/cypress/e2e/po/components/registries-tab.po';
66
import NetworkTabPo from '@/cypress/e2e/po/components/network-tab.po';
7+
import AddonConfigPo from '@/cypress/e2e/po/components/addon-config.po';
78

89
/**
910
* Create page for an RKE2 custom cluster
@@ -44,4 +45,8 @@ export default class ClusterManagerCreateRke2CustomPagePo extends ClusterManager
4445
network(): NetworkTabPo {
4546
return new NetworkTabPo();
4647
}
48+
49+
calicoAddonConfig(): AddonConfigPo {
50+
return new AddonConfigPo();
51+
}
4752
}

‎cypress/e2e/tests/pages/manager/cluster-manager.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,31 @@ describe('Cluster Manager', { testIsolation: 'off', tags: ['@manager', '@adminUs
311311
editCreatedClusterPage.nameNsDescription().description().self().should('have.value', rke2CustomName);
312312
});
313313

314+
it('will disbable saving if an addon config has invalid data', () => {
315+
clusterList.goTo();
316+
317+
clusterList.checkIsCurrentPage();
318+
clusterList.createCluster();
319+
320+
createRKE2ClusterPage.waitForPage();
321+
322+
createRKE2ClusterPage.rkeToggle().set('RKE2/K3s');
323+
324+
createRKE2ClusterPage.selectCustom(0);
325+
326+
createRKE2ClusterPage.nameNsDescription().name().set('abc');
327+
328+
createRKE2ClusterPage.clusterConfigurationTabs().clickTabWithSelector('#rke2-calico');
329+
330+
createRKE2ClusterPage.resourceDetail().createEditView().saveButtonPo().expectToBeEnabled();
331+
332+
createRKE2ClusterPage.calicoAddonConfig().yamlEditor().input().set('badvalue: -');
333+
createRKE2ClusterPage.resourceDetail().createEditView().saveButtonPo().expectToBeDisabled();
334+
335+
createRKE2ClusterPage.calicoAddonConfig().yamlEditor().input().set('goodvalue: yay');
336+
createRKE2ClusterPage.resourceDetail().createEditView().saveButtonPo().expectToBeEnabled();
337+
});
338+
314339
it('can view cluster YAML editor', () => {
315340
clusterList.goTo();
316341
clusterList.list().actionMenu(rke2CustomName).getMenuItem('Edit YAML').click();

‎shell/components/CodeMirror.vue

+28-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { _EDIT, _VIEW } from '@shell/config/query-params';
55
export default {
66
name: 'CodeMirror',
77
8-
emits: ['onReady', 'onInput', 'onChanges', 'onFocus'],
8+
emits: ['onReady', 'onInput', 'onChanges', 'onFocus', 'validationChanged'],
99
1010
props: {
1111
/**
@@ -39,6 +39,7 @@ export default {
3939
codeMirrorRef: null,
4040
loaded: false,
4141
removeKeyMapBox: false,
42+
hasLintErrors: false,
4243
};
4344
},
4445
@@ -65,6 +66,7 @@ export default {
6566
foldGutter: true,
6667
styleSelectedText: true,
6768
showCursorWhenSelecting: true,
69+
autocorrect: false,
6870
};
6971
7072
if (this.asTextArea) {
@@ -76,6 +78,11 @@ export default {
7678
7779
Object.assign(out, this.options);
7880
81+
// parent components control lint with a boolean; if linting is enabled, we need to override that boolean with a custom error handler to wire lint errors into dashboard validation
82+
if (this.options?.lint) {
83+
out.lint = { onUpdateLinting: this.handleLintErrors };
84+
}
85+
7986
return out;
8087
},
8188
@@ -104,7 +111,25 @@ export default {
104111
}
105112
},
106113
114+
watch: {
115+
hasLintErrors(neu) {
116+
this.$emit('validationChanged', !neu);
117+
}
118+
},
119+
107120
methods: {
121+
/**
122+
* Codemirror yaml linting uses js-yaml parse
123+
* it does not distinguish between warnings and errors so we will treat all yaml lint messages as errors
124+
* other codemirror linters (eg json) will report from, to, severity where severity may be 'warning' or 'error'
125+
* only 'error' level linting will trigger a validation event from this component
126+
*/
127+
handleLintErrors(diagnostics = []) {
128+
const hasLintErrors = diagnostics.filter((d) => !d.severity || d.severity === 'error').length > 0;
129+
130+
this.hasLintErrors = hasLintErrors;
131+
},
132+
108133
focus() {
109134
if ( this.$refs.codeMirrorRef ) {
110135
this.$refs.codeMirrorRef.cminstance.focus();
@@ -118,6 +143,8 @@ export default {
118143
},
119144
120145
onReady(codeMirrorRef) {
146+
this.$emit('validationChanged', true);
147+
121148
this.$nextTick(() => {
122149
codeMirrorRef.refresh();
123150
this.codeMirrorRef = codeMirrorRef;

‎shell/components/YamlEditor.vue

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const EDITOR_MODES = {
1313
};
1414
1515
export default {
16-
emits: ['update:value', 'newObject', 'onInput', 'onReady', 'onChanges'],
16+
emits: ['update:value', 'newObject', 'onInput', 'onReady', 'onChanges', 'validationChanged'],
1717
1818
components: {
1919
CodeMirror,
@@ -236,6 +236,7 @@ export default {
236236
@onInput="onInput"
237237
@onReady="onReady"
238238
@onChanges="onChanges"
239+
@validationChanged="$emit('validationChanged', $event)"
239240
/>
240241
<FileDiff
241242
v-else

‎shell/edit/provisioning.cattle.io.cluster/rke2.vue

+13-2
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export default {
248248
busy: false,
249249
machinePoolValidation: {}, // map of validation states for each machine pool
250250
machinePoolErrors: {},
251+
addonConfigValidation: {}, // validation state of each addon config (boolean of whether codemirror's yaml lint passed)
251252
allNamespaces: [],
252253
extensionTabs: getApplicableExtensionEnhancements(this, ExtensionPoint.TAB, TabLocation.CLUSTER_CREATE_RKE2, this.$route, this),
253254
labelForAddon
@@ -797,7 +798,9 @@ export default {
797798
// and in all of the validation statuses for each machine pool
798799
Object.values(this.machinePoolValidation).forEach((v) => (base = base && v));
799800
800-
return validRequiredPools && base;
801+
const hasAddonConfigErrors = Object.values(this.addonConfigValidation).filter((v) => v === false).length > 0;
802+
803+
return validRequiredPools && base && !hasAddonConfigErrors;
801804
},
802805
currentCluster() {
803806
if (this.mode === _EDIT) {
@@ -1565,6 +1568,8 @@ export default {
15651568
* 2) We're ready to cache any values the user provides for each addon
15661569
*/
15671570
async initAddons() {
1571+
this.addonConfigValidation = {};
1572+
15681573
for (const chartName of this.addonNames) {
15691574
const entry = this.chartVersions[chartName];
15701575
@@ -2133,7 +2138,11 @@ export default {
21332138
}
21342139
}
21352140
}
2136-
}
2141+
},
2142+
2143+
addonConfigValidationChanged(configName, isValid) {
2144+
this.addonConfigValidation[configName] = isValid;
2145+
},
21372146
}
21382147
};
21392148
</script>
@@ -2430,6 +2439,7 @@ export default {
24302439
:label="labelForAddon($store, v.name, false)"
24312440
:weight="9"
24322441
:showHeader="false"
2442+
:error="addonConfigValidation[v.name]===false"
24332443
@active="showAddons(v.name)"
24342444
>
24352445
<AddOnConfig
@@ -2444,6 +2454,7 @@ export default {
24442454
@update:value="$emit('input', $event)"
24452455
@update-questions="syncChartValues"
24462456
@update-values="updateValues"
2457+
@validationChanged="e => addonConfigValidationChanged(v.name, e)"
24472458
/>
24482459
</Tab>
24492460

‎shell/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue

+10-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { labelForAddon } from '@shell/utils/cluster';
77
import { _EDIT } from '@shell/config/query-params';
88
99
export default {
10-
emits: ['additional-manifest-changed', 'update-questions', 'update-values'],
10+
emits: ['additional-manifest-changed', 'update-questions', 'update-values', 'validationChanged'],
1111
1212
components: {
1313
Banner,
@@ -60,7 +60,13 @@ export default {
6060
isEdit() {
6161
return this.mode === _EDIT;
6262
}
63-
}
63+
},
64+
65+
methods: {
66+
handleValidationChanged(e) {
67+
this.$emit('validationChanged', e);
68+
}
69+
},
6470
};
6571
</script>
6672

@@ -91,12 +97,14 @@ export default {
9197
<YamlEditor
9298
v-else
9399
ref="yaml-values"
100+
data-testid="addon-yaml-editor"
94101
:value="initYamlEditor(addonVersion.name)"
95102
:scrolling="true"
96103
:as-object="true"
97104
:editor-mode="mode === 'view' ? 'VIEW_CODE' : 'EDIT_CODE'"
98105
:hide-preview-buttons="true"
99106
@update:value="$emit('update-values', addonVersion.name, $event)"
107+
@validationChanged="handleValidationChanged"
100108
/>
101109
<div class="spacer" />
102110
</div>

0 commit comments

Comments
 (0)
Please sign in to comment.