@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
|
@@ -26,8 +26,11 @@ export interface IGceHttpLoadBalancerEditorProps {
|
|
|
26
26
|
|
|
27
27
|
interface IGceHttpLoadBalancerDataItem extends IGceLoadBalancerDataItem {
|
|
28
28
|
account?: string;
|
|
29
|
+
address?: string;
|
|
30
|
+
addressType?: string;
|
|
29
31
|
id?: string;
|
|
30
32
|
network?: string;
|
|
33
|
+
purpose?: string;
|
|
31
34
|
region?: string;
|
|
32
35
|
}
|
|
33
36
|
|
|
@@ -63,11 +66,35 @@ export function buildGceHttpLoadBalancerOptions(
|
|
|
63
66
|
data: IGceLoadBalancerData,
|
|
64
67
|
): IGceHttpLoadBalancerOptions {
|
|
65
68
|
const internal = command.loadBalancerType === 'INTERNAL_MANAGED';
|
|
69
|
+
const externalManaged = command.loadBalancerType === 'EXTERNAL_MANAGED';
|
|
66
70
|
const accountMatches = (item: IGceHttpLoadBalancerDataItem): boolean => item.account === command.credentials;
|
|
67
|
-
const locationMatches = (item: IGceHttpLoadBalancerDataItem): boolean =>
|
|
68
|
-
accountMatches(item)
|
|
69
|
-
|
|
71
|
+
const locationMatches = (item: IGceHttpLoadBalancerDataItem): boolean => {
|
|
72
|
+
if (!accountMatches(item)) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
if (internal || externalManaged) {
|
|
76
|
+
return item.region === command.region;
|
|
77
|
+
}
|
|
78
|
+
return item.region === undefined || item.region === 'global';
|
|
79
|
+
};
|
|
80
|
+
const addressMatches = (item: IGceHttpLoadBalancerDataItem): boolean =>
|
|
81
|
+
locationMatches(item) && (!externalManaged || item.addressType === 'EXTERNAL');
|
|
70
82
|
const networks = (data.networks as IGceHttpLoadBalancerDataItem[]).filter(accountMatches);
|
|
83
|
+
const proxyNetworkIds = new Set(
|
|
84
|
+
(data.subnets as IGceHttpLoadBalancerDataItem[])
|
|
85
|
+
.filter(
|
|
86
|
+
(subnet) =>
|
|
87
|
+
accountMatches(subnet) && subnet.region === command.region && subnet.purpose === 'REGIONAL_MANAGED_PROXY',
|
|
88
|
+
)
|
|
89
|
+
.map((subnet) => subnet.network)
|
|
90
|
+
.filter(Boolean),
|
|
91
|
+
);
|
|
92
|
+
const eligibleNetworks = externalManaged
|
|
93
|
+
? networks.filter(
|
|
94
|
+
(network) =>
|
|
95
|
+
isAccountLocalNetwork(network) && (proxyNetworkIds.has(network.id) || proxyNetworkIds.has(network.name)),
|
|
96
|
+
)
|
|
97
|
+
: networks;
|
|
71
98
|
const selectedNetwork = networks.find(
|
|
72
99
|
(network) => network.name === command.network?.name || network.id === command.network?.name,
|
|
73
100
|
);
|
|
@@ -83,7 +110,7 @@ export function buildGceHttpLoadBalancerOptions(
|
|
|
83
110
|
return {
|
|
84
111
|
accounts: mergeGceResourceOptions(data.accounts, command.credentials ? [{ name: command.credentials }] : []),
|
|
85
112
|
addresses: mergeGceResourceOptions(
|
|
86
|
-
(data.addresses as IGceHttpLoadBalancerDataItem[]).filter(
|
|
113
|
+
(data.addresses as IGceHttpLoadBalancerDataItem[]).filter(addressMatches),
|
|
87
114
|
uniqueOptions(command.listeners.flatMap((listener) => (listener.address ? [listener.address] : []))),
|
|
88
115
|
),
|
|
89
116
|
backendServices: mergeGceResourceOptions(
|
|
@@ -102,7 +129,7 @@ export function buildGceHttpLoadBalancerOptions(
|
|
|
102
129
|
(data.healthChecks as IGceHttpLoadBalancerDataItem[]).filter(locationMatches),
|
|
103
130
|
uniqueOptions([...command.healthChecks, ...backendHealthChecks]),
|
|
104
131
|
),
|
|
105
|
-
networks: mergeGceResourceOptions(
|
|
132
|
+
networks: mergeGceResourceOptions(eligibleNetworks, command.network ? [command.network] : []),
|
|
106
133
|
regions: mergeGceResourceOptions(data.regions, command.region ? [{ name: command.region }] : []),
|
|
107
134
|
subnets: mergeGceResourceOptions(
|
|
108
135
|
internal
|
|
@@ -119,25 +146,51 @@ export function buildGceHttpLoadBalancerOptions(
|
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
export function constrainGceHttpLoadBalancerCommand(command: IGceLoadBalancerCommand): IGceLoadBalancerCommand {
|
|
122
|
-
const constrainListener = (
|
|
149
|
+
const constrainListener = (
|
|
150
|
+
{ subnet, networkTier, ...listener }: IGceLoadBalancerListener,
|
|
151
|
+
keepSubnet: boolean,
|
|
152
|
+
keepNetworkTier: boolean,
|
|
153
|
+
) => {
|
|
154
|
+
const constrainedNetworkTier =
|
|
155
|
+
keepNetworkTier && (networkTier || (listener.address?.networkTier as string | undefined) || 'PREMIUM');
|
|
123
156
|
if (listener.protocol === 'HTTPS') {
|
|
124
|
-
return {
|
|
157
|
+
return {
|
|
158
|
+
...listener,
|
|
159
|
+
portRange: '443',
|
|
160
|
+
protocol: 'HTTPS' as const,
|
|
161
|
+
...(keepSubnet && subnet ? { subnet } : {}),
|
|
162
|
+
...(constrainedNetworkTier ? { networkTier: constrainedNetworkTier } : {}),
|
|
163
|
+
};
|
|
125
164
|
}
|
|
126
|
-
const { certificate: _certificate, ...plaintextListener } = listener;
|
|
127
|
-
return {
|
|
165
|
+
const { certificate: _certificate, certificateMap: _certificateMap, ...plaintextListener } = listener;
|
|
166
|
+
return {
|
|
167
|
+
...plaintextListener,
|
|
168
|
+
protocol: 'HTTP' as const,
|
|
169
|
+
...(keepSubnet && subnet ? { subnet } : {}),
|
|
170
|
+
...(constrainedNetworkTier ? { networkTier: constrainedNetworkTier } : {}),
|
|
171
|
+
};
|
|
128
172
|
};
|
|
129
173
|
|
|
130
174
|
if (command.loadBalancerType === 'INTERNAL_MANAGED') {
|
|
131
175
|
return {
|
|
132
176
|
...command,
|
|
133
|
-
listeners: command.listeners.map((listener) => constrainListener(listener, true)),
|
|
177
|
+
listeners: command.listeners.map((listener) => constrainListener(listener, true, false)),
|
|
134
178
|
region: command.region === 'global' ? '' : command.region,
|
|
135
179
|
};
|
|
136
180
|
}
|
|
137
181
|
|
|
182
|
+
if (command.loadBalancerType === 'EXTERNAL_MANAGED') {
|
|
183
|
+
return {
|
|
184
|
+
...command,
|
|
185
|
+
listeners: command.listeners.map((listener) => constrainListener(listener, false, true)),
|
|
186
|
+
region: command.region === 'global' ? '' : command.region,
|
|
187
|
+
subnet: undefined,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
138
191
|
return {
|
|
139
192
|
...command,
|
|
140
|
-
listeners: command.listeners.map((listener) => constrainListener(listener, false)),
|
|
193
|
+
listeners: command.listeners.map((listener) => constrainListener(listener, false, false)),
|
|
141
194
|
loadBalancerType: 'HTTP',
|
|
142
195
|
network: undefined,
|
|
143
196
|
region: 'global',
|
|
@@ -148,15 +201,21 @@ export function constrainGceHttpLoadBalancerCommand(command: IGceLoadBalancerCom
|
|
|
148
201
|
export function validateGceHttpLoadBalancerCommand(command: IGceLoadBalancerCommand): string[] {
|
|
149
202
|
const errors = new Set<string>();
|
|
150
203
|
const internal = command.loadBalancerType === 'INTERNAL_MANAGED';
|
|
204
|
+
const externalManaged = command.loadBalancerType === 'EXTERNAL_MANAGED';
|
|
151
205
|
|
|
152
206
|
if (!command.name.trim()) errors.add('Name is required.');
|
|
153
207
|
if (!command.credentials) errors.add('Account is required.');
|
|
154
|
-
if (internal) {
|
|
208
|
+
if (internal || externalManaged) {
|
|
155
209
|
if (!command.region || command.region === 'global') {
|
|
156
|
-
errors.add(
|
|
210
|
+
errors.add(`Region is required for ${internal ? 'INTERNAL_MANAGED' : 'EXTERNAL_MANAGED'} load balancers.`);
|
|
211
|
+
}
|
|
212
|
+
if (!command.network?.name) {
|
|
213
|
+
errors.add(`Network is required for ${internal ? 'INTERNAL_MANAGED' : 'EXTERNAL_MANAGED'} load balancers.`);
|
|
157
214
|
}
|
|
158
|
-
if (
|
|
159
|
-
|
|
215
|
+
if (externalManaged && command.network && !isAccountLocalNetwork(command.network)) {
|
|
216
|
+
errors.add('Shared VPC networks are not supported for EXTERNAL_MANAGED load balancers.');
|
|
217
|
+
}
|
|
218
|
+
if (internal && !command.subnet?.name) errors.add('Subnet is required for INTERNAL_MANAGED load balancers.');
|
|
160
219
|
} else if (command.region !== 'global') {
|
|
161
220
|
errors.add('HTTP load balancers must use the global location.');
|
|
162
221
|
}
|
|
@@ -176,6 +235,24 @@ export function validateGceHttpLoadBalancerCommand(command: IGceLoadBalancerComm
|
|
|
176
235
|
if (listener.protocol === 'HTTPS' && listener.portRange !== '443') {
|
|
177
236
|
errors.add('HTTPS listeners must use port 443.');
|
|
178
237
|
}
|
|
238
|
+
if (externalManaged && listener.certificateMap) {
|
|
239
|
+
errors.add('Certificate maps are not supported for EXTERNAL_MANAGED load balancers.');
|
|
240
|
+
}
|
|
241
|
+
if (externalManaged && listener.address?.networkTier && listener.networkTier !== listener.address.networkTier) {
|
|
242
|
+
errors.add('Reserved IP address network tier must match the listener network tier.');
|
|
243
|
+
}
|
|
244
|
+
if (externalManaged && command.mode === 'edit') {
|
|
245
|
+
const original = command.original?.listeners.find(({ name }) => name === listener.name);
|
|
246
|
+
if (
|
|
247
|
+
original &&
|
|
248
|
+
(listener.portRange !== original.portRange ||
|
|
249
|
+
referenceIdentity(listener.address) !== referenceIdentity(original.address) ||
|
|
250
|
+
listener.networkTier !== original.networkTier ||
|
|
251
|
+
listener.protocol !== original.protocol)
|
|
252
|
+
) {
|
|
253
|
+
errors.add('Rename the listener to change its port, address, network tier, or HTTP/HTTPS protocol.');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
179
256
|
});
|
|
180
257
|
|
|
181
258
|
if (!command.backendServices.length) errors.add('At least one backend service is required.');
|
|
@@ -183,6 +260,9 @@ export function validateGceHttpLoadBalancerCommand(command: IGceLoadBalancerComm
|
|
|
183
260
|
if (!backendService.name.trim()) errors.add('Backend service name is required.');
|
|
184
261
|
if (!backendService.healthCheck?.name) errors.add('Each backend service requires a health check.');
|
|
185
262
|
if (!backendService.portName) errors.add('Backend service port name is required.');
|
|
263
|
+
if (externalManaged && !['HTTP', 'HTTPS'].includes(String(backendService.protocol || 'HTTP'))) {
|
|
264
|
+
errors.add('Backend service protocol must be HTTP or HTTPS.');
|
|
265
|
+
}
|
|
186
266
|
if (negative(backendService.connectionDrainingTimeoutSec)) {
|
|
187
267
|
errors.add('Connection draining timeout must be zero or greater.');
|
|
188
268
|
}
|
|
@@ -240,6 +320,14 @@ function validPort(value: unknown): boolean {
|
|
|
240
320
|
return Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
241
321
|
}
|
|
242
322
|
|
|
323
|
+
function isAccountLocalNetwork(reference: { id?: unknown; name?: unknown; selfLink?: unknown }): boolean {
|
|
324
|
+
return !/^[^/]+\/[^/]+$/.test(String(reference.id || reference.selfLink || reference.name || ''));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function referenceIdentity(reference: IGceResourceReference | undefined): string {
|
|
328
|
+
return String(reference?.selfLink || reference?.name || '');
|
|
329
|
+
}
|
|
330
|
+
|
|
243
331
|
function negative(value: unknown): boolean {
|
|
244
332
|
return value !== undefined && Number(value) < 0;
|
|
245
333
|
}
|
|
@@ -268,6 +356,13 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
268
356
|
const update = (updates: Partial<IGceLoadBalancerCommand>): void => {
|
|
269
357
|
onChange(constrainGceHttpLoadBalancerCommand({ ...command, ...updates } as IGceLoadBalancerCommand));
|
|
270
358
|
};
|
|
359
|
+
// EXTERNAL_MANAGED reserved addresses belong to one account and region, so a scope change drops them.
|
|
360
|
+
const updateScope = (updates: Partial<IGceLoadBalancerCommand>): void =>
|
|
361
|
+
update(
|
|
362
|
+
command.loadBalancerType === 'EXTERNAL_MANAGED'
|
|
363
|
+
? { ...updates, listeners: command.listeners.map((listener) => ({ ...listener, address: undefined })) }
|
|
364
|
+
: updates,
|
|
365
|
+
);
|
|
271
366
|
|
|
272
367
|
const updateBackendService = (
|
|
273
368
|
index: number,
|
|
@@ -342,6 +437,7 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
342
437
|
>
|
|
343
438
|
<option value="HTTP">HTTP(S)</option>
|
|
344
439
|
<option value="INTERNAL_MANAGED">Internal managed HTTP</option>
|
|
440
|
+
<option value="EXTERNAL_MANAGED">External managed HTTP(S)</option>
|
|
345
441
|
</select>
|
|
346
442
|
</FormRow>
|
|
347
443
|
<FormRow label="Account">
|
|
@@ -351,7 +447,7 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
351
447
|
disabled={editing}
|
|
352
448
|
required
|
|
353
449
|
value={command.credentials}
|
|
354
|
-
onChange={(event) =>
|
|
450
|
+
onChange={(event) => updateScope({ credentials: event.target.value })}
|
|
355
451
|
>
|
|
356
452
|
<option value="">Select...</option>
|
|
357
453
|
{options.accounts.map((account) => (
|
|
@@ -361,7 +457,7 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
361
457
|
))}
|
|
362
458
|
</select>
|
|
363
459
|
</FormRow>
|
|
364
|
-
{command.loadBalancerType === 'INTERNAL_MANAGED' && (
|
|
460
|
+
{(command.loadBalancerType === 'INTERNAL_MANAGED' || command.loadBalancerType === 'EXTERNAL_MANAGED') && (
|
|
365
461
|
<React.Fragment>
|
|
366
462
|
<FormRow label="Region">
|
|
367
463
|
<select
|
|
@@ -370,7 +466,7 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
370
466
|
disabled={editing}
|
|
371
467
|
required
|
|
372
468
|
value={command.region}
|
|
373
|
-
onChange={(event) =>
|
|
469
|
+
onChange={(event) => updateScope({ region: event.target.value })}
|
|
374
470
|
>
|
|
375
471
|
<option value="">Select...</option>
|
|
376
472
|
{options.regions.map((region) => (
|
|
@@ -384,6 +480,7 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
384
480
|
<select
|
|
385
481
|
className="form-control input-sm"
|
|
386
482
|
data-testid="network"
|
|
483
|
+
disabled={editing && command.loadBalancerType === 'EXTERNAL_MANAGED'}
|
|
387
484
|
required
|
|
388
485
|
value={command.network?.name || ''}
|
|
389
486
|
onChange={(event) => update({ network: selectedReference(event.target.value, options.networks) })}
|
|
@@ -396,28 +493,30 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
396
493
|
))}
|
|
397
494
|
</select>
|
|
398
495
|
</FormRow>
|
|
399
|
-
|
|
400
|
-
<
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{subnet.name}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
496
|
+
{command.loadBalancerType === 'INTERNAL_MANAGED' && (
|
|
497
|
+
<FormRow label="Subnet">
|
|
498
|
+
<select
|
|
499
|
+
className="form-control input-sm"
|
|
500
|
+
data-testid="subnet"
|
|
501
|
+
required
|
|
502
|
+
value={command.subnet?.name || ''}
|
|
503
|
+
onChange={(event) => {
|
|
504
|
+
const subnet = selectedReference(event.target.value, options.subnets);
|
|
505
|
+
update({
|
|
506
|
+
subnet,
|
|
507
|
+
listeners: command.listeners.map((listener) => ({ ...listener, subnet })),
|
|
508
|
+
});
|
|
509
|
+
}}
|
|
510
|
+
>
|
|
511
|
+
<option value="">Select...</option>
|
|
512
|
+
{options.subnets.map((subnet) => (
|
|
513
|
+
<option key={subnet.name} value={subnet.name}>
|
|
514
|
+
{subnet.name}
|
|
515
|
+
</option>
|
|
516
|
+
))}
|
|
517
|
+
</select>
|
|
518
|
+
</FormRow>
|
|
519
|
+
)}
|
|
421
520
|
</React.Fragment>
|
|
422
521
|
)}
|
|
423
522
|
</section>
|
|
@@ -479,7 +578,15 @@ export function GceHttpLoadBalancerEditor({ command, data, onChange }: IGceHttpL
|
|
|
479
578
|
className="add-new btn btn-block"
|
|
480
579
|
onClick={() =>
|
|
481
580
|
update({
|
|
482
|
-
backendServices: [
|
|
581
|
+
backendServices: [
|
|
582
|
+
...command.backendServices,
|
|
583
|
+
{
|
|
584
|
+
name: '',
|
|
585
|
+
portName: 'http',
|
|
586
|
+
...(command.loadBalancerType === 'EXTERNAL_MANAGED' ? { protocol: 'HTTP' as const } : {}),
|
|
587
|
+
sessionAffinity: 'NONE',
|
|
588
|
+
},
|
|
589
|
+
],
|
|
483
590
|
})
|
|
484
591
|
}
|
|
485
592
|
>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { fireEvent, render, within } from '@testing-library/react';
|
|
1
2
|
import React from 'react';
|
|
2
|
-
import { shallow } from 'enzyme';
|
|
3
3
|
|
|
4
4
|
import { GceHttpLoadBalancerListenerEditor } from './GceHttpLoadBalancerListenerEditor';
|
|
5
|
+
import { buildGceLoadBalancerJobs } from '../common';
|
|
5
6
|
|
|
6
7
|
describe('GceHttpLoadBalancerListenerEditor', () => {
|
|
7
8
|
it('edits listener addresses and certificates without submitting the parent form', () => {
|
|
8
|
-
const onChange =
|
|
9
|
-
const
|
|
9
|
+
const onChange = vi.fn();
|
|
10
|
+
const { getByTestId, getAllByRole } = render(
|
|
10
11
|
<GceHttpLoadBalancerListenerEditor
|
|
11
12
|
addresses={[{ name: 'removed-address', selfLink: 'https://compute/addresses/removed-address' }]}
|
|
12
13
|
certificates={[{ name: 'removed-cert', selfLink: 'https://compute/certificates/removed-cert' }]}
|
|
@@ -19,27 +20,23 @@ describe('GceHttpLoadBalancerListenerEditor', () => {
|
|
|
19
20
|
}}
|
|
20
21
|
loadBalancerType="HTTP"
|
|
21
22
|
onChange={onChange}
|
|
22
|
-
onRemove={
|
|
23
|
+
onRemove={vi.fn()}
|
|
23
24
|
subnets={[]}
|
|
24
25
|
/>,
|
|
25
26
|
);
|
|
26
27
|
|
|
27
|
-
expect(
|
|
28
|
-
|
|
29
|
-
);
|
|
30
|
-
expect(wrapper.find('[data-testid="listener-certificate"] option').map((option) => option.prop('value'))).toContain(
|
|
31
|
-
'removed-cert',
|
|
32
|
-
);
|
|
33
|
-
expect(wrapper.find('button').everyWhere((button) => button.prop('type') === 'button')).toBe(true);
|
|
28
|
+
expect(optionValues(getByTestId('listener-address'))).toContain('removed-address');
|
|
29
|
+
expect(optionValues(getByTestId('listener-certificate'))).toContain('removed-cert');
|
|
30
|
+
expect(getAllByRole('button').every((button) => button.getAttribute('type') === 'button')).toBe(true);
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
fireEvent.change(getByTestId('listener-address'), { target: { value: '' } });
|
|
36
33
|
expect(onChange).toHaveBeenCalledWith(
|
|
37
|
-
|
|
34
|
+
expect.objectContaining({ address: undefined, certificate: expect.any(Object), name: 'frontend' }),
|
|
38
35
|
);
|
|
39
36
|
});
|
|
40
37
|
|
|
41
38
|
it('supports HTTPS certificates for INTERNAL_MANAGED listeners', () => {
|
|
42
|
-
const
|
|
39
|
+
const { getByTestId } = render(
|
|
43
40
|
<GceHttpLoadBalancerListenerEditor
|
|
44
41
|
addresses={[]}
|
|
45
42
|
certificates={[{ name: 'regional-cert' }]}
|
|
@@ -51,44 +48,180 @@ describe('GceHttpLoadBalancerListenerEditor', () => {
|
|
|
51
48
|
subnet: { name: 'subnet-a' },
|
|
52
49
|
}}
|
|
53
50
|
loadBalancerType="INTERNAL_MANAGED"
|
|
54
|
-
onChange={
|
|
55
|
-
onRemove={
|
|
51
|
+
onChange={vi.fn()}
|
|
52
|
+
onRemove={vi.fn()}
|
|
56
53
|
subnets={[{ name: 'subnet-a' }]}
|
|
57
54
|
/>,
|
|
58
55
|
);
|
|
59
56
|
|
|
60
|
-
expect(
|
|
61
|
-
|
|
62
|
-
'HTTPS',
|
|
63
|
-
]);
|
|
64
|
-
expect(wrapper.find('[data-testid="listener-certificate"]').prop('value')).toBe('regional-cert');
|
|
57
|
+
expect(optionValues(getByTestId('listener-protocol'))).toEqual(['HTTP', 'HTTPS']);
|
|
58
|
+
expect(getByTestId('listener-certificate')).toHaveValue('regional-cert');
|
|
65
59
|
});
|
|
66
60
|
|
|
67
61
|
(['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
|
|
68
62
|
it(`sets and locks port 443 for ${loadBalancerType} HTTPS listeners`, () => {
|
|
69
|
-
const onChange =
|
|
70
|
-
const
|
|
63
|
+
const onChange = vi.fn();
|
|
64
|
+
const props = {
|
|
65
|
+
addresses: [],
|
|
66
|
+
certificates: [{ name: 'cert-a' }],
|
|
67
|
+
listener: { name: 'frontend', portRange: '80', protocol: 'HTTP' as const },
|
|
68
|
+
loadBalancerType,
|
|
69
|
+
onChange,
|
|
70
|
+
onRemove: vi.fn(),
|
|
71
|
+
subnets: [],
|
|
72
|
+
};
|
|
73
|
+
const { getByTestId, rerender } = render(<GceHttpLoadBalancerListenerEditor {...props} />);
|
|
74
|
+
|
|
75
|
+
fireEvent.change(getByTestId('listener-protocol'), { target: { value: 'HTTPS' } });
|
|
76
|
+
|
|
77
|
+
expect(onChange).toHaveBeenCalledWith(
|
|
78
|
+
expect.objectContaining({ name: 'frontend', portRange: '443', protocol: 'HTTPS' }),
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
rerender(
|
|
71
82
|
<GceHttpLoadBalancerListenerEditor
|
|
72
|
-
|
|
73
|
-
|
|
83
|
+
{...props}
|
|
84
|
+
listener={{ certificate: { name: 'cert-a' }, name: 'frontend', portRange: '443', protocol: 'HTTPS' }}
|
|
85
|
+
/>,
|
|
86
|
+
);
|
|
87
|
+
expect(getByTestId('listener-port')).toBeDisabled();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('edits EXTERNAL_MANAGED listener addresses, certificates, and network tier', () => {
|
|
92
|
+
const onChange = vi.fn();
|
|
93
|
+
const { getByLabelText, getByTestId } = render(
|
|
94
|
+
<GceHttpLoadBalancerListenerEditor
|
|
95
|
+
addresses={[{ address: '203.0.113.10', name: 'external-address' }]}
|
|
96
|
+
certificates={[
|
|
97
|
+
{
|
|
98
|
+
name: 'regional-cert',
|
|
99
|
+
selfLink:
|
|
100
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert',
|
|
101
|
+
},
|
|
102
|
+
]}
|
|
103
|
+
listener={{
|
|
104
|
+
address: { name: 'external-address', address: '203.0.113.10' },
|
|
105
|
+
certificate: {
|
|
106
|
+
name: 'regional-cert',
|
|
107
|
+
selfLink:
|
|
108
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert',
|
|
109
|
+
},
|
|
110
|
+
name: 'app-https',
|
|
111
|
+
networkTier: 'STANDARD',
|
|
112
|
+
portRange: '443',
|
|
113
|
+
protocol: 'HTTPS',
|
|
114
|
+
}}
|
|
115
|
+
loadBalancerType="EXTERNAL_MANAGED"
|
|
116
|
+
onChange={onChange}
|
|
117
|
+
onRemove={vi.fn()}
|
|
118
|
+
subnets={[]}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
expect(optionValues(getByTestId('listener-address'))).toContain('external-address');
|
|
123
|
+
expect(optionValues(getByTestId('listener-certificate'))).toContain('regional-cert');
|
|
124
|
+
const networkTier = getByLabelText('Network tier');
|
|
125
|
+
expect(networkTier).toHaveValue('STANDARD');
|
|
126
|
+
|
|
127
|
+
fireEvent.change(networkTier, { target: { value: 'PREMIUM' } });
|
|
128
|
+
expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ networkTier: 'PREMIUM' }));
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('does not expose certificate map controls for EXTERNAL_MANAGED listeners', () => {
|
|
132
|
+
const { queryByLabelText, queryByTestId } = render(
|
|
133
|
+
<GceHttpLoadBalancerListenerEditor
|
|
134
|
+
addresses={[]}
|
|
135
|
+
certificates={[{ name: 'regional-cert' }]}
|
|
136
|
+
listener={{ name: 'frontend', portRange: '443', protocol: 'HTTPS' }}
|
|
137
|
+
loadBalancerType="EXTERNAL_MANAGED"
|
|
138
|
+
onChange={vi.fn()}
|
|
139
|
+
onRemove={vi.fn()}
|
|
140
|
+
subnets={[]}
|
|
141
|
+
/>,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(queryByTestId('listener-certificate-map')).not.toBeInTheDocument();
|
|
145
|
+
expect(queryByLabelText('Certificate map')).not.toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('accepts a direct Certificate Manager URL while preserving selectable Compute certificates', () => {
|
|
149
|
+
const onChange = vi.fn();
|
|
150
|
+
const certificateUrl =
|
|
151
|
+
'//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/manager-cert';
|
|
152
|
+
const props = {
|
|
153
|
+
addresses: [],
|
|
154
|
+
certificates: [{ name: 'compute-cert', selfLink: 'https://compute/sslCertificates/compute-cert' }],
|
|
155
|
+
listener: { name: 'external-https', portRange: '443', protocol: 'HTTPS' as const },
|
|
156
|
+
loadBalancerType: 'EXTERNAL_MANAGED' as const,
|
|
157
|
+
onChange,
|
|
158
|
+
onRemove: vi.fn(),
|
|
159
|
+
subnets: [],
|
|
160
|
+
};
|
|
161
|
+
const { getByLabelText, getByTestId, rerender } = render(<GceHttpLoadBalancerListenerEditor {...props} />);
|
|
162
|
+
|
|
163
|
+
expect(optionValues(getByTestId('listener-certificate'))).toContain('compute-cert');
|
|
164
|
+
fireEvent.change(getByTestId('listener-certificate'), { target: { value: 'compute-cert' } });
|
|
165
|
+
const computeListener = onChange.mock.lastCall[0];
|
|
166
|
+
expect(computeListener.certificate).toEqual({
|
|
167
|
+
name: 'compute-cert',
|
|
168
|
+
selfLink: 'https://compute/sslCertificates/compute-cert',
|
|
169
|
+
});
|
|
170
|
+
rerender(<GceHttpLoadBalancerListenerEditor {...props} listener={computeListener} />);
|
|
171
|
+
expect(getByTestId('listener-certificate-manager-url')).toHaveValue('');
|
|
172
|
+
|
|
173
|
+
const certificateUrlInput = getByLabelText('Certificate Manager resource URL');
|
|
174
|
+
expect(certificateUrlInput).toBe(getByTestId('listener-certificate-manager-url'));
|
|
175
|
+
fireEvent.change(certificateUrlInput, { target: { value: '//certificate' } });
|
|
176
|
+
rerender(<GceHttpLoadBalancerListenerEditor {...props} listener={onChange.mock.lastCall[0]} />);
|
|
177
|
+
expect(getByTestId('listener-certificate-manager-url')).toHaveValue('//certificate');
|
|
178
|
+
|
|
179
|
+
fireEvent.change(getByTestId('listener-certificate-manager-url'), { target: { value: certificateUrl } });
|
|
180
|
+
|
|
181
|
+
const listener = onChange.mock.lastCall[0];
|
|
182
|
+
expect(listener.certificate).toEqual({ name: 'manager-cert', selfLink: certificateUrl });
|
|
183
|
+
expect(listener.certificateMap).toBeUndefined();
|
|
184
|
+
|
|
185
|
+
const operations = buildGceLoadBalancerJobs({
|
|
186
|
+
backendServices: [{ healthCheck: { name: 'check-a' }, name: 'backend-a', portName: 'http' }],
|
|
187
|
+
credentials: 'account-a',
|
|
188
|
+
defaultService: { name: 'backend-a' },
|
|
189
|
+
healthChecks: [{ healthCheckType: 'HTTPS', name: 'check-a', port: 443, requestPath: '/health' }],
|
|
190
|
+
hostRules: [],
|
|
191
|
+
listeners: [listener],
|
|
192
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
193
|
+
mode: 'pipeline',
|
|
194
|
+
name: 'app-main',
|
|
195
|
+
network: { name: 'default' },
|
|
196
|
+
region: 'europe-west1',
|
|
197
|
+
} as any);
|
|
198
|
+
expect(operations[0].certificate).toBe(certificateUrl);
|
|
199
|
+
expect(operations[0].certificateMap).toBeUndefined();
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
(['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
|
|
203
|
+
it(`does not propagate address networkTier for ${loadBalancerType} listeners`, () => {
|
|
204
|
+
const onChange = vi.fn();
|
|
205
|
+
const { getByTestId } = render(
|
|
206
|
+
<GceHttpLoadBalancerListenerEditor
|
|
207
|
+
addresses={[{ address: '203.0.113.10', name: 'external-address', networkTier: 'STANDARD' }]}
|
|
208
|
+
certificates={[]}
|
|
74
209
|
listener={{ name: 'frontend', portRange: '80', protocol: 'HTTP' }}
|
|
75
210
|
loadBalancerType={loadBalancerType}
|
|
76
211
|
onChange={onChange}
|
|
77
|
-
onRemove={
|
|
212
|
+
onRemove={vi.fn()}
|
|
78
213
|
subnets={[]}
|
|
79
214
|
/>,
|
|
80
215
|
);
|
|
81
216
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expect(onChange).toHaveBeenCalledWith(
|
|
85
|
-
jasmine.objectContaining({ name: 'frontend', portRange: '443', protocol: 'HTTPS' }),
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
wrapper.setProps({
|
|
89
|
-
listener: { certificate: { name: 'cert-a' }, name: 'frontend', portRange: '443', protocol: 'HTTPS' },
|
|
90
|
-
});
|
|
91
|
-
expect(wrapper.find('[data-testid="listener-port"]').prop('disabled')).toBe(true);
|
|
217
|
+
fireEvent.change(getByTestId('listener-address'), { target: { value: 'external-address' } });
|
|
218
|
+
expect(onChange.mock.lastCall[0].networkTier).toBeUndefined();
|
|
92
219
|
});
|
|
93
220
|
});
|
|
94
221
|
});
|
|
222
|
+
|
|
223
|
+
function optionValues(select: HTMLElement): string[] {
|
|
224
|
+
return within(select)
|
|
225
|
+
.getAllByRole('option')
|
|
226
|
+
.map((option) => (option as HTMLOptionElement).value);
|
|
227
|
+
}
|