diff --git a/pkg/gke/components/CruGKE.vue b/pkg/gke/components/CruGKE.vue index 0af4315d545..7e75d8bc072 100644 --- a/pkg/gke/components/CruGKE.vue +++ b/pkg/gke/components/CruGKE.vue @@ -34,7 +34,7 @@ import type { getGKEMachineTypesResponse, getGKEServiceAccountsResponse } from ' import type { GKEMachineTypeOption } from '../types/index.d.ts'; import debounce from 'lodash/debounce'; import { - clusterNameChars, clusterNameStartEnd, requiredInCluster, ipv4WithCidr, ipv4oripv6WithCidr + clusterNameChars, clusterNameStartEnd, requiredInCluster, ipv4WithCidr, ipv4oripv6WithCidr, GKEInitialCount } from '../util/validators'; import { diffUpstreamSpec, syncUpstreamConfig } from '@shell/utils/kontainer'; @@ -322,6 +322,7 @@ export default defineComponent({ nodeIpv4CidrBlockFormat: ipv4WithCidr(this, 'gke.nodeIpv4CidrBlock.label', 'gkeConfig.ipAllocationPolicy.nodeIpv4CidrBlock'), servicesIpv4CidrBlockFormat: ipv4WithCidr(this, 'gke.servicesIpv4CidrBlock.label', 'gkeConfig.ipAllocationPolicy.servicesIpv4CidrBlock'), clusterIpv4CidrFormat: ipv4oripv6WithCidr(this, 'gke.clusterIpv4Cidr.label', 'gkeConfig.clusterIpv4Cidr'), + initialNodeCount: GKEInitialCount(this), /** * The nodepool validators below are performing double duty. When passed directly to an input, the val argument is provided and validated - this generates the error icon in the input component. * otherwise they're run in the fv mixin and ALL nodepools are validated - this disables the cruresource create button @@ -339,19 +340,6 @@ export default defineComponent({ return !!this.nodePools.find((pool: GKENodePool) => !valid(pool.config.diskSizeGb || 0) ) ? this.t('gke.errors.diskSizeGb') : null; }, - initialNodeCount: (val: number) => { - if (!this.isAuthenticated) { - return; - } - const valid = (input: number) => input >= 1; - - if (val || val === 0) { - return !valid(val) ? this.t('gke.errors.initialNodeCount') : null; - } - - return !!this.nodePools.find((pool: GKENodePool) => !valid(pool.initialNodeCount || 0) ) ? this.t('gke.errors.initialNodeCount') : null; - }, - ssdCount: (val: number) => { if (!this.isAuthenticated) { return; @@ -686,7 +674,7 @@ export default defineComponent({ @error="e=>errors=e" @finish="save" > - + diff --git a/pkg/gke/l10n/en-us.yaml b/pkg/gke/l10n/en-us.yaml index 0d913698afa..4d72cdee509 100644 --- a/pkg/gke/l10n/en-us.yaml +++ b/pkg/gke/l10n/en-us.yaml @@ -83,7 +83,7 @@ gke: clusterNameStartEnd: Cluster name must startd and end with a letter or number. diskSizeGb: Disk Size must be at least 10GB. genericKey: Value - initialNodeCount: Initial Node Count must be at least 1. + initialNodeCount: Initial Node Count must be at least 0, and no greater than 1000. ipv4Cidr: '{key} must be a valid ipv4 cidr range.' ipv4oripv6: '{key} must be a valid ipv6 or ipv4 CIDR address' minMaxNodeCount: Minimum Node Count cannot exceed Maximum Node Count. diff --git a/pkg/gke/util/__tests__/validators.test.ts b/pkg/gke/util/__tests__/validators.test.ts new file mode 100644 index 00000000000..f7dc4459433 --- /dev/null +++ b/pkg/gke/util/__tests__/validators.test.ts @@ -0,0 +1,42 @@ +import { GKEInitialCount } from '../validators'; + +const mockTranslation = (key: string) => key; + +describe('validate GKE node group initial count', () => { + it.each([ + [-1, 'gke.errors.initialNodeCount'], + [0, null], + [2, null], + [1000, null], + [1001, 'gke.errors.initialNodeCount'] + ])('should return an error if the initial node count is less than 0 or greater than 1000', (count, errMsg) => { + const ctx = { + config: { }, + t: mockTranslation, + isAuthenticated: true + } as any; + + const res = GKEInitialCount(ctx)(count); + + expect(res).toBe(errMsg); + }); + + it.each([ + [[{ initialNodeCount: -1 }], 'gke.errors.initialNodeCount'], + [[{ initialNodeCount: 0 }, { initialNodeCount: 1 }], null], + [[{ initialNodeCount: 1000 }, { initialNodeCount: 1 }], null], + [[{ initialNodeCount: 1001 }, { initialNodeCount: 1 }], 'gke.errors.initialNodeCount'] + + ])('should validate each node group in the component context if not called with a specific count value', (nodePools, errMsg) => { + const ctx = { + config: { }, + t: mockTranslation, + nodePools, + isAuthenticated: true + } as any; + + const res = GKEInitialCount(ctx)(); + + expect(res).toBe(errMsg); + }); +}); diff --git a/pkg/gke/util/validators.ts b/pkg/gke/util/validators.ts index 63841369491..996e19df3de 100644 --- a/pkg/gke/util/validators.ts +++ b/pkg/gke/util/validators.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { get } from '@shell/utils/object'; +import { GKENodePool } from '../types'; import ipaddr from 'ipaddr.js'; // no need to try to validate any fields if the user is still selecting a credential and the rest of the form isn't visible @@ -69,3 +70,18 @@ export const ipv4oripv6WithCidr = (ctx: any, labelKey: string, clusterPath: stri return isValid || !toValidate.length ? undefined : ctx.t('gke.errors.ipv4Cidr', { key: ctx.t(labelKey) || ctx.t('gke.errors.genericKey') }); }; }; + +export const GKEInitialCount = (ctx:any) => { + return (val?: number) => { + if (!ctx.isAuthenticated) { + return; + } + const valid = (input?: number) => (!!input || input === 0) && input >= 0 && input <= 1000; + + if (val || val === 0) { + return !valid(val) ? ctx.t('gke.errors.initialNodeCount') : null; + } + + return !!ctx.nodePools.find((pool: GKENodePool) => !valid(pool.initialNodeCount) ) ? ctx.t('gke.errors.initialNodeCount') : null; + }; +};