@spinnaker/google 2026.3.2 → 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
|
@@ -24,6 +24,22 @@ function selectedReference(name: string, options: IGceLoadBalancerDataItem[]): I
|
|
|
24
24
|
return (options.find((option) => option.name === name) || { name }) as IGceResourceReference;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
function certificateManagerReference(resourceUrl: string): IGceResourceReference | undefined {
|
|
28
|
+
const selfLink = resourceUrl.trim();
|
|
29
|
+
if (!selfLink) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name: selfLink.split('/').filter(Boolean).pop() || selfLink,
|
|
34
|
+
selfLink,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function certificateManagerUrl(certificate: IGceResourceReference | undefined): string {
|
|
39
|
+
const selfLink = certificate?.selfLink || '';
|
|
40
|
+
return selfLink.includes('/sslCertificates/') ? '' : selfLink;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
export function GceHttpLoadBalancerListenerEditor({
|
|
28
44
|
addresses,
|
|
29
45
|
certificates,
|
|
@@ -34,6 +50,24 @@ export function GceHttpLoadBalancerListenerEditor({
|
|
|
34
50
|
subnets,
|
|
35
51
|
}: IGceHttpLoadBalancerListenerEditorProps): JSX.Element {
|
|
36
52
|
const protocols = ['HTTP', 'HTTPS'];
|
|
53
|
+
const externalManaged = loadBalancerType === 'EXTERNAL_MANAGED';
|
|
54
|
+
const reservedNetworkTier = listener.address?.networkTier as string | undefined;
|
|
55
|
+
|
|
56
|
+
const updateAddress = (name: string): void => {
|
|
57
|
+
const address = selectedReference(name, addresses);
|
|
58
|
+
if (externalManaged) {
|
|
59
|
+
const selectedAddress = addresses.find((option) => option.name === name);
|
|
60
|
+
const networkTier = (selectedAddress as { networkTier?: string } | undefined)?.networkTier;
|
|
61
|
+
onChange({
|
|
62
|
+
...listener,
|
|
63
|
+
address,
|
|
64
|
+
...(networkTier ? { networkTier } : {}),
|
|
65
|
+
});
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const { networkTier: _networkTier, ...listenerWithoutTier } = listener;
|
|
69
|
+
onChange({ ...listenerWithoutTier, address });
|
|
70
|
+
};
|
|
37
71
|
|
|
38
72
|
return (
|
|
39
73
|
<fieldset className="gce-http-listener">
|
|
@@ -99,7 +133,7 @@ export function GceHttpLoadBalancerListenerEditor({
|
|
|
99
133
|
<select
|
|
100
134
|
data-testid="listener-address"
|
|
101
135
|
value={listener.address?.name || ''}
|
|
102
|
-
onChange={(event) =>
|
|
136
|
+
onChange={(event) => updateAddress(event.target.value)}
|
|
103
137
|
>
|
|
104
138
|
<option value="">Ephemeral</option>
|
|
105
139
|
{addresses.map((address) => (
|
|
@@ -132,19 +166,53 @@ export function GceHttpLoadBalancerListenerEditor({
|
|
|
132
166
|
))}
|
|
133
167
|
</select>
|
|
134
168
|
</label>
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
{externalManaged && (
|
|
170
|
+
<label>
|
|
171
|
+
Certificate Manager resource URL
|
|
172
|
+
<input
|
|
173
|
+
data-testid="listener-certificate-manager-url"
|
|
174
|
+
placeholder="//certificatemanager.googleapis.com/projects/.../locations/.../certificates/..."
|
|
175
|
+
type="text"
|
|
176
|
+
value={certificateManagerUrl(listener.certificate)}
|
|
177
|
+
onChange={(event) =>
|
|
178
|
+
onChange({
|
|
179
|
+
...listener,
|
|
180
|
+
certificate: certificateManagerReference(event.target.value),
|
|
181
|
+
certificateMap: undefined,
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
/>
|
|
185
|
+
</label>
|
|
186
|
+
)}
|
|
187
|
+
{!externalManaged && (
|
|
188
|
+
<label>
|
|
189
|
+
Certificate map
|
|
190
|
+
<input
|
|
191
|
+
data-testid="listener-certificate-map"
|
|
192
|
+
type="text"
|
|
193
|
+
value={listener.certificateMap || ''}
|
|
194
|
+
onChange={(event) =>
|
|
195
|
+
onChange({ ...listener, certificate: undefined, certificateMap: event.target.value || undefined })
|
|
196
|
+
}
|
|
197
|
+
/>
|
|
198
|
+
</label>
|
|
199
|
+
)}
|
|
146
200
|
</>
|
|
147
201
|
)}
|
|
202
|
+
{externalManaged && (
|
|
203
|
+
<label>
|
|
204
|
+
Network tier
|
|
205
|
+
<select
|
|
206
|
+
data-testid="listener-network-tier"
|
|
207
|
+
disabled={Boolean(reservedNetworkTier)}
|
|
208
|
+
value={reservedNetworkTier || listener.networkTier || 'PREMIUM'}
|
|
209
|
+
onChange={(event) => onChange({ ...listener, networkTier: event.target.value })}
|
|
210
|
+
>
|
|
211
|
+
<option value="PREMIUM">Premium</option>
|
|
212
|
+
<option value="STANDARD">Standard</option>
|
|
213
|
+
</select>
|
|
214
|
+
</label>
|
|
215
|
+
)}
|
|
148
216
|
<button type="button" className="btn btn-sm btn-default" onClick={onRemove}>
|
|
149
217
|
Remove listener
|
|
150
218
|
</button>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { fireEvent, screen } from '@testing-library/react';
|
|
3
3
|
|
|
4
4
|
import { TaskExecutor } from '@spinnaker/core';
|
|
5
|
+
import { renderWithRouter } from '../../../../../core/src/utils/testUtils/rtl';
|
|
5
6
|
|
|
6
7
|
import { buildGceLoadBalancerJobs } from '../common';
|
|
8
|
+
import { validateGceHttpLoadBalancerCommand } from './GceHttpLoadBalancerEditor';
|
|
7
9
|
import { GceHttpLoadBalancerModal, initializeGceHttpLoadBalancerCommand } from './GceHttpLoadBalancerModal';
|
|
8
10
|
|
|
9
11
|
describe('GceHttpLoadBalancerModal', () => {
|
|
@@ -75,71 +77,79 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
it('returns exact operations without executing a task in pipeline-edit mode', () => {
|
|
78
|
-
const closeModal =
|
|
79
|
-
const executeTask = spyOn(TaskExecutor, 'executeTask');
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
hostRules: [
|
|
92
|
-
{
|
|
93
|
-
hostPatterns: ['api.example.com'],
|
|
94
|
-
pathMatcher: {
|
|
95
|
-
defaultService: 'backend-a',
|
|
96
|
-
pathRules: [{ backendService: 'backend-a', paths: ['/v1'] }],
|
|
97
|
-
},
|
|
80
|
+
const closeModal = vi.fn();
|
|
81
|
+
const executeTask = vi.spyOn(TaskExecutor, 'executeTask').mockReturnValue(undefined);
|
|
82
|
+
const loadBalancer = {
|
|
83
|
+
account: 'account-a',
|
|
84
|
+
backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
|
|
85
|
+
defaultService: 'backend-a',
|
|
86
|
+
healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
|
|
87
|
+
hostRules: [
|
|
88
|
+
{
|
|
89
|
+
hostPatterns: ['api.example.com'],
|
|
90
|
+
pathMatcher: {
|
|
91
|
+
defaultService: 'backend-a',
|
|
92
|
+
pathRules: [{ backendService: 'backend-a', paths: ['/v1'] }],
|
|
98
93
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
listeners: [{ ipAddress: 'address-a', name: 'frontend', port: 80, protocol: 'HTTP' }],
|
|
97
|
+
loadBalancerType: 'HTTP',
|
|
98
|
+
name: 'test-app-main',
|
|
99
|
+
};
|
|
100
|
+
const expectedOperations = buildGceLoadBalancerJobs(
|
|
101
|
+
initializeGceHttpLoadBalancerCommand(loadBalancer, 'pipeline', application),
|
|
102
|
+
);
|
|
103
|
+
renderWithRouter(
|
|
104
|
+
<GceHttpLoadBalancerModal
|
|
105
|
+
app={application}
|
|
106
|
+
closeModal={closeModal}
|
|
107
|
+
data={emptyData}
|
|
108
|
+
dismissModal={vi.fn()}
|
|
109
|
+
forPipelineConfig={true}
|
|
110
|
+
isNew={false}
|
|
111
|
+
loadBalancer={loadBalancer}
|
|
112
|
+
/>,
|
|
113
|
+
);
|
|
107
114
|
|
|
108
|
-
(
|
|
115
|
+
fireEvent.click(screen.getByRole('button', { name: 'Done' }));
|
|
109
116
|
|
|
110
|
-
expect((modal as any).state.command.mode).toBe('pipeline');
|
|
111
117
|
expect(executeTask).not.toHaveBeenCalled();
|
|
112
|
-
expect(closeModal).
|
|
118
|
+
expect(closeModal).toHaveBeenCalledExactlyOnceWith(expectedOperations);
|
|
113
119
|
expect(expectedOperations.map(({ loadBalancerName }) => loadBalancerName)).toEqual(['frontend']);
|
|
114
120
|
});
|
|
115
121
|
|
|
116
122
|
it('executes an update task instead of returning operations in infrastructure-edit mode', () => {
|
|
117
|
-
const closeModal =
|
|
118
|
-
const executeTask =
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
123
|
+
const closeModal = vi.fn();
|
|
124
|
+
const executeTask = vi.fn().mockReturnValue(Promise.resolve({ id: 'task' }));
|
|
125
|
+
const loadBalancer = {
|
|
126
|
+
account: 'account-a',
|
|
127
|
+
backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
|
|
128
|
+
defaultService: 'backend-a',
|
|
129
|
+
healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
|
|
130
|
+
listeners: [{ ipAddress: 'address-a', name: 'frontend', port: 80, protocol: 'HTTP' }],
|
|
131
|
+
loadBalancerType: 'HTTP',
|
|
132
|
+
name: 'test-app-main',
|
|
133
|
+
};
|
|
134
|
+
const expectedOperations = buildGceLoadBalancerJobs(
|
|
135
|
+
initializeGceHttpLoadBalancerCommand(loadBalancer, 'edit', application),
|
|
136
|
+
);
|
|
137
|
+
renderWithRouter(
|
|
138
|
+
<GceHttpLoadBalancerModal
|
|
139
|
+
app={application}
|
|
140
|
+
closeModal={closeModal}
|
|
141
|
+
data={emptyData}
|
|
142
|
+
dismissModal={vi.fn()}
|
|
143
|
+
executeTask={executeTask}
|
|
144
|
+
isNew={false}
|
|
145
|
+
loadBalancer={loadBalancer}
|
|
146
|
+
/>,
|
|
147
|
+
);
|
|
137
148
|
|
|
138
|
-
(
|
|
149
|
+
fireEvent.click(screen.getByRole('button', { name: 'Update' }));
|
|
139
150
|
|
|
140
|
-
expect((modal as any).state.command.mode).toBe('edit');
|
|
141
151
|
expect(closeModal).not.toHaveBeenCalled();
|
|
142
|
-
expect(executeTask).
|
|
152
|
+
expect(executeTask).toHaveBeenCalledExactlyOnceWith({
|
|
143
153
|
application,
|
|
144
154
|
description: 'Update Load Balancer: test-app-main',
|
|
145
155
|
job: expectedOperations,
|
|
@@ -147,37 +157,40 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
147
157
|
});
|
|
148
158
|
|
|
149
159
|
it('executes normalized listener jobs in infrastructure mode', () => {
|
|
150
|
-
const executeTask =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
160
|
+
const executeTask = vi.fn().mockReturnValue(Promise.resolve({ id: 'task' }));
|
|
161
|
+
renderWithRouter(
|
|
162
|
+
<GceHttpLoadBalancerModal
|
|
163
|
+
app={application}
|
|
164
|
+
closeModal={vi.fn()}
|
|
165
|
+
data={emptyData}
|
|
166
|
+
dismissModal={vi.fn()}
|
|
167
|
+
executeTask={executeTask}
|
|
168
|
+
loadBalancer={{
|
|
169
|
+
account: 'account-a',
|
|
170
|
+
backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
|
|
171
|
+
defaultService: 'backend-a',
|
|
172
|
+
healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
|
|
173
|
+
listeners: [
|
|
174
|
+
{ ipAddress: 'address-a', name: 'frontend-a', port: 80, protocol: 'HTTP' },
|
|
175
|
+
{ ipAddress: 'address-b', name: 'frontend-b', port: 8080, protocol: 'HTTP' },
|
|
176
|
+
],
|
|
177
|
+
loadBalancerType: 'INTERNAL_MANAGED',
|
|
178
|
+
name: 'test-app-main',
|
|
179
|
+
network: 'network-a',
|
|
180
|
+
region: 'europe-west1',
|
|
181
|
+
subnet: 'subnet-a',
|
|
182
|
+
}}
|
|
183
|
+
mode="create"
|
|
184
|
+
/>,
|
|
185
|
+
);
|
|
173
186
|
|
|
174
|
-
(
|
|
187
|
+
fireEvent.click(screen.getByRole('button', { name: 'Create' }));
|
|
175
188
|
|
|
176
189
|
expect(executeTask).toHaveBeenCalled();
|
|
177
|
-
const jobs = executeTask.
|
|
190
|
+
const jobs = executeTask.mock.lastCall[0].job;
|
|
178
191
|
expect(jobs.map(({ name }: any) => name)).toEqual(['frontend-a', 'frontend-b']);
|
|
179
192
|
expect(jobs[0]).toEqual(
|
|
180
|
-
|
|
193
|
+
expect.objectContaining({
|
|
181
194
|
ipAddress: 'address-a',
|
|
182
195
|
ipProtocol: 'TCP',
|
|
183
196
|
loadBalancerName: 'frontend-a',
|
|
@@ -189,11 +202,56 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
189
202
|
});
|
|
190
203
|
|
|
191
204
|
it('serializes INTERNAL_MANAGED HTTPS listeners with certificates', () => {
|
|
192
|
-
const closeModal =
|
|
205
|
+
const closeModal = vi.fn();
|
|
206
|
+
renderWithRouter(
|
|
207
|
+
<GceHttpLoadBalancerModal
|
|
208
|
+
app={application}
|
|
209
|
+
closeModal={closeModal}
|
|
210
|
+
data={emptyData}
|
|
211
|
+
dismissModal={vi.fn()}
|
|
212
|
+
forPipelineConfig={true}
|
|
213
|
+
loadBalancer={{
|
|
214
|
+
account: 'account-a',
|
|
215
|
+
backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
|
|
216
|
+
defaultService: 'backend-a',
|
|
217
|
+
healthChecks: [{ healthCheckType: 'HTTPS', name: 'check-a', port: 443, requestPath: '/health' }],
|
|
218
|
+
listeners: [
|
|
219
|
+
{
|
|
220
|
+
certificate: 'regional-cert',
|
|
221
|
+
name: 'internal-https',
|
|
222
|
+
port: 443,
|
|
223
|
+
protocol: 'HTTPS',
|
|
224
|
+
subnet: 'subnet-a',
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
loadBalancerType: 'INTERNAL_MANAGED',
|
|
228
|
+
name: 'test-app-internal',
|
|
229
|
+
network: 'network-a',
|
|
230
|
+
region: 'europe-west1',
|
|
231
|
+
subnet: 'subnet-a',
|
|
232
|
+
}}
|
|
233
|
+
/>,
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
fireEvent.click(screen.getByRole('button', { name: 'Done' }));
|
|
237
|
+
|
|
238
|
+
expect(closeModal).toHaveBeenCalledWith([
|
|
239
|
+
expect.objectContaining({
|
|
240
|
+
certificate: 'regional-cert',
|
|
241
|
+
loadBalancerName: 'internal-https',
|
|
242
|
+
portRange: '443',
|
|
243
|
+
}),
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('flattens EXTERNAL_MANAGED pipeline payloads with regional network tier and certificate URL', () => {
|
|
248
|
+
const closeModal = vi.fn();
|
|
249
|
+
const regionalCertUrl =
|
|
250
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert';
|
|
193
251
|
const modal = new GceHttpLoadBalancerModal({
|
|
194
252
|
app: application,
|
|
195
253
|
closeModal,
|
|
196
|
-
dismissModal:
|
|
254
|
+
dismissModal: vi.fn(),
|
|
197
255
|
forPipelineConfig: true,
|
|
198
256
|
loadBalancer: {
|
|
199
257
|
account: 'account-a',
|
|
@@ -202,42 +260,86 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
202
260
|
healthChecks: [{ healthCheckType: 'HTTPS', name: 'check-a', port: 443, requestPath: '/health' }],
|
|
203
261
|
listeners: [
|
|
204
262
|
{
|
|
205
|
-
certificate:
|
|
206
|
-
|
|
263
|
+
certificate: regionalCertUrl,
|
|
264
|
+
ipAddress: '203.0.113.10',
|
|
265
|
+
name: 'external-https',
|
|
266
|
+
networkTier: 'STANDARD',
|
|
207
267
|
port: 443,
|
|
208
268
|
protocol: 'HTTPS',
|
|
209
|
-
subnet: 'subnet-a',
|
|
210
269
|
},
|
|
211
270
|
],
|
|
212
|
-
loadBalancerType: '
|
|
213
|
-
name: 'test-app-
|
|
271
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
272
|
+
name: 'test-app-external',
|
|
214
273
|
network: 'network-a',
|
|
215
274
|
region: 'europe-west1',
|
|
216
|
-
subnet: 'subnet-a',
|
|
217
275
|
},
|
|
218
276
|
} as any);
|
|
219
277
|
|
|
220
278
|
(modal as any).submit();
|
|
221
279
|
|
|
222
280
|
expect(closeModal).toHaveBeenCalledWith([
|
|
223
|
-
|
|
224
|
-
certificate:
|
|
225
|
-
loadBalancerName: '
|
|
281
|
+
expect.objectContaining({
|
|
282
|
+
certificate: regionalCertUrl,
|
|
283
|
+
loadBalancerName: 'external-https',
|
|
284
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
285
|
+
network: 'network-a',
|
|
286
|
+
networkTier: 'STANDARD',
|
|
226
287
|
portRange: '443',
|
|
288
|
+
region: 'europe-west1',
|
|
289
|
+
urlMapName: 'test-app-external',
|
|
227
290
|
}),
|
|
228
291
|
]);
|
|
292
|
+
const operations = closeModal.mock.lastCall[0];
|
|
293
|
+
expect((operations as any[])[0].certificateMap).toBeUndefined();
|
|
294
|
+
expect((operations as any[])[0].listeners).toBeUndefined();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('initializes persisted EXTERNAL_MANAGED data with raw listener identity and regional certificate URL', () => {
|
|
298
|
+
const regionalCertUrl =
|
|
299
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert';
|
|
300
|
+
const command = initializeGceHttpLoadBalancerCommand(
|
|
301
|
+
{
|
|
302
|
+
account: 'account-a',
|
|
303
|
+
listeners: [
|
|
304
|
+
{
|
|
305
|
+
certificate: regionalCertUrl,
|
|
306
|
+
ipAddress: '203.0.113.10',
|
|
307
|
+
name: 'external-https',
|
|
308
|
+
networkTier: 'STANDARD',
|
|
309
|
+
port: 443,
|
|
310
|
+
protocol: 'HTTPS',
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
314
|
+
urlMapName: 'test-app-external',
|
|
315
|
+
},
|
|
316
|
+
'edit',
|
|
317
|
+
application,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
expect(command.name).toBe('test-app-external');
|
|
321
|
+
expect(command.loadBalancerType).toBe('EXTERNAL_MANAGED');
|
|
322
|
+
expect(command.listeners[0]).toEqual({
|
|
323
|
+
address: { name: '203.0.113.10' },
|
|
324
|
+
certificate: { name: 'regional-cert', selfLink: regionalCertUrl },
|
|
325
|
+
name: 'external-https',
|
|
326
|
+
networkTier: 'STANDARD',
|
|
327
|
+
portRange: '443',
|
|
328
|
+
protocol: 'HTTPS',
|
|
329
|
+
});
|
|
330
|
+
expect(command.listeners[0].certificateMap).toBeUndefined();
|
|
229
331
|
});
|
|
230
332
|
|
|
231
333
|
(['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
|
|
232
334
|
['pipeline', 'infrastructure'].forEach((submissionMode) => {
|
|
233
335
|
it(`rejects ${loadBalancerType} ${submissionMode} commands with an unresolved path default and non-443 HTTPS port`, () => {
|
|
234
|
-
const closeModal =
|
|
235
|
-
const executeTask =
|
|
336
|
+
const closeModal = vi.fn();
|
|
337
|
+
const executeTask = vi.fn();
|
|
236
338
|
const props = {
|
|
237
339
|
app: application,
|
|
238
340
|
closeModal,
|
|
239
341
|
data: emptyData,
|
|
240
|
-
dismissModal:
|
|
342
|
+
dismissModal: vi.fn(),
|
|
241
343
|
executeTask,
|
|
242
344
|
forPipelineConfig: submissionMode === 'pipeline',
|
|
243
345
|
loadBalancer: {
|
|
@@ -255,38 +357,37 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
255
357
|
},
|
|
256
358
|
mode: submissionMode === 'infrastructure' ? 'create' : undefined,
|
|
257
359
|
} as any;
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
360
|
+
const command = initializeGceHttpLoadBalancerCommand(
|
|
361
|
+
props.loadBalancer,
|
|
362
|
+
submissionMode === 'pipeline' ? 'pipeline' : 'create',
|
|
363
|
+
application,
|
|
364
|
+
);
|
|
365
|
+
command.listeners = [{ ...command.listeners[0], portRange: '8443' }];
|
|
366
|
+
expect(validateGceHttpLoadBalancerCommand(command)).toEqual(
|
|
367
|
+
expect.arrayContaining([
|
|
368
|
+
'Path matcher default backend service is required.',
|
|
369
|
+
'HTTPS listeners must use port 443.',
|
|
370
|
+
]),
|
|
371
|
+
);
|
|
372
|
+
renderWithRouter(<GceHttpLoadBalancerModal {...props} />);
|
|
269
373
|
|
|
270
374
|
expect(executeTask).not.toHaveBeenCalled();
|
|
271
375
|
expect(closeModal).not.toHaveBeenCalled();
|
|
272
|
-
expect(
|
|
273
|
-
|
|
274
|
-
);
|
|
275
|
-
expect(wrapper.find('.gce-http-validation-errors').text()).toContain('HTTPS listeners must use port 443.');
|
|
276
|
-
expect(wrapper.find('.btn-primary').prop('disabled')).toBe(true);
|
|
376
|
+
expect(screen.getByText('Path matcher default backend service is required.')).toBeInTheDocument();
|
|
377
|
+
expect(screen.getByRole('button', { name: submissionMode === 'pipeline' ? 'Done' : 'Create' })).toBeDisabled();
|
|
277
378
|
});
|
|
278
379
|
});
|
|
279
380
|
});
|
|
280
381
|
|
|
281
382
|
['pipeline', 'infrastructure'].forEach((submissionMode) => {
|
|
282
383
|
it(`does not submit invalid ${submissionMode} commands`, () => {
|
|
283
|
-
const closeModal =
|
|
284
|
-
const executeTask =
|
|
384
|
+
const closeModal = vi.fn();
|
|
385
|
+
const executeTask = vi.fn();
|
|
285
386
|
const props = {
|
|
286
387
|
app: application,
|
|
287
388
|
closeModal,
|
|
288
389
|
data: emptyData,
|
|
289
|
-
dismissModal:
|
|
390
|
+
dismissModal: vi.fn(),
|
|
290
391
|
executeTask,
|
|
291
392
|
forPipelineConfig: submissionMode === 'pipeline',
|
|
292
393
|
loadBalancer: {
|
|
@@ -297,30 +398,29 @@ describe('GceHttpLoadBalancerModal', () => {
|
|
|
297
398
|
},
|
|
298
399
|
mode: submissionMode === 'infrastructure' ? 'create' : undefined,
|
|
299
400
|
} as any;
|
|
300
|
-
|
|
301
|
-
const wrapper = shallow(<GceHttpLoadBalancerModal {...props} />);
|
|
302
|
-
|
|
303
|
-
(modal as any).submit();
|
|
401
|
+
renderWithRouter(<GceHttpLoadBalancerModal {...props} />);
|
|
304
402
|
|
|
305
403
|
expect(executeTask).not.toHaveBeenCalled();
|
|
306
404
|
expect(closeModal).not.toHaveBeenCalled();
|
|
307
|
-
expect(
|
|
308
|
-
expect(
|
|
405
|
+
expect(screen.getByText('Name is required.')).toBeInTheDocument();
|
|
406
|
+
expect(screen.getByRole('button', { name: submissionMode === 'pipeline' ? 'Done' : 'Create' })).toBeDisabled();
|
|
309
407
|
});
|
|
310
408
|
});
|
|
311
409
|
|
|
312
410
|
it('renders modal controls as non-submit buttons', () => {
|
|
313
|
-
const
|
|
411
|
+
const { container } = renderWithRouter(
|
|
314
412
|
<GceHttpLoadBalancerModal
|
|
315
413
|
app={application}
|
|
316
|
-
closeModal={
|
|
414
|
+
closeModal={vi.fn()}
|
|
317
415
|
data={emptyData}
|
|
318
|
-
dismissModal={
|
|
416
|
+
dismissModal={vi.fn()}
|
|
319
417
|
loadBalancer={{ account: 'account-a', loadBalancerType: 'HTTP', name: 'test-app-main' } as any}
|
|
320
418
|
mode="edit"
|
|
321
419
|
/>,
|
|
322
420
|
);
|
|
323
421
|
|
|
324
|
-
expect(
|
|
422
|
+
expect(
|
|
423
|
+
Array.from(container.querySelectorAll('button')).every((button) => button.getAttribute('type') === 'button'),
|
|
424
|
+
).toBe(true);
|
|
325
425
|
});
|
|
326
426
|
});
|
|
@@ -43,7 +43,7 @@ export interface IGceHttpLoadBalancerModalProps extends IModalComponentProps {
|
|
|
43
43
|
forPipelineConfig?: boolean;
|
|
44
44
|
isNew?: boolean;
|
|
45
45
|
loadBalancer?: Record<string, unknown>;
|
|
46
|
-
loadBalancerType?: Extract<GceLoadBalancerType, 'HTTP' | 'INTERNAL_MANAGED'>;
|
|
46
|
+
loadBalancerType?: Extract<GceLoadBalancerType, 'HTTP' | 'INTERNAL_MANAGED' | 'EXTERNAL_MANAGED'>;
|
|
47
47
|
mode?: GceLoadBalancerEditorMode;
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -171,7 +171,13 @@ export class GceHttpLoadBalancerModal extends React.Component<
|
|
|
171
171
|
const validationErrors = validateGceHttpLoadBalancerCommand(command);
|
|
172
172
|
const heading =
|
|
173
173
|
command.mode === 'create'
|
|
174
|
-
? `Create ${
|
|
174
|
+
? `Create ${
|
|
175
|
+
command.loadBalancerType === 'INTERNAL_MANAGED'
|
|
176
|
+
? 'Internal managed HTTP(S)'
|
|
177
|
+
: command.loadBalancerType === 'EXTERNAL_MANAGED'
|
|
178
|
+
? 'External managed HTTP(S)'
|
|
179
|
+
: 'HTTP(S)'
|
|
180
|
+
} load balancer`
|
|
175
181
|
: `Edit ${command.name}`;
|
|
176
182
|
|
|
177
183
|
return (
|