@spinnaker/google 2026.3.1 → 2026.4.0
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.
- package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
- package/dist/domain/loadBalancer.d.ts +6 -3
- package/dist/image/ImageSelect.d.ts +1 -1
- package/dist/index.js +159 -1
- package/dist/index.js.map +1 -1
- package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
- package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
- package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
- package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
- package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
- package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
- package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
- package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
- package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
- package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
- package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
- package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
- package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
- package/package.json +5 -5
- package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
- package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
- package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
- package/src/domain/loadBalancer.ts +6 -3
- package/src/gce.module.spec.ts +132 -153
- package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
- package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
- package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
- package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
- package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
- package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
- package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
- package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
- package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
- package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
- package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
- package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
- package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
- package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
- package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
- package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
- package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
- package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
- package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
- package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
- package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
- package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
- package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
- package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
- package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
- package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
- package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
- package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
- package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
- package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
- package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
- package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
- package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
- package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
- package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
- package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
- package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
- package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
- package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
- package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
- package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
- package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
- package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
- package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
- package/src/serverGroup/serverGroup.transformer.js +1 -0
- package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
- package/src/validation/ApplicationNameValidator.spec.ts +1 -1
- /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
|
@@ -1,16 +1,39 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
4
|
import { DeckRuntimeContext } from '@spinnaker/core';
|
|
5
|
-
import { CustomInstanceConfigurer } from './CustomInstanceConfigurer';
|
|
6
5
|
import { GceCustomInstanceBuilder } from './GceCustomInstanceBuilder';
|
|
7
6
|
|
|
7
|
+
vi.mock('./CustomInstanceConfigurer', () => ({
|
|
8
|
+
CustomInstanceConfigurer: (props: any) => (
|
|
9
|
+
<div
|
|
10
|
+
data-testid="custom-instance-configurer"
|
|
11
|
+
data-extended={String(props.selectedExtendedMemory)}
|
|
12
|
+
data-family={props.selectedInstanceFamily}
|
|
13
|
+
data-memory={String(props.selectedMemory)}
|
|
14
|
+
data-vcpu={String(props.selectedVCpuCount)}
|
|
15
|
+
>
|
|
16
|
+
<span>{`families:${props.instanceFamilyList.join(',')}`}</span>
|
|
17
|
+
<span>{`cpus:${props.vCpuList.join(',')}`}</span>
|
|
18
|
+
<span>{`memory:${props.memoryList.join(',')}`}</span>
|
|
19
|
+
<button
|
|
20
|
+
onClick={() => props.onChange({ instanceFamily: 'N2', vCpuCount: 8, memory: 32, extendedMemory: false })}
|
|
21
|
+
type="button"
|
|
22
|
+
>
|
|
23
|
+
Select N2 custom type
|
|
24
|
+
</button>
|
|
25
|
+
<button
|
|
26
|
+
onClick={() => props.onChange({ instanceFamily: 'N1', vCpuCount: null, memory: 4, extendedMemory: false })}
|
|
27
|
+
type="button"
|
|
28
|
+
>
|
|
29
|
+
Select memory before cores
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
),
|
|
33
|
+
}));
|
|
34
|
+
|
|
8
35
|
describe('GceCustomInstanceBuilder', () => {
|
|
9
36
|
let runtimeServices: any;
|
|
10
|
-
const RuntimeWrapper = ({ children }: React.PropsWithChildren<{}>) => (
|
|
11
|
-
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>{children}</DeckRuntimeContext.Provider>
|
|
12
|
-
);
|
|
13
|
-
const mount = (component: React.ReactElement) => enzymeMount(component, { wrappingComponent: RuntimeWrapper });
|
|
14
37
|
|
|
15
38
|
beforeEach(() => {
|
|
16
39
|
runtimeServices = {};
|
|
@@ -20,49 +43,43 @@ describe('GceCustomInstanceBuilder', () => {
|
|
|
20
43
|
runtimeServices.instanceTypeService = instanceTypeService();
|
|
21
44
|
const command = commandWithCustomInstance('n2-custom-4-16384-ext');
|
|
22
45
|
|
|
23
|
-
|
|
24
|
-
const configurer =
|
|
46
|
+
renderBuilder(command, vi.fn());
|
|
47
|
+
const configurer = screen.getByTestId('custom-instance-configurer');
|
|
25
48
|
|
|
26
|
-
expect(configurer.
|
|
27
|
-
expect(configurer.
|
|
28
|
-
expect(configurer.
|
|
29
|
-
expect(configurer.
|
|
30
|
-
expect(
|
|
31
|
-
expect(
|
|
32
|
-
expect(
|
|
49
|
+
expect(configurer).toHaveAttribute('data-family', 'N2');
|
|
50
|
+
expect(configurer).toHaveAttribute('data-vcpu', '4');
|
|
51
|
+
expect(configurer).toHaveAttribute('data-memory', '16');
|
|
52
|
+
expect(configurer).toHaveAttribute('data-extended', 'true');
|
|
53
|
+
expect(screen.getByText(/families:.*N2D/)).toBeInTheDocument();
|
|
54
|
+
expect(screen.getByText(/cpus:.*4/)).toBeInTheDocument();
|
|
55
|
+
expect(screen.getByText(/memory:.*16/)).toBeInTheDocument();
|
|
33
56
|
});
|
|
34
57
|
|
|
35
58
|
it('updates command.instanceType, notifies, and loads details when custom choices change', async () => {
|
|
36
59
|
const instanceTypeDetails = { name: 'n2-custom-8-32768' };
|
|
37
60
|
runtimeServices.instanceTypeService = instanceTypeService(instanceTypeDetails);
|
|
38
|
-
const onTypeChanged =
|
|
61
|
+
const onTypeChanged = vi.fn();
|
|
39
62
|
const command = commandWithCustomInstance('n2-custom-4-16384');
|
|
63
|
+
renderBuilder(command, onTypeChanged);
|
|
40
64
|
|
|
41
|
-
|
|
42
|
-
component.find(CustomInstanceConfigurer).prop('onChange')({
|
|
43
|
-
instanceFamily: 'N2',
|
|
44
|
-
vCpuCount: 8,
|
|
45
|
-
memory: 32,
|
|
46
|
-
extendedMemory: false,
|
|
47
|
-
});
|
|
48
|
-
await settle(component);
|
|
65
|
+
fireEvent.click(screen.getByRole('button', { name: 'Select N2 custom type' }));
|
|
49
66
|
|
|
50
67
|
expect(command.instanceType).toBe('n2-custom-8-32768');
|
|
51
68
|
expect(command.customInstanceChanged).toHaveBeenCalledWith(command);
|
|
52
69
|
expect(onTypeChanged).toHaveBeenCalledWith('n2-custom-8-32768');
|
|
53
|
-
expect(command.viewState.instanceTypeDetails).toBe(instanceTypeDetails
|
|
70
|
+
await waitFor(() => expect(command.viewState.instanceTypeDetails).toBe(instanceTypeDetails));
|
|
54
71
|
});
|
|
55
72
|
|
|
56
73
|
it('initializes missing custom values from valid lists', () => {
|
|
57
74
|
runtimeServices.instanceTypeService = instanceTypeService();
|
|
58
75
|
const command = commandWithCustomInstance(null);
|
|
59
76
|
|
|
60
|
-
|
|
61
|
-
const configurer =
|
|
77
|
+
renderBuilder(command, vi.fn());
|
|
78
|
+
const configurer = screen.getByTestId('custom-instance-configurer');
|
|
62
79
|
|
|
63
|
-
expect(configurer.
|
|
64
|
-
expect(configurer.
|
|
65
|
-
expect(configurer.
|
|
80
|
+
expect(configurer).toHaveAttribute('data-family', 'N1');
|
|
81
|
+
expect(configurer).toHaveAttribute('data-vcpu', '1');
|
|
82
|
+
expect(configurer).toHaveAttribute('data-memory', '1');
|
|
66
83
|
expect(command.viewState.customInstance).toEqual({
|
|
67
84
|
extendedMemory: false,
|
|
68
85
|
instanceFamily: 'N1',
|
|
@@ -71,23 +88,28 @@ describe('GceCustomInstanceBuilder', () => {
|
|
|
71
88
|
});
|
|
72
89
|
});
|
|
73
90
|
|
|
74
|
-
it('keeps valid defaults when memory changes before cores',
|
|
91
|
+
it('keeps valid defaults when memory changes before cores', () => {
|
|
75
92
|
runtimeServices.instanceTypeService = instanceTypeService();
|
|
76
|
-
const onTypeChanged =
|
|
93
|
+
const onTypeChanged = vi.fn();
|
|
77
94
|
const command = commandWithCustomInstance(null);
|
|
95
|
+
renderBuilder(command, onTypeChanged);
|
|
78
96
|
|
|
79
|
-
|
|
80
|
-
component.find(CustomInstanceConfigurer).prop('onChange')({
|
|
81
|
-
instanceFamily: 'N1',
|
|
82
|
-
vCpuCount: null,
|
|
83
|
-
memory: 4,
|
|
84
|
-
extendedMemory: false,
|
|
85
|
-
} as any);
|
|
86
|
-
await settle(component);
|
|
97
|
+
fireEvent.click(screen.getByRole('button', { name: 'Select memory before cores' }));
|
|
87
98
|
|
|
88
99
|
expect(command.instanceType).toBe('custom-1-4096');
|
|
89
100
|
expect(onTypeChanged).toHaveBeenCalledWith('custom-1-4096');
|
|
90
101
|
});
|
|
102
|
+
|
|
103
|
+
function renderBuilder(
|
|
104
|
+
command: ReturnType<typeof commandWithCustomInstance>,
|
|
105
|
+
onTypeChanged: ReturnType<typeof vi.fn>,
|
|
106
|
+
) {
|
|
107
|
+
return render(
|
|
108
|
+
<DeckRuntimeContext.Provider value={{ services: runtimeServices } as any}>
|
|
109
|
+
<GceCustomInstanceBuilder command={command as any} onTypeChanged={onTypeChanged} />
|
|
110
|
+
</DeckRuntimeContext.Provider>,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
91
113
|
});
|
|
92
114
|
|
|
93
115
|
function commandWithCustomInstance(instanceType: string | null) {
|
|
@@ -96,7 +118,7 @@ function commandWithCustomInstance(instanceType: string | null) {
|
|
|
96
118
|
instanceType,
|
|
97
119
|
region: 'us-central1',
|
|
98
120
|
regional: true,
|
|
99
|
-
viewState: {},
|
|
121
|
+
viewState: {} as any,
|
|
100
122
|
backingData: {
|
|
101
123
|
customInstanceTypes: {},
|
|
102
124
|
credentialsKeyedByAccount: {
|
|
@@ -108,18 +130,12 @@ function commandWithCustomInstance(instanceType: string | null) {
|
|
|
108
130
|
},
|
|
109
131
|
},
|
|
110
132
|
credentials: 'test',
|
|
111
|
-
customInstanceChanged:
|
|
133
|
+
customInstanceChanged: vi.fn(),
|
|
112
134
|
};
|
|
113
135
|
}
|
|
114
136
|
|
|
115
137
|
function instanceTypeService(details = { name: 'n2-custom-8-32768' }) {
|
|
116
138
|
return {
|
|
117
|
-
getInstanceTypeDetails:
|
|
139
|
+
getInstanceTypeDetails: vi.fn().mockResolvedValue(details),
|
|
118
140
|
};
|
|
119
141
|
}
|
|
120
|
-
|
|
121
|
-
async function settle(component: any) {
|
|
122
|
-
await Promise.resolve();
|
|
123
|
-
await Promise.resolve();
|
|
124
|
-
component.update();
|
|
125
|
-
}
|
|
@@ -1,48 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { AdvancedSettings } from './AdvancedSettings';
|
|
4
|
+
import { AdvancedSettings, validateGceAdvancedSettings } from './AdvancedSettings';
|
|
7
5
|
import type { IGceServerGroupCommand } from '../GceServerGroupWizard.types';
|
|
8
6
|
|
|
9
7
|
describe('GCE server group Advanced Settings page', () => {
|
|
10
8
|
it('restores persisted advanced fields and preserves unknown options and maps', () => {
|
|
11
9
|
const values = command();
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
expect(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
expect(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
]);
|
|
22
|
-
expect(
|
|
23
|
-
'nvidia-tesla-t4',
|
|
24
|
-
'legacy-accelerator',
|
|
25
|
-
]);
|
|
26
|
-
expect(wrapper.find('[data-testid="service-account"] input').prop('value')).toBe('custom@example.com');
|
|
27
|
-
expect(wrapper.find('[data-testid="auth-scope-0"] input').prop('value')).toBe('unknown.scope');
|
|
28
|
-
expect(wrapper.find('[data-testid="enable-confidential-compute"] input').prop('checked')).toBe(true);
|
|
29
|
-
|
|
30
|
-
const editors = wrapper.find(MapEditor);
|
|
31
|
-
expect(editors.at(0).prop('model')).toBe(values.instanceMetadata);
|
|
32
|
-
expect(editors.at(1).prop('model')).toBe(values.labels);
|
|
33
|
-
expect(editors.at(2).prop('model')).toBe(values.resourceManagerTags);
|
|
10
|
+
renderAdvanced(values);
|
|
11
|
+
|
|
12
|
+
expect(optionValues(screen.getByTestId('minimum-cpu-platform'))).toEqual(['Automatic', 'legacy-platform']);
|
|
13
|
+
expect(optionValues(screen.getByTestId('disk-type-0'))).toEqual(['pd-ssd', 'legacy-disk']);
|
|
14
|
+
expect(optionValues(screen.getByTestId('accelerator-type-0'))).toEqual(['nvidia-tesla-t4', 'legacy-accelerator']);
|
|
15
|
+
expect(screen.getByTestId('service-account').querySelector('input')).toHaveValue('custom@example.com');
|
|
16
|
+
expect(screen.getByTestId('auth-scope-0').querySelector('input')).toHaveValue('unknown.scope');
|
|
17
|
+
expect(screen.getByTestId('enable-confidential-compute').querySelector('input')).toBeChecked();
|
|
18
|
+
expect(mapValues('Custom Metadata')).toEqual(['unknownMetadata', 'keep']);
|
|
19
|
+
expect(mapValues('Labels')).toEqual(['unknownLabel', 'keep']);
|
|
20
|
+
expect(mapValues('Resource Manager Tags')).toEqual(['unknownTag', 'keep']);
|
|
34
21
|
});
|
|
35
22
|
|
|
36
23
|
it('preserves unknown disk and accelerator fields while editing known values', () => {
|
|
37
|
-
|
|
38
|
-
const
|
|
24
|
+
// A persisted legacy accelerator only offers its stored count, so edit a known type with selectable counts.
|
|
25
|
+
const values = command({
|
|
26
|
+
acceleratorConfigs: [{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: 2, unknown: 'keep' }],
|
|
27
|
+
});
|
|
28
|
+
renderAdvanced(values);
|
|
39
29
|
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
fireEvent.change(screen.getByTestId('disk-size-0'), { target: { value: '200' } });
|
|
31
|
+
fireEvent.change(screen.getByTestId('accelerator-count-0'), { target: { value: '4' } });
|
|
42
32
|
|
|
43
33
|
expect(values.disks[0]).toEqual({ type: 'legacy-disk', sizeGb: 200, sourceImage: 'image', unknown: 'keep' });
|
|
44
34
|
expect(values.acceleratorConfigs[0]).toEqual({
|
|
45
|
-
acceleratorType: '
|
|
35
|
+
acceleratorType: 'nvidia-tesla-t4',
|
|
46
36
|
acceleratorCount: 4,
|
|
47
37
|
unknown: 'keep',
|
|
48
38
|
});
|
|
@@ -52,16 +42,16 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
52
42
|
const firstLocalSsd = { type: 'local-ssd', sizeGb: 375, autoDelete: true, unknown: 'first' };
|
|
53
43
|
const secondLocalSsd = { type: 'local-ssd', sizeGb: 375, autoDelete: true, unknown: 'second' };
|
|
54
44
|
const values = command({ disks: [command().disks[0], firstLocalSsd, secondLocalSsd] });
|
|
55
|
-
|
|
45
|
+
renderAdvanced(values);
|
|
56
46
|
|
|
57
|
-
expect(
|
|
58
|
-
expect(
|
|
47
|
+
expect(screen.getByTestId('local-ssd-count')).toHaveValue(2);
|
|
48
|
+
expect(screen.getAllByTestId(/^disk-type-/)).toHaveLength(1);
|
|
59
49
|
|
|
60
|
-
|
|
50
|
+
fireEvent.change(screen.getByTestId('local-ssd-count'), { target: { value: '3' } });
|
|
61
51
|
expect(values.disks.slice(1, 3)).toEqual([firstLocalSsd, secondLocalSsd]);
|
|
62
52
|
expect(values.disks[3]).toEqual({ type: 'local-ssd', sizeGb: 375 });
|
|
63
53
|
|
|
64
|
-
|
|
54
|
+
fireEvent.change(screen.getByTestId('local-ssd-count'), { target: { value: '1' } });
|
|
65
55
|
expect(values.disks).toEqual([command().disks[0], firstLocalSsd]);
|
|
66
56
|
});
|
|
67
57
|
|
|
@@ -73,33 +63,30 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
73
63
|
{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: '${ parameters.acceleratorCount }' },
|
|
74
64
|
],
|
|
75
65
|
});
|
|
76
|
-
|
|
66
|
+
renderAdvanced(values);
|
|
77
67
|
|
|
78
|
-
expect(
|
|
79
|
-
expect(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
.find('[data-testid="accelerator-count-0"]')
|
|
83
|
-
.simulate('change', { target: { value: '${ acceleratorCount }' } });
|
|
68
|
+
expect(screen.getByTestId('disk-size-0')).toHaveAttribute('type', 'text');
|
|
69
|
+
expect(screen.getByTestId('accelerator-count-0')).toHaveAttribute('type', 'text');
|
|
70
|
+
fireEvent.change(screen.getByTestId('disk-size-0'), { target: { value: '${ diskSize }' } });
|
|
71
|
+
fireEvent.change(screen.getByTestId('accelerator-count-0'), { target: { value: '${ acceleratorCount }' } });
|
|
84
72
|
|
|
85
73
|
expect(values.disks[0].sizeGb).toBe('${ diskSize }');
|
|
86
74
|
expect(values.acceleratorConfigs[0].acceleratorCount).toBe('${ acceleratorCount }');
|
|
87
|
-
expect(
|
|
75
|
+
expect(validateGceAdvancedSettings(values)).toEqual({});
|
|
88
76
|
});
|
|
89
77
|
|
|
90
78
|
it('enforces concrete disk and accelerator bounds and rejects expressions outside pipelines', () => {
|
|
91
|
-
const page = new AdvancedSettings({ app: {} as any, formik: formik(command()) } as any);
|
|
92
79
|
const invalid = command({
|
|
93
80
|
disks: [{ type: 'pd-ssd', sizeGb: 9 }],
|
|
94
81
|
acceleratorConfigs: [{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: 0 }],
|
|
95
82
|
});
|
|
96
83
|
|
|
97
|
-
expect(
|
|
84
|
+
expect(validateGceAdvancedSettings(invalid)).toEqual({
|
|
98
85
|
disks: 'Every persistent disk requires a type and an integer size between 10 and 65536 GB.',
|
|
99
86
|
acceleratorConfigs: 'Every accelerator requires a type and a supported positive integer count.',
|
|
100
87
|
});
|
|
101
88
|
expect(
|
|
102
|
-
|
|
89
|
+
validateGceAdvancedSettings(
|
|
103
90
|
command({
|
|
104
91
|
disks: [{ type: 'pd-ssd', sizeGb: 65537 }],
|
|
105
92
|
acceleratorConfigs: [{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: 3 }],
|
|
@@ -110,7 +97,7 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
110
97
|
acceleratorConfigs: 'Every accelerator requires a type and a supported positive integer count.',
|
|
111
98
|
});
|
|
112
99
|
expect(
|
|
113
|
-
|
|
100
|
+
validateGceAdvancedSettings(
|
|
114
101
|
command({
|
|
115
102
|
disks: [{ type: 'pd-ssd', sizeGb: '${ diskSize }' }],
|
|
116
103
|
acceleratorConfigs: [{ acceleratorType: 'nvidia-tesla-t4', acceleratorCount: '${ count }' }],
|
|
@@ -121,19 +108,18 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
121
108
|
acceleratorConfigs: 'Every accelerator requires a type and a supported positive integer count.',
|
|
122
109
|
});
|
|
123
110
|
|
|
124
|
-
|
|
125
|
-
expect(
|
|
126
|
-
expect(
|
|
111
|
+
renderAdvanced(command());
|
|
112
|
+
expect(screen.getByTestId('disk-size-0')).toHaveAttribute('min', '10');
|
|
113
|
+
expect(screen.getByTestId('disk-size-0')).toHaveAttribute('max', '65536');
|
|
127
114
|
});
|
|
128
115
|
|
|
129
116
|
it('does not render or validate the unsupported partner metadata editor', () => {
|
|
130
117
|
const values = command({ partnerMetadata: { partner: { entries: { unknown: 'keep' } } } });
|
|
131
|
-
const
|
|
132
|
-
const page = new AdvancedSettings({ app: {} as any, formik: formik(values) } as any);
|
|
118
|
+
const { container } = renderAdvanced(values);
|
|
133
119
|
|
|
134
|
-
expect(
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
120
|
+
expect(screen.queryByTestId('partner-metadata')).not.toBeInTheDocument();
|
|
121
|
+
expect(container).not.toHaveTextContent('Partner Metadata');
|
|
122
|
+
expect(validateGceAdvancedSettings(command({ partnerMetadata: '{legacy-invalid-json' }))).toEqual({});
|
|
137
123
|
});
|
|
138
124
|
|
|
139
125
|
it('keeps scheduling and shielded VM constraints internally consistent', () => {
|
|
@@ -144,43 +130,44 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
144
130
|
enableVtpm: true,
|
|
145
131
|
enableIntegrityMonitoring: true,
|
|
146
132
|
});
|
|
147
|
-
const
|
|
133
|
+
const page = renderAdvanced(values);
|
|
148
134
|
|
|
149
|
-
|
|
135
|
+
fireEvent.click(screen.getByTestId('preemptible-on'));
|
|
150
136
|
expect(values.preemptible).toBe(true);
|
|
151
137
|
expect(values.automaticRestart).toBe(false);
|
|
152
138
|
expect(values.onHostMaintenance).toBe('TERMINATE');
|
|
153
139
|
|
|
154
|
-
|
|
140
|
+
page.rerenderPage(values);
|
|
141
|
+
fireEvent.click(screen.getByTestId('preemptible-off'));
|
|
155
142
|
expect(values.preemptible).toBe(false);
|
|
156
143
|
expect(values.automaticRestart).toBe(true);
|
|
157
144
|
expect(values.onHostMaintenance).toBe('MIGRATE');
|
|
158
145
|
|
|
159
|
-
|
|
146
|
+
fireEvent.click(screen.getByTestId('enable-vtpm'));
|
|
160
147
|
expect(values.enableVtpm).toBe(false);
|
|
161
148
|
expect(values.enableIntegrityMonitoring).toBe(false);
|
|
162
149
|
});
|
|
163
150
|
|
|
164
151
|
it('uses accessible non-submit controls for every add and remove action', () => {
|
|
165
|
-
const
|
|
166
|
-
const actionButtons =
|
|
152
|
+
const { container } = renderAdvanced(command());
|
|
153
|
+
const actionButtons = Array.from(container.querySelectorAll('button'));
|
|
167
154
|
|
|
168
155
|
expect(actionButtons.length).toBeGreaterThan(0);
|
|
169
156
|
actionButtons.forEach((button) => {
|
|
170
|
-
expect(button.
|
|
171
|
-
expect(button.
|
|
157
|
+
expect(button).toHaveAttribute('type', 'button');
|
|
158
|
+
expect(button.getAttribute('aria-label') || button.textContent?.trim()).toBeTruthy();
|
|
172
159
|
});
|
|
173
160
|
});
|
|
174
161
|
|
|
175
162
|
it('adds and removes disks, accelerators, tags, and custom scopes without mutating existing entries', () => {
|
|
176
163
|
const values = command();
|
|
177
164
|
const originalDisk = values.disks[0];
|
|
178
|
-
|
|
165
|
+
renderAdvanced(values);
|
|
179
166
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
167
|
+
fireEvent.click(screen.getByTestId('add-disk'));
|
|
168
|
+
fireEvent.click(screen.getByTestId('add-accelerator'));
|
|
169
|
+
fireEvent.click(screen.getByTestId('add-network-tag'));
|
|
170
|
+
fireEvent.click(screen.getByTestId('add-auth-scope'));
|
|
184
171
|
|
|
185
172
|
expect(values.disks.length).toBe(2);
|
|
186
173
|
expect(values.disks[0]).toBe(originalDisk);
|
|
@@ -188,10 +175,10 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
188
175
|
expect(values.tags).toEqual([{ value: 'existing-tag', unknown: 'keep' }, { value: '' }]);
|
|
189
176
|
expect(values.authScopes).toEqual(['unknown.scope', '']);
|
|
190
177
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
178
|
+
fireEvent.click(screen.getByTestId('remove-disk-0'));
|
|
179
|
+
fireEvent.click(screen.getByTestId('remove-accelerator-0'));
|
|
180
|
+
fireEvent.click(screen.getByTestId('remove-network-tag-0'));
|
|
181
|
+
fireEvent.click(screen.getByTestId('remove-auth-scope-0'));
|
|
195
182
|
|
|
196
183
|
expect(values.disks.length).toBe(1);
|
|
197
184
|
expect(values.acceleratorConfigs.length).toBe(1);
|
|
@@ -200,10 +187,8 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
200
187
|
});
|
|
201
188
|
|
|
202
189
|
it('validates invalid disks, accelerators, maps, tags, scopes, and confidential settings', () => {
|
|
203
|
-
const page = new AdvancedSettings({ app: {} as any, formik: formik(command()) } as any);
|
|
204
|
-
|
|
205
190
|
expect(
|
|
206
|
-
|
|
191
|
+
validateGceAdvancedSettings(
|
|
207
192
|
command({
|
|
208
193
|
disks: [{ type: '', sizeGb: 0 }],
|
|
209
194
|
acceleratorConfigs: [{ acceleratorType: '', acceleratorCount: 0 }],
|
|
@@ -240,7 +225,7 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
240
225
|
enableConfidentialCompute: true,
|
|
241
226
|
confidentialInstanceType: '',
|
|
242
227
|
});
|
|
243
|
-
const
|
|
228
|
+
const { container } = renderAdvanced(values);
|
|
244
229
|
|
|
245
230
|
[
|
|
246
231
|
['[data-testid="local-ssd-count"]', 'gce-advanced-disks-error'],
|
|
@@ -255,34 +240,48 @@ describe('GCE server group Advanced Settings page', () => {
|
|
|
255
240
|
['#gce-confidential-instance-type', 'gce-advanced-confidential-instance-type-error'],
|
|
256
241
|
['[aria-label="Auth scope 1"]', 'gce-advanced-auth-scopes-error'],
|
|
257
242
|
].forEach(([selector, errorId]) => {
|
|
258
|
-
const control =
|
|
259
|
-
expect(control.
|
|
260
|
-
expect(control.
|
|
243
|
+
const control = container.querySelector(selector);
|
|
244
|
+
expect(control, selector).toHaveAttribute('aria-invalid', 'true');
|
|
245
|
+
expect(control, selector).toHaveAttribute('aria-describedby', errorId);
|
|
261
246
|
});
|
|
262
247
|
|
|
263
|
-
expectAdvancedError(
|
|
264
|
-
expectAdvancedError(
|
|
265
|
-
expectAdvancedError(
|
|
266
|
-
expectAdvancedError(
|
|
267
|
-
expectAdvancedError(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
);
|
|
272
|
-
expectAdvancedError(wrapper, 'gce-advanced-network-tags-error', 'Network tags cannot be empty.');
|
|
273
|
-
expectAdvancedError(
|
|
274
|
-
wrapper,
|
|
275
|
-
'gce-advanced-confidential-instance-type-error',
|
|
276
|
-
'Confidential instance type required.',
|
|
277
|
-
);
|
|
278
|
-
expectAdvancedError(wrapper, 'gce-advanced-auth-scopes-error', 'Auth scopes cannot be empty.');
|
|
248
|
+
expectAdvancedError('gce-advanced-disks-error', 'Every persistent disk requires a type');
|
|
249
|
+
expectAdvancedError('gce-advanced-accelerators-error', 'Every accelerator requires a type');
|
|
250
|
+
expectAdvancedError('gce-advanced-instance-metadata-error', 'Metadata keys cannot be empty.');
|
|
251
|
+
expectAdvancedError('gce-advanced-labels-error', 'Label values cannot be empty.');
|
|
252
|
+
expectAdvancedError('gce-advanced-resource-manager-tags-error', 'Resource Manager tag values cannot be empty.');
|
|
253
|
+
expectAdvancedError('gce-advanced-network-tags-error', 'Network tags cannot be empty.');
|
|
254
|
+
expectAdvancedError('gce-advanced-confidential-instance-type-error', 'Confidential instance type required.');
|
|
255
|
+
expectAdvancedError('gce-advanced-auth-scopes-error', 'Auth scopes cannot be empty.');
|
|
279
256
|
});
|
|
280
257
|
});
|
|
281
258
|
|
|
282
|
-
function
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
259
|
+
function renderAdvanced(values: IGceServerGroupCommand) {
|
|
260
|
+
const pageProps = { app: {} as any, formik: formik(values) };
|
|
261
|
+
const rendered = render(<AdvancedSettings {...pageProps} />);
|
|
262
|
+
return {
|
|
263
|
+
...rendered,
|
|
264
|
+
rerenderPage(nextValues: IGceServerGroupCommand) {
|
|
265
|
+
rendered.rerender(<AdvancedSettings {...pageProps} formik={formik(nextValues)} />);
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function optionValues(select: HTMLElement): string[] {
|
|
271
|
+
return within(select)
|
|
272
|
+
.getAllByRole('option')
|
|
273
|
+
.map((option) => (option as HTMLOptionElement).value);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function mapValues(label: string): string[] {
|
|
277
|
+
const editor = document.querySelector(`[aria-label="${label}"]`)!;
|
|
278
|
+
return Array.from(editor.querySelectorAll('input')).map((input) => input.value);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function expectAdvancedError(id: string, message: string): void {
|
|
282
|
+
const error = document.getElementById(id);
|
|
283
|
+
expect(error, id).toHaveAttribute('role', 'alert');
|
|
284
|
+
expect(error, id).toHaveTextContent(message);
|
|
286
285
|
}
|
|
287
286
|
|
|
288
287
|
function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGroupCommand {
|
|
@@ -292,13 +291,7 @@ function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGro
|
|
|
292
291
|
viewState: {
|
|
293
292
|
mode: 'clone',
|
|
294
293
|
instanceTypeDetails: { storage: { localSSDSupported: true } },
|
|
295
|
-
acceleratorTypes: [
|
|
296
|
-
{
|
|
297
|
-
name: 'nvidia-tesla-t4',
|
|
298
|
-
description: 'NVIDIA Tesla T4',
|
|
299
|
-
availableCardCounts: [1, 2, 4],
|
|
300
|
-
},
|
|
301
|
-
],
|
|
294
|
+
acceleratorTypes: [{ name: 'nvidia-tesla-t4', description: 'NVIDIA Tesla T4', availableCardCounts: [1, 2, 4] }],
|
|
302
295
|
},
|
|
303
296
|
backingData: {
|
|
304
297
|
persistentDiskTypes: ['pd-ssd'],
|
|
@@ -332,7 +325,7 @@ function command(overrides: Partial<IGceServerGroupCommand> = {}): IGceServerGro
|
|
|
332
325
|
function formik(values: IGceServerGroupCommand): any {
|
|
333
326
|
return {
|
|
334
327
|
values,
|
|
335
|
-
setFieldValue:
|
|
328
|
+
setFieldValue: vi.fn().mockImplementation((field: string, value: any) => {
|
|
336
329
|
values[field] = value;
|
|
337
330
|
}),
|
|
338
331
|
};
|
|
@@ -80,34 +80,7 @@ function renderValidationError(error: string, id: string): React.ReactNode {
|
|
|
80
80
|
|
|
81
81
|
export class AdvancedSettings extends GceServerGroupWizardPage {
|
|
82
82
|
public validate(values: IGceServerGroupCommand): { [key: string]: string } {
|
|
83
|
-
|
|
84
|
-
if (getPersistentDisks(values).some((disk: any) => !disk.type || !isValidInteger(disk.sizeGb, 10, values, 65536))) {
|
|
85
|
-
errors.disks = 'Every persistent disk requires a type and an integer size between 10 and 65536 GB.';
|
|
86
|
-
}
|
|
87
|
-
if ((values.acceleratorConfigs || []).some((accelerator: any) => !isValidAccelerator(accelerator, values))) {
|
|
88
|
-
errors.acceleratorConfigs = 'Every accelerator requires a type and a supported positive integer count.';
|
|
89
|
-
}
|
|
90
|
-
if (hasEmptyKey(values.instanceMetadata)) {
|
|
91
|
-
errors.instanceMetadata = 'Metadata keys cannot be empty.';
|
|
92
|
-
}
|
|
93
|
-
if (hasEmptyKey(values.labels) || hasEmptyValue(values.labels)) {
|
|
94
|
-
errors.labels = hasEmptyKey(values.labels) ? 'Label keys cannot be empty.' : 'Label values cannot be empty.';
|
|
95
|
-
}
|
|
96
|
-
if (hasEmptyKey(values.resourceManagerTags) || hasEmptyValue(values.resourceManagerTags)) {
|
|
97
|
-
errors.resourceManagerTags = hasEmptyKey(values.resourceManagerTags)
|
|
98
|
-
? 'Resource Manager tag keys cannot be empty.'
|
|
99
|
-
: 'Resource Manager tag values cannot be empty.';
|
|
100
|
-
}
|
|
101
|
-
if ((values.tags || []).some((tag: any) => !(typeof tag === 'string' ? tag : tag.value)?.trim())) {
|
|
102
|
-
errors.tags = 'Network tags cannot be empty.';
|
|
103
|
-
}
|
|
104
|
-
if ((values.authScopes || []).some((scope: string) => !scope.trim())) {
|
|
105
|
-
errors.authScopes = 'Auth scopes cannot be empty.';
|
|
106
|
-
}
|
|
107
|
-
if (values.enableConfidentialCompute && 'confidentialInstanceType' in values && !values.confidentialInstanceType) {
|
|
108
|
-
errors.confidentialInstanceType = 'Confidential instance type required.';
|
|
109
|
-
}
|
|
110
|
-
return errors;
|
|
83
|
+
return validateGceAdvancedSettings(values);
|
|
111
84
|
}
|
|
112
85
|
|
|
113
86
|
private setField = (field: string, value: any): void => {
|
|
@@ -869,3 +842,34 @@ export class AdvancedSettings extends GceServerGroupWizardPage {
|
|
|
869
842
|
);
|
|
870
843
|
}
|
|
871
844
|
}
|
|
845
|
+
|
|
846
|
+
export function validateGceAdvancedSettings(values: IGceServerGroupCommand): { [key: string]: string } {
|
|
847
|
+
const errors: { [key: string]: string } = {};
|
|
848
|
+
if (getPersistentDisks(values).some((disk: any) => !disk.type || !isValidInteger(disk.sizeGb, 10, values, 65536))) {
|
|
849
|
+
errors.disks = 'Every persistent disk requires a type and an integer size between 10 and 65536 GB.';
|
|
850
|
+
}
|
|
851
|
+
if ((values.acceleratorConfigs || []).some((accelerator: any) => !isValidAccelerator(accelerator, values))) {
|
|
852
|
+
errors.acceleratorConfigs = 'Every accelerator requires a type and a supported positive integer count.';
|
|
853
|
+
}
|
|
854
|
+
if (hasEmptyKey(values.instanceMetadata)) {
|
|
855
|
+
errors.instanceMetadata = 'Metadata keys cannot be empty.';
|
|
856
|
+
}
|
|
857
|
+
if (hasEmptyKey(values.labels) || hasEmptyValue(values.labels)) {
|
|
858
|
+
errors.labels = hasEmptyKey(values.labels) ? 'Label keys cannot be empty.' : 'Label values cannot be empty.';
|
|
859
|
+
}
|
|
860
|
+
if (hasEmptyKey(values.resourceManagerTags) || hasEmptyValue(values.resourceManagerTags)) {
|
|
861
|
+
errors.resourceManagerTags = hasEmptyKey(values.resourceManagerTags)
|
|
862
|
+
? 'Resource Manager tag keys cannot be empty.'
|
|
863
|
+
: 'Resource Manager tag values cannot be empty.';
|
|
864
|
+
}
|
|
865
|
+
if ((values.tags || []).some((tag: any) => !(typeof tag === 'string' ? tag : tag.value)?.trim())) {
|
|
866
|
+
errors.tags = 'Network tags cannot be empty.';
|
|
867
|
+
}
|
|
868
|
+
if ((values.authScopes || []).some((scope: string) => !scope.trim())) {
|
|
869
|
+
errors.authScopes = 'Auth scopes cannot be empty.';
|
|
870
|
+
}
|
|
871
|
+
if (values.enableConfidentialCompute && 'confidentialInstanceType' in values && !values.confidentialInstanceType) {
|
|
872
|
+
errors.confidentialInstanceType = 'Confidential instance type required.';
|
|
873
|
+
}
|
|
874
|
+
return errors;
|
|
875
|
+
}
|