+
+
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index aa88746..435df30 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -89,6 +89,22 @@ export default defineComponent({
}
},
+ newComponentType(variableDef: ClusterClassVariable, idx: number) {
+ const ref = `${ variableDef.name }-input`;
+ const nextDef = this.variableDefinitions[idx + 1];
+
+ if (!nextDef) {
+ return false;
+ }
+ const nextRef = `${ nextDef.name }-input`;
+
+ const nextComponent = this.$refs?.[nextRef]?.[0]?.componentForType;
+ const currentComponent = this.$refs?.[ref]?.[0]?.componentForType;
+
+ console.log(this.$refs?.[ref]?.[0]);
+
+ return nextComponent && currentComponent && (nextComponent?.name !== currentComponent?.name);
+ },
},
});
@@ -97,14 +113,17 @@ export default defineComponent({
- updateVariables(e, variableDef)"
- @validation-passed="e=>updateErrors(e, variableDef)"
- />
+
+ updateVariables(e, variableDef)"
+ @validation-passed="e=>updateErrors(e, variableDef)"
+ />
+
+
@@ -116,10 +135,18 @@ export default defineComponent({
flex-wrap: wrap;
&>*{
- flex: 1 0 20%;
+ flex: 0 0 23.25%;
margin: 0 1.75% 10px 0;
max-width: 23.25%;
- align-self: center;
+ &::v-deep.wider{
+ flex: 0 0 48.25%;
+ max-width: 48.25%;
+ }
+ &.row-break {
+ flex: 1 0 100%;
+ max-width: 100%;
+ margin: 10px 0px 0px 0px;
+ }
}
}
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
new file mode 100644
index 0000000..e17e48e
--- /dev/null
+++ b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pkg/capi/util/validators.ts b/pkg/capi/util/validators.ts
index 424aebc..b376140 100644
--- a/pkg/capi/util/validators.ts
+++ b/pkg/capi/util/validators.ts
@@ -44,7 +44,7 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
out.push((val: string) => val && val.length < Number(minLength) ? t('validation.minLength', { key, min: minLength }) : undefined);
}
if (maxLength) {
- out.push((val: string) => val && val.length > Number(maxLength) ? t('validation.minLength', { key, max: maxLength }) : undefined);
+ out.push((val: string) => val && val.length > Number(maxLength) ? t('validation.maxLength', { key, max: maxLength }) : undefined);
}
if (maxItems) {
out.push((val: any[]) => val && val.length > maxItems ? t('validation.maxItems', { key, maxItems }) : undefined);
From da8dac3b48ac2e2a11570b0624ffa99044597ef4 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Mon, 18 Dec 2023 14:12:41 -0800
Subject: [PATCH 09/23] refactor error handling
---
pkg/capi/components/CCVariables/Variable.vue | 11 +++++----
pkg/capi/components/CCVariables/index.vue | 24 ++++++++------------
pkg/capi/edit/cluster.x-k8s.io.cluster.vue | 19 +++++-----------
3 files changed, 21 insertions(+), 33 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index 4257fbe..80ee191 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -31,6 +31,12 @@ export default defineComponent({
}
},
+ created() {
+ if (!this.isValid) {
+ this.$emit('validation-passed', false);
+ }
+ },
+
computed: {
componentForType() {
const { type } = this.schema;
@@ -86,10 +92,6 @@ export default defineComponent({
});
},
- isMultiSelect() {
- return this.componentForType === LabeledSelect && this.schema.type === 'array' && typeof this.variableOptions?.[0] !== 'object';
- },
-
validationRules() {
const out = openAPIV3SchemaValidators(this.$store.getters['i18n/t'], { key: this.variable.name }, this.schema);
@@ -140,7 +142,6 @@ export default defineComponent({
:required="variable.required"
:title="variable.name"
:options="variableOptions"
- :multiple="isMultiSelect"
:rules="validationRules"
:type="schema.type === 'number' || schema.type === 'integer' ? 'number' : 'text'"
@input="setValue"
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 435df30..32dd433 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -27,11 +27,11 @@ export default defineComponent({
},
data() {
- return { errorMap: {} as {[key:string]: boolean} };
+ return { errorCount: 0 };
},
watch: {
- hasValidationErrors: {
+ errorCount: {
handler: debounce(function(neu) {
this.$emit('validation-passed', !neu);
}, 5),
@@ -57,11 +57,7 @@ export default defineComponent({
computed: {
variableDefinitions() {
return this.clusterClass?.spec?.variables || [];
- },
-
- hasValidationErrors() {
- return !!Object.keys(this.errorMap).length;
- },
+ }
},
methods: {
@@ -81,11 +77,11 @@ export default defineComponent({
this.$emit('input', out);
},
- updateErrors(isValid: boolean, variableDef: ClusterClassVariable) {
- if (isValid && this.errorMap[variableDef.name]) {
- this.$delete(this.errorMap, variableDef.name);
- } else if (!isValid && !this.errorMap[variableDef.name]) {
- this.$set(this.errorMap, variableDef.name, true);
+ updateErrors(isValid: boolean) {
+ if (!isValid) {
+ this.errorCount++;
+ } else {
+ this.errorCount--;
}
},
@@ -101,8 +97,6 @@ export default defineComponent({
const nextComponent = this.$refs?.[nextRef]?.[0]?.componentForType;
const currentComponent = this.$refs?.[ref]?.[0]?.componentForType;
- console.log(this.$refs?.[ref]?.[0]);
-
return nextComponent && currentComponent && (nextComponent?.name !== currentComponent?.name);
},
},
@@ -120,7 +114,7 @@ export default defineComponent({
:variable="variableDef"
:value="valueFor(variableDef)"
@input="e=>updateVariables(e, variableDef)"
- @validation-passed="e=>updateErrors(e, variableDef)"
+ @validation-passed="updateErrors"
/>
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
index e17e48e..0579a67 100644
--- a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
+++ b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
@@ -29,28 +29,21 @@ export default defineComponent({
},
data() {
- return { clusterClass: null, variables: [] };
+ return {
+ clusterClass: null, variables: [], variablesValid: true
+ };
},
- // computed: {
- // variables: {
- // get() {
- // return this.value?.spec?.topology?.variables || [];
- // },
- // set(neu) {
- // this.$set(this.value, 'spec.topology.variables', neu);
- // }
- // }
- // },
-
});
+
Is valid: {{ variablesValid }}
+
-
+ variablesValid=e" />
From 1423371058e0df5a6671cd17c34ba83e7d3b8359 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Mon, 18 Dec 2023 14:44:38 -0800
Subject: [PATCH 10/23] remove test file
---
pkg/capi/edit/cluster.x-k8s.io.cluster.vue | 50 ----------------------
1 file changed, 50 deletions(-)
delete mode 100644 pkg/capi/edit/cluster.x-k8s.io.cluster.vue
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
deleted file mode 100644
index 0579a67..0000000
--- a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
Is valid: {{ variablesValid }}
-
-
-
- variablesValid=e" />
-
-
-
-
From ae0d883140da0bf3f2917d205d491eb5057a7e6d Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Wed, 20 Dec 2023 09:32:29 -0800
Subject: [PATCH 11/23] re-set variables when clusterclass changes
---
pkg/capi/components/CCVariables/index.vue | 67 ++++++++++++++++------
pkg/capi/edit/cluster.x-k8s.io.cluster.vue | 58 +++++++++++++++++++
pkg/capi/util/validators.ts | 49 ++++++++++++++++
3 files changed, 157 insertions(+), 17 deletions(-)
create mode 100644 pkg/capi/edit/cluster.x-k8s.io.cluster.vue
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 32dd433..038c98c 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -35,23 +35,14 @@ export default defineComponent({
handler: debounce(function(neu) {
this.$emit('validation-passed', !neu);
}, 5),
- }
+ },
+ variableDefinitions(neu, old) {
+ this.updateVariableDefaults(neu, old);
+ },
},
created() {
- const out = [...this.value];
-
- this.variableDefinitions.forEach((def: ClusterClassVariable) => {
- const defaultValue = def.schema.openAPIV3Schema.default;
-
- if (!out.find((variable: any) => {
- return variable.name === def.name;
- }) && defaultValue) {
- out.push({ name: def.name, value: defaultValue });
- }
- });
-
- this.$emit('input', out);
+ this.updateVariableDefaults(this.variableDefinitions, []);
},
computed: {
@@ -77,6 +68,47 @@ export default defineComponent({
this.$emit('input', out);
},
+ /**
+ * When the cluster class changes, update the variables array:
+ * remove any variables not defined in the new cluster class
+ * if a variable is defined in both cluster classes, clear out the old default
+ * set default values from the new cluster class definitions
+ * if a variable is defined in both old and new cluster classes, and the user has set its value to something other than the old default, preserve that
+ */
+ updateVariableDefaults(neu: ClusterClassVariable[], old: ClusterClassVariable[]) {
+ const out = [...this.value].reduce((acc: CapiClusterVariable[], existingVar: CapiClusterVariable) => {
+ const neuDef = (neu || []).find(n => n.name === existingVar.name);
+
+ if (!neuDef) {
+ return acc;
+ }
+ const oldDefault = (old || []).find(d => d.name === existingVar.name)?.schema?.openAPIV3Schema?.default;
+
+ if (oldDefault && existingVar.value === oldDefault) {
+ delete existingVar.value;
+ }
+ const newDefault = neuDef.schema?.openAPIV3Schema?.default;
+
+ if (newDefault && !existingVar.value) {
+ existingVar.value = newDefault;
+ }
+ acc.push(existingVar);
+
+ return acc;
+ }, []);
+
+ neu.forEach((def) => {
+ const newDefault = def.schema?.openAPIV3Schema?.default;
+
+ if (newDefault && !out.find(v => v.name === def.name)) {
+ out.push({ name: def.name, value: newDefault });
+ }
+ });
+
+ this.errorCount = 0;
+ this.$emit('input', out);
+ },
+
updateErrors(isValid: boolean) {
if (!isValid) {
this.errorCount++;
@@ -86,6 +118,7 @@ export default defineComponent({
},
newComponentType(variableDef: ClusterClassVariable, idx: number) {
+ console.log('calculating new component type');
const ref = `${ variableDef.name }-input`;
const nextDef = this.variableDefinitions[idx + 1];
@@ -105,18 +138,18 @@ export default defineComponent({
-
+
updateVariables(e, variableDef)"
@validation-passed="updateErrors"
/>
-
+
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
new file mode 100644
index 0000000..4fd6d84
--- /dev/null
+++ b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
Is valid: {{ variablesValid }}
+
+
+
+ variablesValid=e" />
+
+
+
+
diff --git a/pkg/capi/util/validators.ts b/pkg/capi/util/validators.ts
index b376140..ee7717e 100644
--- a/pkg/capi/util/validators.ts
+++ b/pkg/capi/util/validators.ts
@@ -1,5 +1,54 @@
import { Validator, ValidationOptions } from '@shell/utils/validators/formRules';
import { Translation } from '@shell/types/t';
+
+// const stringFormats = {
+// // this is a mongodb id - requires library to validate?
+// // "bsonobjectid", // bson object ID
+
+// // "uri", // an URI as parsed by Golang net/url.ParseRequestURI
+// // "email", // an email address as parsed by Golang net/mail.ParseAddress
+// // use wildcardHostname
+// "hostname", // a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
+
+// // use utils/string/isIpv4
+// "ipv4", // an IPv4 IP as parsed by Golang net.ParseIP
+
+// // "ipv6", // an IPv6 IP as parsed by Golang net.ParseIP
+
+// //use isValidCIDR
+// "cidr", // a CIDR as parsed by Golang net.ParseCIDR
+
+// //use isValidMac
+// "mac", // a MAC address as parsed by Golang net.ParseMAC
+
+// // use toLowerCase() instead of mode modifier
+// "uuid", // an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
+// "uuid3", // an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
+// "uuid4", // an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
+// "uuid5", // an UUID6 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
+
+// //use https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s13.html
+// "isbn", // an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
+// "isbn10", // an ISBN10 number string like "0321751043"
+// "isbn13", // an ISBN13 number string like "978-0321751041"
+
+// "creditcard", // a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
+// "ssn", // a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
+// "hexcolor", // an hexadecimal color code like "#FFFFFF", following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
+
+// // /^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/
+// "rgbcolor", // an RGB color code like rgb like "rgb(255,255,2559"
+
+// //use base64Decode?
+// "byte", // base64 encoded binary data
+
+// "password", // any kind of string
+
+// "date", // a date string like "2006-01-02" as defined by full-date in RFC3339
+// "duration", // a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
+// "datetime", // a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339}
+// }
+
/**
*
* @param t this.$store.getters[i18n/t]
From c069fb2e1636133b21a15339d73180618d2d0ed7 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Wed, 20 Dec 2023 12:33:27 -0800
Subject: [PATCH 12/23] fix spacer element visibility when cluster class
changes
---
pkg/capi/components/CCVariables/Variable.vue | 19 +++----
pkg/capi/components/CCVariables/index.vue | 52 +++++++++++---------
2 files changed, 38 insertions(+), 33 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index 80ee191..2359b4f 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -43,28 +43,28 @@ export default defineComponent({
let out = null;
if (this.variableOptions) {
- out = { component: LabeledSelect, name: 'LabeledSelect' };
+ out = { component: LabeledSelect, name: 'text-var' };
} else {
switch (type) {
case 'object':
- out = { component: KeyValue, name: 'KeyValue' };
+ out = { component: KeyValue, name: 'keyvalue-var' };
break;
case 'array':
- out = { component: ArrayList, name: 'ArrayList' };
+ out = { component: ArrayList, name: 'arraylist-var' };
break;
case 'string':
- out = { component: LabeledInput, name: 'LabeledInput' };
+ out = { component: LabeledInput, name: 'text-var' };
break;
case 'integer':
- out = { component: LabeledInput, name: 'LabeledInput' };
+ out = { component: LabeledInput, name: 'text-var' };
break;
case 'number':
- out = { component: LabeledInput, name: 'LabeledInput' };
+ out = { component: LabeledInput, name: 'text-var' };
break;
case 'boolean':
- out = { component: Checkbox, name: 'Checkbox' };
+ out = { component: Checkbox, name: 'checkbox-var' };
break;
default:
@@ -109,7 +109,7 @@ export default defineComponent({
},
widerComponent() {
- return this.componentForType?.name === 'ArrayList' || this.componentForType?.name === 'KeyValue';
+ return this.componentForType?.name === 'arraylist-var' || this.componentForType?.name === 'keyvalue-var';
}
},
@@ -131,7 +131,7 @@ export default defineComponent({
-
+
From f74e8681b47d95608721ff0df0f44615a0b4402a Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Wed, 20 Dec 2023 15:38:28 -0800
Subject: [PATCH 13/23] update required validator to account for a required
boolean
---
pkg/capi/components/CCVariables/Variable.vue | 6 ++++--
pkg/capi/components/CCVariables/index.vue | 17 ++++++++++++-----
pkg/capi/util/validators.ts | 2 ++
3 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index 2359b4f..9aba92f 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -9,7 +9,7 @@ import LabeledSelect from '@shell/components/form/LabeledSelect';
import formRulesGenerator, { Validator } from '@shell/utils/validators/formRules';
import type { ClusterClassVariable } from '../../types/clusterClass';
-import { openAPIV3SchemaValidators } from '../../util/validators';
+import { isDefined, openAPIV3SchemaValidators } from '../../util/validators';
export default defineComponent({
name: 'CCVariable',
@@ -98,7 +98,9 @@ export default defineComponent({
const required = this.variable?.required;
if (required) {
- out.push(formRulesGenerator(this.$store.getters['i18n/t'], { key: this.variable.name }).required as Validator);
+ // out.push(formRulesGenerator(this.$store.getters['i18n/t'], { key: this.variable.name }).required as Validator);
+
+ out.push(val => !isDefined(val) ? this.$store.getters['i18n/t']('validation.required', { key: this.variable.name }) : null);
}
return out;
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 6d1c64e..7d14795 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -5,6 +5,7 @@ import type { PropType } from 'vue';
import { randomStr } from '@shell/utils/string';
import { ClusterClassVariable } from '../../types/clusterClass';
import type { CapiClusterVariable } from '../../types/cluster.x-k8s.io.cluster';
+import { isDefined } from '../../util/validators';
import Variable from './Variable.vue';
export default defineComponent({
@@ -90,12 +91,15 @@ export default defineComponent({
}
const oldDefault = (old || []).find(d => d.name === existingVar.name)?.schema?.openAPIV3Schema?.default;
- if (oldDefault && existingVar.value === oldDefault) {
+ if (isDefined(oldDefault) && existingVar.value === oldDefault) {
delete existingVar.value;
}
- const newDefault = neuDef.schema?.openAPIV3Schema?.default;
+ let newDefault = neuDef.schema?.openAPIV3Schema?.default;
- if (newDefault && !existingVar.value) {
+ if (neuDef.schema?.openAPIV3Schema?.type === 'boolean' && !newDefault) {
+ newDefault = false;
+ }
+ if (isDefined(newDefault) && !existingVar.value) {
existingVar.value = newDefault;
}
acc.push(existingVar);
@@ -104,9 +108,12 @@ export default defineComponent({
}, []);
neu.forEach((def) => {
- const newDefault = def.schema?.openAPIV3Schema?.default;
+ let newDefault = def.schema?.openAPIV3Schema?.default;
- if (newDefault && !out.find(v => v.name === def.name)) {
+ if (def.schema?.openAPIV3Schema?.type === 'boolean' && !newDefault) {
+ newDefault = false;
+ }
+ if (isDefined(newDefault) && !out.find(v => v.name === def.name)) {
out.push({ name: def.name, value: newDefault });
}
});
diff --git a/pkg/capi/util/validators.ts b/pkg/capi/util/validators.ts
index ee7717e..d7a4803 100644
--- a/pkg/capi/util/validators.ts
+++ b/pkg/capi/util/validators.ts
@@ -117,3 +117,5 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
return out;
};
+
+export const isDefined = (val: any) => val || val === false;
From b30c156aa6da2020eb36ce8c91b715924f1e994f Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Thu, 21 Dec 2023 16:05:49 -0800
Subject: [PATCH 14/23] add machine-scoped ccvariables
---
pkg/capi/components/CCVariables/index.vue | 60 ++++++++++++++++++-
pkg/capi/edit/cluster.x-k8s.io.cluster.vue | 69 ++++++++++++++++++++--
2 files changed, 122 insertions(+), 7 deletions(-)
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 7d14795..1d27480 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -20,11 +20,25 @@ export default defineComponent({
},
//
.spec.topology.variables
+ // OR .spec.topology.workers.machineDeployments[].variables.overrides
+ // OR .spec.topology.workers.machinePools[].variables.overrides
value: {
type: Array as PropType>,
default: () => {
return [];
}
+ },
+
+ // if this and machinePoolClass are empty, ALL variables will be shown
+ // only 1 of machinePoolClass and machineDeploymentClass should be set
+ machineDeploymentClass: {
+ type: String,
+ default: null
+ },
+
+ machinePoolClass: {
+ type: String,
+ default: null
}
},
@@ -53,9 +67,53 @@ export default defineComponent({
computed: {
variableDefinitions() {
- return this.clusterClass?.spec?.variables || [];
+ const allVariableDefinitions = this.clusterClass?.spec?.variables || [];
+
+ if (!this.machineDeploymentClass && !this.machinePoolClass) {
+ return allVariableDefinitions;
+ }
+ const variableNames = this.machineScopedJsonPatches.reduce((names, patch) => {
+ const valueFromVariable = patch?.valueFrom?.variable;
+
+ if (!valueFromVariable) {
+ return names;
+ }
+
+ const parsedName = valueFromVariable.split(/\.|\[/)[0];
+
+ names.push(parsedName);
+
+ // the value here could be a field or index of the variable, defined . or [i]
+ return names;
+ }, []);
+
+ return allVariableDefinitions.filter(v => variableNames.includes(v.name));
},
+ machineScopedJsonPatches() {
+ if (!this.machineDeploymentClass && !this.machinePoolClass) {
+ return [];
+ }
+ const out = [] as any[];
+ const matchName = this.machineDeploymentClass || this.machinePoolClass;
+ const matchKey = this.machineDeploymentClass ? 'machineDeploymentClass' : 'machinePoolClass';
+
+ const patches = this.clusterClass?.spec?.patches || [];
+
+ patches.forEach((p) => {
+ const definitions = p?.definitions || [];
+
+ definitions.forEach((definition) => {
+ const matchMachines = definition?.selector?.matchResources?.[matchKey]?.names || [];
+
+ if (matchMachines.includes(matchName)) {
+ out.push(...definition.jsonPatches);
+ }
+ });
+ });
+
+ return out;
+ },
},
methods: {
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
index 4fd6d84..ed3158f 100644
--- a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
+++ b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
@@ -3,6 +3,7 @@ import { defineComponent } from 'vue';
import Tabbed from '@shell/components/Tabbed';
import Tab from '@shell/components/Tabbed/Tab';
import LabeledSelect from '@shell/components/form/LabeledSelect';
+import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
import { CAPI } from '../types/capi';
import CCVariables from '../components/CCVariables';
@@ -11,7 +12,7 @@ const TEST_CC_ID = 'default/quickstart-more-variables';
export default defineComponent({
name: 'EditCapiCluster',
components: {
- CCVariables, Tabbed, Tab, LabeledSelect
+ CCVariables, Tabbed, Tab, LabeledSelect, LabeledInput
},
props: {
value: {
@@ -22,12 +23,16 @@ export default defineComponent({
}
},
async fetch() {
- this.$set(this.value, 'spec', { topology: { variables: [] } });
+ this.$set(this.value, 'spec', { topology: { variables: [], workers: { machineDeployments: [], machinePools: [] } } });
this.clusterClasses = await this.$store.dispatch('management/findAll', { type: CAPI.CLUSTER_CLASS });
},
data() {
return {
- clusterClassId: null, clusterClasses: [], variables: [], variablesValid: true
+ clusterClassId: null,
+ clusterClasses: [],
+ variables: [],
+ variablesValid: true,
+ selectedMachineDeployment: ''
};
},
@@ -38,8 +43,22 @@ export default defineComponent({
return { label: cc.metadata.name, value: cc.id };
});
},
+
clusterClass() {
return this.clusterClassId && this.clusterClasses ? this.clusterClasses.find(cc => cc.id === this.clusterClassId) : null;
+ },
+
+ machineDeploymentOptions() {
+ return (this.clusterClass?.spec?.workers?.machineDeployments || []).map((d: any) => d.class);
+ }
+ },
+ methods: {
+ addDeployment() {
+ const idx = (this.value.spec.topology.workers.machineDeployments || []).length;
+
+ this.value.spec.topology.workers.machineDeployments.push({
+ class: this.selectedMachineDeployment, name: `machine-${ idx }`, variables: { overrides: [] }
+ });
}
}
});
@@ -48,11 +67,49 @@ export default defineComponent({
Is valid: {{ variablesValid }}
-
+
-
+
variablesValid=e" />
-
+
+
+
+
+
+
+
+
+
+ {{ md.class }} Variable Overrides
+
+
+ variablesValid=e"
+ />
+
+
From dff4855d41b6c9627750203b1213daa80970d7b4 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Fri, 22 Dec 2023 08:48:14 -0800
Subject: [PATCH 15/23] exclude patches using builtin vars
---
pkg/capi/components/CCVariables/index.vue | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 1d27480..07a125e 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -53,6 +53,7 @@ export default defineComponent({
this.$emit('validation-passed', !neu);
}, 5),
},
+
variableDefinitions(neu, old) {
this.updateVariableDefaults(neu, old);
this.$nextTick(() => {
@@ -79,15 +80,17 @@ export default defineComponent({
return names;
}
+ // the value here could be a field or index of the variable, defined . or [i]
const parsedName = valueFromVariable.split(/\.|\[/)[0];
- names.push(parsedName);
+ if (parsedName !== 'builtin') {
+ names.push(parsedName);
+ }
- // the value here could be a field or index of the variable, defined . or [i]
return names;
}, []);
- return allVariableDefinitions.filter(v => variableNames.includes(v.name));
+ return allVariableDefinitions.filter((v: ClusterClassVariable) => variableNames.includes(v.name));
},
machineScopedJsonPatches() {
@@ -103,7 +106,7 @@ export default defineComponent({
patches.forEach((p) => {
const definitions = p?.definitions || [];
- definitions.forEach((definition) => {
+ definitions.forEach((definition: any) => {
const matchMachines = definition?.selector?.matchResources?.[matchKey]?.names || [];
if (matchMachines.includes(matchName)) {
From f6469e87e443b876504ea20523aed965ec02bd55 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Fri, 22 Dec 2023 10:46:35 -0800
Subject: [PATCH 16/23] update list component validation, required validator
---
pkg/capi/components/CCVariables/Variable.vue | 40 +++++++++++++++++---
pkg/capi/util/validators.ts | 3 +-
2 files changed, 37 insertions(+), 6 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index 9aba92f..bd1d0f8 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -107,10 +107,22 @@ export default defineComponent({
},
isValid() {
- return !this.validationRules.find((rule: Validator) => !!rule(this.value));
+ return !this.validationErrors.length;
},
- widerComponent() {
+ validationErrors() {
+ return this.validationRules.reduce((errs: string[], rule: Validator) => {
+ const message = rule(this.value);
+
+ if (message) {
+ errs.push(message);
+ }
+
+ return errs;
+ }, []);
+ },
+
+ listComponent() {
return this.componentForType?.name === 'arraylist-var' || this.componentForType?.name === 'keyvalue-var';
}
},
@@ -133,7 +145,7 @@ export default defineComponent({
-
+
+ >
+
+
+ {{ variable.name }}
+
+
+
+
+
+
@@ -155,4 +176,13 @@ export default defineComponent({
.align-center {
align-self: 'center'
}
+.input-label{
+ color: var(--input-label);
+ margin-bottom: 5px;
+ display: block;
+ width:100%;
+ .icon-warning{
+ color: var(--error)
+ }
+}
diff --git a/pkg/capi/util/validators.ts b/pkg/capi/util/validators.ts
index d7a4803..b932b8b 100644
--- a/pkg/capi/util/validators.ts
+++ b/pkg/capi/util/validators.ts
@@ -1,5 +1,6 @@
import { Validator, ValidationOptions } from '@shell/utils/validators/formRules';
import { Translation } from '@shell/types/t';
+import isEmpty from 'lodash/isEmpty';
// const stringFormats = {
// // this is a mongodb id - requires library to validate?
@@ -118,4 +119,4 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
return out;
};
-export const isDefined = (val: any) => val || val === false;
+export const isDefined = (val: any) => (val || val === false) && !isEmpty(val);
From 4ffe7310ad9c2aeb050970caeec3b4849394516a Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Tue, 2 Jan 2024 17:17:38 -0800
Subject: [PATCH 17/23] add string format validation
---
pkg/capi/components/CCVariables/Variable.vue | 13 ++-
pkg/capi/components/CCVariables/index.vue | 1 +
pkg/capi/l10n/en-us.yaml | 1 +
pkg/capi/util/validators.ts | 103 ++++++++++---------
4 files changed, 62 insertions(+), 56 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index bd1d0f8..1965081 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -6,7 +6,6 @@ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
import KeyValue from '@shell/components/form/KeyValue';
import ArrayList from '@shell/components/form/ArrayList';
import LabeledSelect from '@shell/components/form/LabeledSelect';
-import formRulesGenerator, { Validator } from '@shell/utils/validators/formRules';
import type { ClusterClassVariable } from '../../types/clusterClass';
import { isDefined, openAPIV3SchemaValidators } from '../../util/validators';
@@ -19,9 +18,15 @@ export default defineComponent({
type: Object as PropType
,
required: true
},
+
value: {
type: [String, Object, Boolean, Array, Number],
default: () => null
+ },
+
+ validateRequired: {
+ type: Boolean,
+ default: true
}
},
@@ -97,9 +102,7 @@ export default defineComponent({
const required = this.variable?.required;
- if (required) {
- // out.push(formRulesGenerator(this.$store.getters['i18n/t'], { key: this.variable.name }).required as Validator);
-
+ if (required && this.validateRequired) {
out.push(val => !isDefined(val) ? this.$store.getters['i18n/t']('validation.required', { key: this.variable.name }) : null);
}
@@ -153,7 +156,7 @@ export default defineComponent({
:label="variable.name"
:placeholder="schema.example"
:tooltip="schema.description"
- :required="variable.required"
+ :required="variable.required && validateRequired"
:title="variable.name"
:options="variableOptions"
:rules="!listComponent ? validationRules : []"
diff --git a/pkg/capi/components/CCVariables/index.vue b/pkg/capi/components/CCVariables/index.vue
index 07a125e..1eb0a10 100644
--- a/pkg/capi/components/CCVariables/index.vue
+++ b/pkg/capi/components/CCVariables/index.vue
@@ -215,6 +215,7 @@ export default defineComponent({
:ref="`${variableDef.name}-input`"
:variable="variableDef"
:value="valueFor(variableDef)"
+ :validate-required="!machineDeploymentClass && !machinePoolClass"
@input="e=>updateVariables(e, variableDef)"
@validation-passed="updateErrors"
/>
diff --git a/pkg/capi/l10n/en-us.yaml b/pkg/capi/l10n/en-us.yaml
index ef4d22e..ad317fb 100644
--- a/pkg/capi/l10n/en-us.yaml
+++ b/pkg/capi/l10n/en-us.yaml
@@ -44,4 +44,5 @@ validation:
maxItems: '"{key}" may not contain more than {maxItems} items.'
minItems: '"{key}" must contain at least {minItems} items.'
pattern: '"{key}" must match the pattern {pattern}
.'
+ stringFormat: '"{key}" must be a valid {format}.'
uniqueItems: '"{key}" may not contain duplicate elements.'
diff --git a/pkg/capi/util/validators.ts b/pkg/capi/util/validators.ts
index b932b8b..3350a44 100644
--- a/pkg/capi/util/validators.ts
+++ b/pkg/capi/util/validators.ts
@@ -1,54 +1,9 @@
import { Validator, ValidationOptions } from '@shell/utils/validators/formRules';
import { Translation } from '@shell/types/t';
import isEmpty from 'lodash/isEmpty';
-
-// const stringFormats = {
-// // this is a mongodb id - requires library to validate?
-// // "bsonobjectid", // bson object ID
-
-// // "uri", // an URI as parsed by Golang net/url.ParseRequestURI
-// // "email", // an email address as parsed by Golang net/mail.ParseAddress
-// // use wildcardHostname
-// "hostname", // a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
-
-// // use utils/string/isIpv4
-// "ipv4", // an IPv4 IP as parsed by Golang net.ParseIP
-
-// // "ipv6", // an IPv6 IP as parsed by Golang net.ParseIP
-
-// //use isValidCIDR
-// "cidr", // a CIDR as parsed by Golang net.ParseCIDR
-
-// //use isValidMac
-// "mac", // a MAC address as parsed by Golang net.ParseMAC
-
-// // use toLowerCase() instead of mode modifier
-// "uuid", // an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
-// "uuid3", // an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
-// "uuid4", // an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
-// "uuid5", // an UUID6 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
-
-// //use https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch04s13.html
-// "isbn", // an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
-// "isbn10", // an ISBN10 number string like "0321751043"
-// "isbn13", // an ISBN13 number string like "978-0321751041"
-
-// "creditcard", // a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
-// "ssn", // a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
-// "hexcolor", // an hexadecimal color code like "#FFFFFF", following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
-
-// // /^rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)$/
-// "rgbcolor", // an RGB color code like rgb like "rgb(255,255,2559"
-
-// //use base64Decode?
-// "byte", // base64 encoded binary data
-
-// "password", // any kind of string
-
-// "date", // a date string like "2006-01-02" as defined by full-date in RFC3339
-// "duration", // a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
-// "datetime", // a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339}
-// }
+import { isIpv4 } from '@shell/utils/string';
+import { isValidCIDR, isValidMac } from '@shell/utils/validators/cidr';
+import formRulesGenerator from '@shell/utils/validators/formRules/index.ts';
/**
*
@@ -70,7 +25,8 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
minimum,
pattern,
uniqueItems,
- required: requiredFields
+ required: requiredFields,
+ format
} = openAPIV3Schema;
const out = [] as any[];
@@ -105,7 +61,11 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
}
if (pattern) {
- out.push((val: string) => val && !val.match(new RegExp(pattern)) ? t('validation.pattern', { key, pattern }) : undefined);
+ out.push(regexValidator(t('validation.pattern', { key, pattern }), new RegExp(pattern)));
+ }
+
+ if (format && stringFormatValidators(t, { key }, format)) {
+ out.push(stringFormatValidators(t, { key }, format));
}
if (uniqueItems) {
@@ -119,4 +79,45 @@ export const openAPIV3SchemaValidators = function(t: Translation, { key = 'Value
return out;
};
-export const isDefined = (val: any) => (val || val === false) && !isEmpty(val);
+const regexValidator = function(errorMessage: string, regexp: RegExp ): Validator {
+ return (val: string) => val && !val.match(regexp) ? errorMessage : undefined;
+};
+
+// strings can be evaluated against regular expressions by defining 'pattern' or validated against a common set of regexp using 'format'
+/**
+ *
+ * @param t this.$store.getters[i18n/t]
+ * @param param1 param1.key should be the (already localized) label of the field being validated
+ * @param format one of a set list of string formats defined here https://github.com/kubernetes/apiextensions-apiserver/blob/master/pkg/apiserver/validation/formats.go
+ *
+ * @returns a form validator for the string format, OR undefined if the format specified is invalid or a format we're not validating for practical reasons
+ */
+const stringFormatValidators = function(t: Translation, { key = 'Value' }: ValidationOptions, format: string): Validator | undefined {
+ const formRules = formRulesGenerator(t, { key } );
+ const errorMessage = t('validation.stringFormat', { key, format });
+
+ // there are actually 24 formats in total validated on the backend with some odd options eg ISBN, creditcard
+ // this subset of formats are the most universal, which we can be confident we are validating correctly
+ switch (format) {
+ case 'hostname':
+ return (val: string) => formRules.wildcardHostname(val);
+ case 'ipv4':
+ return (val: string) => val && !isIpv4(val) ? errorMessage : undefined;
+ case 'cidr':
+ return (val: string) => val && !isValidCIDR(val) ? errorMessage : undefined;
+ case 'mac':
+ return (val: string) => val && !isValidMac(val) ? errorMessage : undefined;
+ case 'uuid':
+ return regexValidator(errorMessage, /^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i);
+ case 'uuid3':
+ return regexValidator(errorMessage, /^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$/i);
+ case 'uuid4':
+ return regexValidator(errorMessage, /^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$/i);
+ case 'uuid5':
+ return regexValidator(errorMessage, /^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$/i);
+ default:
+ return undefined;
+ }
+};
+
+export const isDefined = (val: any) => (val || val === false) || !isEmpty(val);
From 8fba487c9e105d61945fa36398d635352fc9ce49 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Tue, 2 Jan 2024 17:21:07 -0800
Subject: [PATCH 18/23] cleanup following review
---
pkg/capi/components/CCVariables/Variable.vue | 2 +-
pkg/capi/components/CCVariables/index.vue | 3 ---
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index 1965081..c639557 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -148,7 +148,7 @@ export default defineComponent({
-
+
{}
},
- // .spec.topology.variables
- // OR .spec.topology.workers.machineDeployments[].variables.overrides
- // OR .spec.topology.workers.machinePools[].variables.overrides
value: {
type: Array as PropType>,
default: () => {
From 18d82edb4ccb79f2856dda93a8edab6822b8e041 Mon Sep 17 00:00:00 2001
From: Nancy Butler <42977925+mantis-toboggan-md@users.noreply.github.com>
Date: Wed, 3 Jan 2024 10:47:50 -0800
Subject: [PATCH 19/23] fix component imports
---
.vscode/settings.json | 1 +
pkg/capi/components/CCVariables/Variable.vue | 6 +++---
pkg/capi/edit/cluster.x-k8s.io.cluster.vue | 8 ++++----
3 files changed, 8 insertions(+), 7 deletions(-)
create mode 100644 .vscode/settings.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/pkg/capi/components/CCVariables/Variable.vue b/pkg/capi/components/CCVariables/Variable.vue
index c639557..f485411 100644
--- a/pkg/capi/components/CCVariables/Variable.vue
+++ b/pkg/capi/components/CCVariables/Variable.vue
@@ -3,9 +3,9 @@ import { defineComponent } from 'vue';
import type { PropType } from 'vue';
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
-import KeyValue from '@shell/components/form/KeyValue';
-import ArrayList from '@shell/components/form/ArrayList';
-import LabeledSelect from '@shell/components/form/LabeledSelect';
+import KeyValue from '@shell/components/form/KeyValue.vue';
+import ArrayList from '@shell/components/form/ArrayList.vue';
+import LabeledSelect from '@shell/components/form/LabeledSelect.vue';
import type { ClusterClassVariable } from '../../types/clusterClass';
import { isDefined, openAPIV3SchemaValidators } from '../../util/validators';
diff --git a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
index ed3158f..e04d0cf 100644
--- a/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
+++ b/pkg/capi/edit/cluster.x-k8s.io.cluster.vue
@@ -1,12 +1,12 @@