@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
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
CloudProviderRegistry,
|
|
7
7
|
CollapsibleSection,
|
|
8
8
|
ConfirmationModalService,
|
|
9
|
+
HelpField,
|
|
9
10
|
InfrastructureCaches,
|
|
10
11
|
LoadBalancerWriter,
|
|
11
12
|
ManagedMenuItem,
|
|
@@ -36,14 +37,21 @@ function isSameVpc(candidate: any, params: any): boolean {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
function findLoadBalancerInList(loadBalancers: any[], params: any): any {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
candidate.name === params.name &&
|
|
40
|
+
const inScope = (loadBalancers || []).filter(
|
|
41
|
+
(candidate: any) =>
|
|
42
42
|
candidate.account === params.accountId &&
|
|
43
43
|
(candidate.region === params.region || candidate.region === 'global') &&
|
|
44
|
-
isSameVpc(candidate, params)
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
isSameVpc(candidate, params),
|
|
45
|
+
);
|
|
46
|
+
// EXTERNAL_MANAGED rows carry a decorated display name; links built from the raw URL map name
|
|
47
|
+
// still resolve once no exact name matches.
|
|
48
|
+
return (
|
|
49
|
+
inScope.find((candidate: any) => candidate.name === params.name) ||
|
|
50
|
+
inScope.find(
|
|
51
|
+
(candidate: any) =>
|
|
52
|
+
gceHttpLoadBalancerUtils.isRegionalHttpLoadBalancer(candidate) && candidate.urlMapName === params.name,
|
|
53
|
+
)
|
|
54
|
+
);
|
|
47
55
|
}
|
|
48
56
|
|
|
49
57
|
function uniqBy<T>(items: T[], getKey: (item: T) => string): T[] {
|
|
@@ -58,11 +66,18 @@ function uniqBy<T>(items: T[], getKey: (item: T) => string): T[] {
|
|
|
58
66
|
});
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
function getProtocol(loadBalancer: any): string {
|
|
69
|
+
function getProtocol(loadBalancer: any, httpFamily = false): string | undefined {
|
|
70
|
+
if (!httpFamily) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
62
73
|
const port = loadBalancer?.listenerDescriptions?.[0]?.listener?.loadBalancerPort;
|
|
63
74
|
return port === '443' ? 'https:' : 'http:';
|
|
64
75
|
}
|
|
65
76
|
|
|
77
|
+
function formatDns(dns: { dnsname: string; protocol?: string }): string {
|
|
78
|
+
return dns.protocol ? `${dns.protocol}//${dns.dnsname}` : dns.dnsname;
|
|
79
|
+
}
|
|
80
|
+
|
|
66
81
|
function getBackendServices(loadBalancer: any): any[] {
|
|
67
82
|
let backendServices = [loadBalancer.defaultService].filter(Boolean);
|
|
68
83
|
|
|
@@ -83,6 +98,7 @@ function getBackendServices(loadBalancer: any): any[] {
|
|
|
83
98
|
function getResourceTypes(loadBalancerType: string): string[] {
|
|
84
99
|
switch (loadBalancerType) {
|
|
85
100
|
case 'INTERNAL':
|
|
101
|
+
case 'REGIONAL_EXTERNAL_NETWORK':
|
|
86
102
|
return ['gce_forwarding_rule', 'gce_backend_service'];
|
|
87
103
|
case 'NETWORK':
|
|
88
104
|
return ['gce_forwarding_rule', 'gce_target_pool', 'gce_health_check'];
|
|
@@ -90,16 +106,23 @@ function getResourceTypes(loadBalancerType: string): string[] {
|
|
|
90
106
|
case 'TCP':
|
|
91
107
|
return ['gce_forwarding_rule', 'gce_backend_service'];
|
|
92
108
|
case 'INTERNAL_MANAGED':
|
|
109
|
+
case 'EXTERNAL_MANAGED':
|
|
93
110
|
return ['http_load_balancer', 'gce_target_http_proxy', 'gce_url_map', 'gce_backend_service'];
|
|
94
111
|
default:
|
|
95
112
|
return ['http_load_balancer', 'gce_target_http_proxy', 'gce_url_map', 'gce_backend_service'];
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
function getLogSearchName(loadBalancer: any): string {
|
|
117
|
+
return gceHttpLoadBalancerUtils.isRegionalHttpLoadBalancer(loadBalancer)
|
|
118
|
+
? loadBalancer.urlMapName || loadBalancer.name
|
|
119
|
+
: loadBalancer.name;
|
|
120
|
+
}
|
|
121
|
+
|
|
99
122
|
function buildLogsLink(project: string, loadBalancer: any): string {
|
|
100
123
|
return `https://console.developers.google.com/project/${project}/logs?advancedFilter=resource.type=(${getResourceTypes(
|
|
101
124
|
loadBalancer.loadBalancerType,
|
|
102
|
-
).join(' OR ')})%0A"${loadBalancer
|
|
125
|
+
).join(' OR ')})%0A"${getLogSearchName(loadBalancer)}"`;
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
async function loadDetails(loadBalancer: any, loadBalancerParams: any, loadBalancerReader: any): Promise<any[]> {
|
|
@@ -126,7 +149,7 @@ async function loadDetails(loadBalancer: any, loadBalancerParams: any, loadBalan
|
|
|
126
149
|
|
|
127
150
|
representativeLoadBalancer.dns = uniqBy(
|
|
128
151
|
loadBalancers
|
|
129
|
-
.map((detail: any) => ({ dnsname: detail.dnsname, protocol: getProtocol(detail) }))
|
|
152
|
+
.map((detail: any) => ({ dnsname: detail.dnsname, protocol: getProtocol(detail, true) }))
|
|
130
153
|
.filter((detail: any) => detail.dnsname),
|
|
131
154
|
(detail: any) => detail.dnsname,
|
|
132
155
|
);
|
|
@@ -143,7 +166,10 @@ async function loadDetails(loadBalancer: any, loadBalancerParams: any, loadBalan
|
|
|
143
166
|
loadBalancer.name,
|
|
144
167
|
);
|
|
145
168
|
if (details[0]) {
|
|
146
|
-
details[0].dns = {
|
|
169
|
+
details[0].dns = {
|
|
170
|
+
dnsname: details[0].dnsname,
|
|
171
|
+
protocol: getProtocol(details[0], loadBalancer.loadBalancerType !== 'REGIONAL_EXTERNAL_NETWORK'),
|
|
172
|
+
};
|
|
147
173
|
}
|
|
148
174
|
return details;
|
|
149
175
|
}
|
|
@@ -175,6 +201,11 @@ export async function loadGceLoadBalancerDetails({
|
|
|
175
201
|
loadBalancer.elb.backendServices.map((backendService: any) => backendService.healthCheck).filter(Boolean),
|
|
176
202
|
(healthCheck: any) => healthCheck.name || healthCheck.selfLink || '',
|
|
177
203
|
);
|
|
204
|
+
} else if (loadBalancer.loadBalancerType === 'REGIONAL_EXTERNAL_NETWORK' && loadBalancer.backendService) {
|
|
205
|
+
loadBalancer.elb.backendServices = [loadBalancer.backendService];
|
|
206
|
+
loadBalancer.elb.healthChecks = loadBalancer.backendService.healthCheck
|
|
207
|
+
? [loadBalancer.backendService.healthCheck]
|
|
208
|
+
: [];
|
|
178
209
|
}
|
|
179
210
|
}
|
|
180
211
|
|
|
@@ -251,9 +282,9 @@ function deleteGceHttpLoadBalancer(loadBalancer: any, app: any, params: any = {}
|
|
|
251
282
|
|
|
252
283
|
return TaskExecutor.executeTask({
|
|
253
284
|
application: app,
|
|
254
|
-
description: `Delete load balancer: ${loadBalancer.urlMapName || loadBalancer.name} in ${
|
|
255
|
-
loadBalancer.
|
|
256
|
-
}
|
|
285
|
+
description: `Delete load balancer: ${loadBalancer.urlMapName || loadBalancer.name} in ${loadBalancer.account}:${
|
|
286
|
+
loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED' ? region : 'global'
|
|
287
|
+
}`,
|
|
257
288
|
job: [job],
|
|
258
289
|
});
|
|
259
290
|
}
|
|
@@ -288,7 +319,10 @@ function GceLoadBalancerDeleteOptions({
|
|
|
288
319
|
deleteParams: { deleteHealthChecks: boolean };
|
|
289
320
|
loadBalancer: any;
|
|
290
321
|
}): JSX.Element {
|
|
291
|
-
const hasHealthChecks =
|
|
322
|
+
const hasHealthChecks =
|
|
323
|
+
gceHttpLoadBalancerUtils.isHttpLoadBalancer(loadBalancer) ||
|
|
324
|
+
!!loadBalancer.healthCheck ||
|
|
325
|
+
(loadBalancer.loadBalancerType === 'REGIONAL_EXTERNAL_NETWORK' && !!loadBalancer.backendService?.healthCheck);
|
|
292
326
|
if (!hasHealthChecks) {
|
|
293
327
|
return null;
|
|
294
328
|
}
|
|
@@ -382,8 +416,8 @@ export function GceLoadBalancerInformationSection({ loadBalancer }: { app: any;
|
|
|
382
416
|
<dt>DNS</dt>
|
|
383
417
|
<dd>
|
|
384
418
|
{Array.isArray(loadBalancer.elb.dns)
|
|
385
|
-
? loadBalancer.elb.dns.map(
|
|
386
|
-
:
|
|
419
|
+
? loadBalancer.elb.dns.map(formatDns).join(', ')
|
|
420
|
+
: formatDns(loadBalancer.elb.dns)}
|
|
387
421
|
</dd>
|
|
388
422
|
</>
|
|
389
423
|
)}
|
|
@@ -392,15 +426,64 @@ export function GceLoadBalancerInformationSection({ loadBalancer }: { app: any;
|
|
|
392
426
|
);
|
|
393
427
|
}
|
|
394
428
|
|
|
429
|
+
function formatListenerDescription(description: any): string {
|
|
430
|
+
const listener = description.listener || description;
|
|
431
|
+
return `${listener.protocol}:${listener.loadBalancerPort} → ${listener.instanceProtocol}:${listener.instancePort}`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function formatNormalizedHttpListener(listener: any): string {
|
|
435
|
+
const port = listener.port || listener.portRange?.split('-')[0];
|
|
436
|
+
const protocol = listener.protocol || (port === '443' || listener.certificate ? 'HTTPS' : 'HTTP');
|
|
437
|
+
return `${protocol}:${port}`;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function getListenerDisplayRows(loadBalancer: any): string[] {
|
|
441
|
+
const listenerDescriptions = loadBalancer.elb?.listenerDescriptions;
|
|
442
|
+
if (listenerDescriptions?.length) {
|
|
443
|
+
return listenerDescriptions.map(formatListenerDescription);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (gceHttpLoadBalancerUtils.isHttpLoadBalancer(loadBalancer) && loadBalancer.listeners?.length) {
|
|
447
|
+
return loadBalancer.listeners.map(formatNormalizedHttpListener);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return [];
|
|
451
|
+
}
|
|
452
|
+
|
|
395
453
|
export function GceLoadBalancerListenersSection({ loadBalancer }: { app: any; loadBalancer: any }): JSX.Element {
|
|
454
|
+
if (
|
|
455
|
+
loadBalancer.loadBalancerType !== 'EXTERNAL_MANAGED' &&
|
|
456
|
+
loadBalancer.loadBalancerType !== 'REGIONAL_EXTERNAL_NETWORK'
|
|
457
|
+
) {
|
|
458
|
+
return (
|
|
459
|
+
<CollapsibleSection heading="Listeners" defaultExpanded={true}>
|
|
460
|
+
{(loadBalancer.listeners || []).length ? (
|
|
461
|
+
<ul>
|
|
462
|
+
{loadBalancer.listeners.map((listener: any, index: number) => (
|
|
463
|
+
<li key={index}>{[listener.protocol, listener.port || listener.portRange].filter(Boolean).join(':')}</li>
|
|
464
|
+
))}
|
|
465
|
+
</ul>
|
|
466
|
+
) : (
|
|
467
|
+
<span>No listeners configured</span>
|
|
468
|
+
)}
|
|
469
|
+
</CollapsibleSection>
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const listenerRows = getListenerDisplayRows(loadBalancer);
|
|
474
|
+
|
|
396
475
|
return (
|
|
397
476
|
<CollapsibleSection heading="Listeners" defaultExpanded={true}>
|
|
398
|
-
{
|
|
399
|
-
<
|
|
400
|
-
|
|
401
|
-
|
|
477
|
+
{listenerRows.length ? (
|
|
478
|
+
<dl>
|
|
479
|
+
<dt>
|
|
480
|
+
Load Balancer → Instance
|
|
481
|
+
{loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED' && <HelpField id="gce.httpLoadBalancer.namedPort" />}
|
|
482
|
+
</dt>
|
|
483
|
+
{listenerRows.map((listenerRow, index) => (
|
|
484
|
+
<dd key={index}>{listenerRow}</dd>
|
|
402
485
|
))}
|
|
403
|
-
</
|
|
486
|
+
</dl>
|
|
404
487
|
) : (
|
|
405
488
|
<span>No listeners configured</span>
|
|
406
489
|
)}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { IGceLoadBalancer } from '../domain/loadBalancer';
|
|
2
|
+
|
|
3
|
+
import { GceHttpLoadBalancerUtils } from './httpLoadBalancerUtils.service';
|
|
4
|
+
|
|
5
|
+
describe('GceHttpLoadBalancerUtils', () => {
|
|
6
|
+
const utils = new GceHttpLoadBalancerUtils();
|
|
7
|
+
|
|
8
|
+
it('treats EXTERNAL_MANAGED as part of the HTTP load balancer family', () => {
|
|
9
|
+
const externalManaged = {
|
|
10
|
+
account: 'account-a',
|
|
11
|
+
listeners: [{ name: 'listener-443' }],
|
|
12
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
13
|
+
provider: 'gce',
|
|
14
|
+
region: 'us-central1',
|
|
15
|
+
urlMapName: 'app-main',
|
|
16
|
+
} as IGceLoadBalancer;
|
|
17
|
+
|
|
18
|
+
expect(utils.isHttpLoadBalancer(externalManaged)).toBe(true);
|
|
19
|
+
expect(utils.isRegionalHttpLoadBalancer(externalManaged)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('keeps global HTTP and INTERNAL_MANAGED classification unchanged', () => {
|
|
23
|
+
const globalHttp = {
|
|
24
|
+
account: 'account-a',
|
|
25
|
+
listeners: [{ name: 'listener-80' }],
|
|
26
|
+
loadBalancerType: 'HTTP',
|
|
27
|
+
provider: 'gce',
|
|
28
|
+
region: 'global',
|
|
29
|
+
urlMapName: 'app-main',
|
|
30
|
+
} as IGceLoadBalancer;
|
|
31
|
+
const internalManaged = {
|
|
32
|
+
account: 'account-a',
|
|
33
|
+
listeners: [{ name: 'listener-internal' }],
|
|
34
|
+
loadBalancerType: 'INTERNAL_MANAGED',
|
|
35
|
+
provider: 'gce',
|
|
36
|
+
region: 'europe-west1',
|
|
37
|
+
urlMapName: 'app-internal',
|
|
38
|
+
} as IGceLoadBalancer;
|
|
39
|
+
|
|
40
|
+
expect(utils.isHttpLoadBalancer(globalHttp)).toBe(true);
|
|
41
|
+
expect(utils.isRegionalHttpLoadBalancer(globalHttp)).toBe(false);
|
|
42
|
+
|
|
43
|
+
expect(utils.isHttpLoadBalancer(internalManaged)).toBe(true);
|
|
44
|
+
expect(utils.isRegionalHttpLoadBalancer(internalManaged)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('scopes regional listener normalization by account and region', () => {
|
|
48
|
+
const loadBalancers = [
|
|
49
|
+
{
|
|
50
|
+
account: 'account-a',
|
|
51
|
+
listeners: [{ name: 'listener-a' }],
|
|
52
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
53
|
+
name: 'app-main (account-a/us-central1/EXTERNAL_MANAGED)',
|
|
54
|
+
provider: 'gce',
|
|
55
|
+
region: 'us-central1',
|
|
56
|
+
urlMapName: 'app-main',
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
account: 'account-a',
|
|
60
|
+
listeners: [{ name: 'listener-b' }],
|
|
61
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
62
|
+
name: 'app-main (account-a/europe-west1/EXTERNAL_MANAGED)',
|
|
63
|
+
provider: 'gce',
|
|
64
|
+
region: 'europe-west1',
|
|
65
|
+
urlMapName: 'app-main',
|
|
66
|
+
},
|
|
67
|
+
] as IGceLoadBalancer[];
|
|
68
|
+
|
|
69
|
+
expect(
|
|
70
|
+
utils.normalizeLoadBalancerNamesForAccount(['listener-a'], 'account-a', loadBalancers, 'us-central1'),
|
|
71
|
+
).toEqual(['app-main (account-a/us-central1/EXTERNAL_MANAGED)']);
|
|
72
|
+
expect(
|
|
73
|
+
utils.normalizeLoadBalancerNamesForAccount(['listener-b'], 'account-a', loadBalancers, 'europe-west1'),
|
|
74
|
+
).toEqual(['app-main (account-a/europe-west1/EXTERNAL_MANAGED)']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('maps global HTTP listener names to url map names without requiring region', () => {
|
|
78
|
+
const loadBalancers = [
|
|
79
|
+
{
|
|
80
|
+
account: 'account-a',
|
|
81
|
+
listeners: [{ name: 'listener-80' }, { name: 'listener-443' }],
|
|
82
|
+
loadBalancerType: 'HTTP',
|
|
83
|
+
name: 'app-main',
|
|
84
|
+
provider: 'gce',
|
|
85
|
+
region: 'global',
|
|
86
|
+
urlMapName: 'app-main',
|
|
87
|
+
},
|
|
88
|
+
] as IGceLoadBalancer[];
|
|
89
|
+
|
|
90
|
+
expect(utils.normalizeLoadBalancerNamesForAccount(['listener-443'], 'account-a', loadBalancers)).toEqual([
|
|
91
|
+
'app-main',
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('keeps the first INTERNAL_MANAGED url map for a listener name regardless of region', () => {
|
|
96
|
+
const loadBalancers = ['us-central1', 'europe-west1'].map((region) => ({
|
|
97
|
+
account: 'account-a',
|
|
98
|
+
listeners: [{ name: 'shared-listener' }],
|
|
99
|
+
loadBalancerType: 'INTERNAL_MANAGED',
|
|
100
|
+
name: `app-internal-${region}`,
|
|
101
|
+
provider: 'gce',
|
|
102
|
+
region,
|
|
103
|
+
urlMapName: `app-internal-${region}`,
|
|
104
|
+
})) as IGceLoadBalancer[];
|
|
105
|
+
|
|
106
|
+
expect(
|
|
107
|
+
utils.normalizeLoadBalancerNamesForAccount(['shared-listener'], 'account-a', loadBalancers, 'europe-west1'),
|
|
108
|
+
).toEqual(['app-internal-us-central1']);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('leaves an EXTERNAL_MANAGED listener name unresolved when regions make it ambiguous', () => {
|
|
112
|
+
const loadBalancers = ['us-central1', 'europe-west1'].map((region) => ({
|
|
113
|
+
account: 'account-a',
|
|
114
|
+
listeners: [{ name: 'shared-listener' }],
|
|
115
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
116
|
+
name: `app-main (account-a/${region}/EXTERNAL_MANAGED)`,
|
|
117
|
+
provider: 'gce',
|
|
118
|
+
region,
|
|
119
|
+
urlMapName: 'app-main',
|
|
120
|
+
})) as IGceLoadBalancer[];
|
|
121
|
+
|
|
122
|
+
expect(utils.normalizeLoadBalancerNamesForAccount(['shared-listener'], 'account-a', loadBalancers)).toEqual([
|
|
123
|
+
'shared-listener',
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
@@ -4,35 +4,51 @@ import type { IGceHttpLoadBalancer, IGceLoadBalancer } from '../domain/loadBalan
|
|
|
4
4
|
|
|
5
5
|
export class GceHttpLoadBalancerUtils {
|
|
6
6
|
public static REGION = 'global';
|
|
7
|
+
public static HTTP_LOAD_BALANCER_TYPES = ['HTTP', 'INTERNAL_MANAGED', 'EXTERNAL_MANAGED'];
|
|
7
8
|
|
|
8
9
|
public isHttpLoadBalancer(lb: IGceLoadBalancer): lb is IGceHttpLoadBalancer {
|
|
9
10
|
const loadBalancer = lb as any;
|
|
10
11
|
return (
|
|
11
12
|
(loadBalancer.provider === 'gce' || loadBalancer.type === 'gce') &&
|
|
12
|
-
(loadBalancer.loadBalancerType
|
|
13
|
+
GceHttpLoadBalancerUtils.HTTP_LOAD_BALANCER_TYPES.includes(loadBalancer.loadBalancerType)
|
|
13
14
|
);
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
public isRegionalHttpLoadBalancer(lb: IGceLoadBalancer): lb is IGceHttpLoadBalancer {
|
|
18
|
+
return this.isHttpLoadBalancer(lb) && lb.loadBalancerType !== 'HTTP';
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
public normalizeLoadBalancerNamesForAccount(
|
|
17
22
|
loadBalancerNames: string[],
|
|
18
23
|
account: string,
|
|
19
24
|
loadBalancers: IGceLoadBalancer[],
|
|
25
|
+
region?: string,
|
|
20
26
|
): string[] {
|
|
21
27
|
// Assume that loadBalancers is a list of all GCE load balancers in an application
|
|
22
28
|
// (but possibly from several accounts), and has already been normalized (listener names mapped to URL map names).
|
|
23
29
|
const normalizedLoadBalancerNames: string[] = [];
|
|
30
|
+
const hasListener = (loadBalancer: IGceLoadBalancer, loadBalancerName: string) =>
|
|
31
|
+
account === loadBalancer.account &&
|
|
32
|
+
this.isHttpLoadBalancer(loadBalancer) &&
|
|
33
|
+
loadBalancer.listeners.map((listener) => listener.name).includes(loadBalancerName);
|
|
24
34
|
loadBalancerNames.forEach((loadBalancerName) => {
|
|
25
|
-
const matchingUrlMap = loadBalancers.find(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
const matchingUrlMap = loadBalancers.find(
|
|
36
|
+
(loadBalancer) =>
|
|
37
|
+
loadBalancer.loadBalancerType !== 'EXTERNAL_MANAGED' && hasListener(loadBalancer, loadBalancerName),
|
|
38
|
+
);
|
|
39
|
+
// EXTERNAL_MANAGED listener names repeat across regions, so a raw alias maps only when the
|
|
40
|
+
// account and region identify one load balancer.
|
|
41
|
+
const externalMatches = matchingUrlMap
|
|
42
|
+
? []
|
|
43
|
+
: loadBalancers.filter(
|
|
44
|
+
(loadBalancer) =>
|
|
45
|
+
loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED' &&
|
|
46
|
+
(!region || loadBalancer.region === region) &&
|
|
47
|
+
hasListener(loadBalancer, loadBalancerName),
|
|
48
|
+
);
|
|
49
|
+
const match = matchingUrlMap || (externalMatches.length === 1 ? externalMatches[0] : undefined);
|
|
32
50
|
|
|
33
|
-
|
|
34
|
-
? normalizedLoadBalancerNames.push(matchingUrlMap.name)
|
|
35
|
-
: normalizedLoadBalancerNames.push(loadBalancerName);
|
|
51
|
+
match ? normalizedLoadBalancerNames.push(match.name) : normalizedLoadBalancerNames.push(loadBalancerName);
|
|
36
52
|
});
|
|
37
53
|
return uniq(normalizedLoadBalancerNames);
|
|
38
54
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { GceLoadBalancerSetTransformer } from './loadBalancer.setTransformer';
|
|
2
|
+
|
|
3
|
+
describe('GceLoadBalancerSetTransformer', () => {
|
|
4
|
+
const transformer = new GceLoadBalancerSetTransformer();
|
|
5
|
+
|
|
6
|
+
it('groups regional HTTP listeners by account, region, load balancer type, and raw urlMapName', () => {
|
|
7
|
+
const loadBalancers = [
|
|
8
|
+
{
|
|
9
|
+
account: 'test',
|
|
10
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
11
|
+
name: 'listener-a',
|
|
12
|
+
portRange: '80-80',
|
|
13
|
+
provider: 'gce',
|
|
14
|
+
region: 'us-central1',
|
|
15
|
+
urlMapName: 'app',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
account: 'test',
|
|
19
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
20
|
+
name: 'listener-b',
|
|
21
|
+
portRange: '80-80',
|
|
22
|
+
provider: 'gce',
|
|
23
|
+
region: 'us-east1',
|
|
24
|
+
urlMapName: 'app',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const normalized = transformer.normalizeLoadBalancerSet(loadBalancers as any);
|
|
29
|
+
|
|
30
|
+
expect(normalized.length).toBe(2);
|
|
31
|
+
expect(normalized.map((loadBalancer) => loadBalancer.region).sort()).toEqual(['us-central1', 'us-east1']);
|
|
32
|
+
expect(normalized.map((loadBalancer) => loadBalancer.name).sort()).toEqual([
|
|
33
|
+
'app (test/us-central1/EXTERNAL_MANAGED)',
|
|
34
|
+
'app (test/us-east1/EXTERNAL_MANAGED)',
|
|
35
|
+
]);
|
|
36
|
+
expect(normalized.every((loadBalancer: any) => loadBalancer.urlMapName === 'app')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('folds same-scope regional external listeners into one normalized load balancer', () => {
|
|
40
|
+
const loadBalancers = [
|
|
41
|
+
{
|
|
42
|
+
account: 'test',
|
|
43
|
+
certificate: undefined,
|
|
44
|
+
ipAddress: '34.0.0.1',
|
|
45
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
46
|
+
name: 'listener-http',
|
|
47
|
+
networkTier: 'STANDARD',
|
|
48
|
+
portRange: '80-80',
|
|
49
|
+
provider: 'gce',
|
|
50
|
+
region: 'us-central1',
|
|
51
|
+
urlMapName: 'app',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
account: 'test',
|
|
55
|
+
certificate: '//certificatemanager.googleapis.com/projects/p/locations/us-central1/certificates/cert',
|
|
56
|
+
ipAddress: '34.0.0.2',
|
|
57
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
58
|
+
name: 'listener-https',
|
|
59
|
+
networkTier: 'PREMIUM',
|
|
60
|
+
portRange: '443-443',
|
|
61
|
+
provider: 'gce',
|
|
62
|
+
region: 'us-central1',
|
|
63
|
+
urlMapName: 'app',
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const [normalized] = transformer.normalizeLoadBalancerSet(loadBalancers as any);
|
|
68
|
+
|
|
69
|
+
expect(normalized.name).toBe('app (test/us-central1/EXTERNAL_MANAGED)');
|
|
70
|
+
expect((normalized as any).urlMapName).toBe('app');
|
|
71
|
+
expect((normalized as any).listeners).toEqual([
|
|
72
|
+
expect.objectContaining({
|
|
73
|
+
certificate: undefined,
|
|
74
|
+
ipAddress: '34.0.0.1',
|
|
75
|
+
name: 'listener-http',
|
|
76
|
+
networkTier: 'STANDARD',
|
|
77
|
+
port: '80',
|
|
78
|
+
}),
|
|
79
|
+
expect.objectContaining({
|
|
80
|
+
certificate: '//certificatemanager.googleapis.com/projects/p/locations/us-central1/certificates/cert',
|
|
81
|
+
ipAddress: '34.0.0.2',
|
|
82
|
+
name: 'listener-https',
|
|
83
|
+
networkTier: 'PREMIUM',
|
|
84
|
+
port: '443',
|
|
85
|
+
}),
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('keeps global HTTP normalization unchanged', () => {
|
|
90
|
+
const result = transformer.normalizeLoadBalancerSet([
|
|
91
|
+
{
|
|
92
|
+
account: 'test',
|
|
93
|
+
loadBalancerType: 'HTTP',
|
|
94
|
+
name: 'forwarding-rule-80',
|
|
95
|
+
portRange: '80-80',
|
|
96
|
+
provider: 'gce',
|
|
97
|
+
region: 'global',
|
|
98
|
+
urlMapName: 'frontend-map',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
account: 'test',
|
|
102
|
+
loadBalancerType: 'HTTP',
|
|
103
|
+
name: 'forwarding-rule-443',
|
|
104
|
+
portRange: '443-443',
|
|
105
|
+
provider: 'gce',
|
|
106
|
+
region: 'global',
|
|
107
|
+
urlMapName: 'frontend-map',
|
|
108
|
+
},
|
|
109
|
+
] as any);
|
|
110
|
+
|
|
111
|
+
expect(result.length).toBe(1);
|
|
112
|
+
expect(result[0].name).toBe('frontend-map');
|
|
113
|
+
expect((result[0] as any).listeners.map((listener: any) => listener.port)).toEqual(['80', '443']);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('keeps INTERNAL_MANAGED names as the raw url map name that Clouddriver attaches by', () => {
|
|
117
|
+
const [internalManaged, externalManaged] = transformer.normalizeLoadBalancerSet([
|
|
118
|
+
{
|
|
119
|
+
account: 'test',
|
|
120
|
+
loadBalancerType: 'INTERNAL_MANAGED',
|
|
121
|
+
name: 'internal-listener',
|
|
122
|
+
portRange: '80-80',
|
|
123
|
+
provider: 'gce',
|
|
124
|
+
region: 'us-central1',
|
|
125
|
+
urlMapName: 'shared-map',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
account: 'test',
|
|
129
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
130
|
+
name: 'external-listener',
|
|
131
|
+
portRange: '80-80',
|
|
132
|
+
provider: 'gce',
|
|
133
|
+
region: 'us-central1',
|
|
134
|
+
urlMapName: 'shared-map',
|
|
135
|
+
},
|
|
136
|
+
] as any);
|
|
137
|
+
|
|
138
|
+
expect(internalManaged.name).toBe('shared-map');
|
|
139
|
+
expect(externalManaged.name).toBe('shared-map (test/us-central1/EXTERNAL_MANAGED)');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('does not group REGIONAL_EXTERNAL_NETWORK load balancers with regional HTTP families', () => {
|
|
143
|
+
const networkLoadBalancer = {
|
|
144
|
+
account: 'test',
|
|
145
|
+
loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
|
|
146
|
+
name: 'regional-network-lb',
|
|
147
|
+
provider: 'gce',
|
|
148
|
+
region: 'us-central1',
|
|
149
|
+
};
|
|
150
|
+
const httpListener = {
|
|
151
|
+
account: 'test',
|
|
152
|
+
loadBalancerType: 'EXTERNAL_MANAGED',
|
|
153
|
+
name: 'listener-http',
|
|
154
|
+
portRange: '80-80',
|
|
155
|
+
provider: 'gce',
|
|
156
|
+
region: 'us-central1',
|
|
157
|
+
urlMapName: 'app',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const normalized = transformer.normalizeLoadBalancerSet([httpListener, networkLoadBalancer] as any);
|
|
161
|
+
|
|
162
|
+
expect(normalized.length).toBe(2);
|
|
163
|
+
expect(normalized.find((loadBalancer) => loadBalancer.name === 'regional-network-lb')).toBe(networkLoadBalancer);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
@@ -10,6 +10,9 @@ export class GceLoadBalancerSetTransformer {
|
|
|
10
10
|
private static normalizeHttpLoadBalancerGroup(group: IGceHttpLoadBalancer[]): IGceHttpLoadBalancer {
|
|
11
11
|
const normalized = cloneDeep(group[0]);
|
|
12
12
|
|
|
13
|
+
// Clouddriver returns one row per forwarding-rule listener. Deck presents the URL map as the
|
|
14
|
+
// logical HTTP(S) load balancer, so the normalized object keeps the URL-map identity and folds
|
|
15
|
+
// forwarding-rule-specific fields into listener rows.
|
|
13
16
|
normalized.listeners = group.map((loadBalancer) => {
|
|
14
17
|
const port = loadBalancer.portRange ? GceLoadBalancerSetTransformer.parsePortRange(loadBalancer.portRange) : null;
|
|
15
18
|
return {
|
|
@@ -18,11 +21,17 @@ export class GceLoadBalancerSetTransformer {
|
|
|
18
21
|
certificate: loadBalancer.certificate,
|
|
19
22
|
certificateMap: loadBalancer.certificateMap,
|
|
20
23
|
ipAddress: loadBalancer.ipAddress,
|
|
24
|
+
networkTier: loadBalancer.networkTier,
|
|
21
25
|
subnet: loadBalancer.subnet,
|
|
22
26
|
};
|
|
23
27
|
});
|
|
24
28
|
|
|
25
|
-
normalized.name =
|
|
29
|
+
normalized.name =
|
|
30
|
+
normalized.loadBalancerType === 'EXTERNAL_MANAGED'
|
|
31
|
+
? // Regional external URL map names can repeat across accounts and regions, and match
|
|
32
|
+
// INTERNAL_MANAGED names; details routing can still use the raw urlMapName plus scope.
|
|
33
|
+
`${normalized.urlMapName} (${normalized.account}/${normalized.region}/${normalized.loadBalancerType})`
|
|
34
|
+
: normalized.urlMapName;
|
|
26
35
|
delete normalized.subnet;
|
|
27
36
|
return normalized;
|
|
28
37
|
}
|
|
@@ -36,7 +45,11 @@ export class GceLoadBalancerSetTransformer {
|
|
|
36
45
|
this.gceHttpLoadBalancerUtils.isHttpLoadBalancer(lb),
|
|
37
46
|
);
|
|
38
47
|
|
|
39
|
-
const groupedByUrlMap = groupBy(httpLoadBalancers,
|
|
48
|
+
const groupedByUrlMap = groupBy(httpLoadBalancers, (loadBalancer) =>
|
|
49
|
+
loadBalancer.loadBalancerType === 'EXTERNAL_MANAGED'
|
|
50
|
+
? [loadBalancer.account, loadBalancer.region, loadBalancer.loadBalancerType, loadBalancer.urlMapName].join(':')
|
|
51
|
+
: loadBalancer.urlMapName,
|
|
52
|
+
);
|
|
40
53
|
const normalizedElSevenLoadBalancers = map(
|
|
41
54
|
groupedByUrlMap,
|
|
42
55
|
GceLoadBalancerSetTransformer.normalizeHttpLoadBalancerGroup,
|