@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.
Files changed (108) hide show
  1. package/dist/autoscalingPolicy/GceAutoScalingFieldLayout.d.ts +2 -2
  2. package/dist/domain/loadBalancer.d.ts +6 -3
  3. package/dist/image/ImageSelect.d.ts +1 -1
  4. package/dist/index.js +159 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/loadBalancer/configure/common/gceLoadBalancerModels.d.ts +13 -2
  7. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerEditor.d.ts +3 -0
  8. package/dist/loadBalancer/configure/http/GceHttpLoadBalancerModal.d.ts +1 -1
  9. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.d.ts +30 -0
  10. package/dist/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.d.ts +53 -0
  11. package/dist/loadBalancer/httpLoadBalancerUtils.service.d.ts +3 -1
  12. package/dist/serverGroup/configure/wizard/GceCloneServerGroupModal.d.ts +3 -0
  13. package/dist/serverGroup/configure/wizard/GceServerGroupWizard.types.d.ts +1 -0
  14. package/dist/serverGroup/configure/wizard/customInstance/CustomInstanceConfigurer.d.ts +1 -1
  15. package/dist/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.d.ts +2 -2
  16. package/dist/serverGroup/configure/wizard/pages/AdvancedSettings.d.ts +3 -0
  17. package/dist/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.d.ts +1 -0
  18. package/dist/serverGroup/configure/wizard/pages/Policies.d.ts +21 -3
  19. package/dist/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.d.ts +1 -0
  20. package/dist/serverGroup/configure/wizard/pages/ServerGroupCapacity.d.ts +1 -0
  21. package/dist/serverGroup/configure/wizard/pages/ServerGroupImageSettings.d.ts +1 -0
  22. package/dist/serverGroup/configure/wizard/pages/ServerGroupInstanceType.d.ts +1 -0
  23. package/dist/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.d.ts +2 -2
  24. package/dist/serverGroup/details/GceInstanceFlexibilityPolicyDetails.d.ts +2 -2
  25. package/dist/serverGroup/details/stateful/MarkDiskStatefulButton.d.ts +2 -2
  26. package/dist/serverGroup/details/stateful/UpdateBootImageButton.d.ts +2 -2
  27. package/package.json +5 -5
  28. package/src/autoHealingPolicy/GceAutoHealingPolicyEditor.spec.tsx +68 -52
  29. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.spec.tsx +43 -50
  30. package/src/autoscalingPolicy/GceAutoscalingPolicyEditor.tsx +1 -1
  31. package/src/autoscalingPolicy/GceAutoscalingPolicyWriter.spec.ts +1 -1
  32. package/src/domain/loadBalancer.ts +6 -3
  33. package/src/gce.module.spec.ts +132 -153
  34. package/src/instance/details/GceInstanceDetails.spec.tsx +86 -83
  35. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.spec.tsx +162 -121
  36. package/src/loadBalancer/configure/choice/GceLoadBalancerChoiceModal.tsx +13 -1
  37. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.spec.tsx +47 -30
  38. package/src/loadBalancer/configure/common/GceProxyLoadBalancerEditor.tsx +2 -1
  39. package/src/loadBalancer/configure/common/GceProxyLoadBalancerModal.spec.tsx +85 -57
  40. package/src/loadBalancer/configure/common/gceLoadBalancerData.spec.ts +17 -16
  41. package/src/loadBalancer/configure/common/gceLoadBalancerModels.spec.ts +230 -5
  42. package/src/loadBalancer/configure/common/gceLoadBalancerModels.ts +186 -22
  43. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.spec.ts +298 -2
  44. package/src/loadBalancer/configure/common/gceLoadBalancerSubmission.ts +35 -5
  45. package/src/loadBalancer/configure/common/useGceLoadBalancerData.spec.tsx +37 -46
  46. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.spec.tsx +399 -58
  47. package/src/loadBalancer/configure/http/GceHttpLoadBalancerEditor.tsx +148 -41
  48. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.spec.tsx +169 -36
  49. package/src/loadBalancer/configure/http/GceHttpLoadBalancerListenerEditor.tsx +80 -12
  50. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.spec.tsx +224 -124
  51. package/src/loadBalancer/configure/http/GceHttpLoadBalancerModal.tsx +8 -2
  52. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.spec.tsx +57 -22
  53. package/src/loadBalancer/configure/http/GceHttpLoadBalancerResourceEditors.tsx +21 -0
  54. package/src/loadBalancer/configure/http/GceHttpLoadBalancerRoutingEditor.spec.tsx +24 -20
  55. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerEditor.spec.tsx +41 -32
  56. package/src/loadBalancer/configure/network/GceNetworkLoadBalancerModal.spec.tsx +35 -30
  57. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.spec.tsx +443 -0
  58. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerEditor.tsx +401 -0
  59. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.spec.tsx +330 -0
  60. package/src/loadBalancer/configure/network/GceRegionalExternalNetworkLoadBalancerModal.tsx +348 -0
  61. package/src/loadBalancer/details/gceLoadBalancerDetails.spec.tsx +439 -35
  62. package/src/loadBalancer/details/gceLoadBalancerDetails.tsx +104 -21
  63. package/src/loadBalancer/httpLoadBalancerUtils.service.spec.ts +126 -0
  64. package/src/loadBalancer/httpLoadBalancerUtils.service.ts +27 -11
  65. package/src/loadBalancer/loadBalancer.setTransformer.spec.ts +165 -0
  66. package/src/loadBalancer/loadBalancer.setTransformer.ts +15 -2
  67. package/src/pipeline/stages/googleStageConfig.spec.js +22 -16
  68. package/src/securityGroup/configure/GceSecurityGroupModal.spec.tsx +177 -193
  69. package/src/securityGroup/configure/GceSecurityGroupModal.tsx +14 -2
  70. package/src/securityGroup/details/GceSecurityGroupDetails.spec.tsx +69 -78
  71. package/src/serverGroup/configure/serverGroupCommandBuilder.service.spec.js +35 -15
  72. package/src/serverGroup/configure/serverGroupConfiguration.service.js +7 -1
  73. package/src/serverGroup/configure/serverGroupConfiguration.service.spec.js +74 -4
  74. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.spec.tsx +593 -855
  75. package/src/serverGroup/configure/wizard/GceCloneServerGroupModal.tsx +120 -38
  76. package/src/serverGroup/configure/wizard/GceServerGroupWizard.types.ts +1 -0
  77. package/src/serverGroup/configure/wizard/GceServerGroupWizardPage.spec.tsx +53 -50
  78. package/src/serverGroup/configure/wizard/customInstance/GceCustomInstanceBuilder.spec.tsx +65 -49
  79. package/src/serverGroup/configure/wizard/hiddenMetadataKeys.value.js +1 -0
  80. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.spec.tsx +101 -108
  81. package/src/serverGroup/configure/wizard/pages/AdvancedSettings.tsx +32 -28
  82. package/src/serverGroup/configure/wizard/pages/GceServerGroupFirewalls.spec.tsx +60 -59
  83. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.spec.tsx +240 -114
  84. package/src/serverGroup/configure/wizard/pages/GceServerGroupLoadBalancers.tsx +152 -63
  85. package/src/serverGroup/configure/wizard/pages/Policies.spec.tsx +93 -48
  86. package/src/serverGroup/configure/wizard/pages/Policies.tsx +80 -55
  87. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.spec.tsx +123 -94
  88. package/src/serverGroup/configure/wizard/pages/ServerGroupBasicSettings.tsx +24 -18
  89. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.spec.tsx +152 -133
  90. package/src/serverGroup/configure/wizard/pages/ServerGroupCapacity.tsx +40 -37
  91. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.spec.tsx +66 -60
  92. package/src/serverGroup/configure/wizard/pages/ServerGroupImageSettings.tsx +8 -2
  93. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.spec.tsx +131 -101
  94. package/src/serverGroup/configure/wizard/pages/ServerGroupInstanceType.tsx +29 -28
  95. package/src/serverGroup/configure/wizard/zones/GceInstanceFlexibilityConfigurer.spec.tsx +104 -81
  96. package/src/serverGroup/details/GceInstanceFlexibilityPolicyDetails.spec.tsx +10 -11
  97. package/src/serverGroup/details/autoHealingPolicy/GceAutoHealingPolicyDetails.spec.tsx +51 -59
  98. package/src/serverGroup/details/autoHealingPolicy/modal/GceUpsertAutoHealingPolicyModal.spec.tsx +3 -3
  99. package/src/serverGroup/details/autoscalingPolicy/GceAutoscalingPolicyDetails.spec.tsx +49 -58
  100. package/src/serverGroup/details/autoscalingPolicy/modal/GceUpsertAutoscalingPolicyModal.spec.tsx +3 -3
  101. package/src/serverGroup/details/gceServerGroupDetails.spec.tsx +162 -141
  102. package/src/serverGroup/details/resize/GceResizeServerGroupModal.spec.tsx +102 -86
  103. package/src/serverGroup/details/resize/GceResizeServerGroupModal.tsx +4 -0
  104. package/src/serverGroup/details/rollback/GceRollbackServerGroupModal.spec.tsx +40 -31
  105. package/src/serverGroup/serverGroup.transformer.js +1 -0
  106. package/src/serverGroup/serverGroup.transformer.spec.js +48 -0
  107. package/src/validation/ApplicationNameValidator.spec.ts +1 -1
  108. /package/dist/{gce.logo2ae85d8704b1d6b8.png → gce.logoChI3w_A5.png} +0 -0
@@ -1,4 +1,13 @@
1
- export const GCE_LOAD_BALANCER_TYPES = ['NETWORK', 'INTERNAL', 'TCP', 'SSL', 'HTTP', 'INTERNAL_MANAGED'] as const;
1
+ export const GCE_LOAD_BALANCER_TYPES = [
2
+ 'NETWORK',
3
+ 'INTERNAL',
4
+ 'TCP',
5
+ 'SSL',
6
+ 'HTTP',
7
+ 'INTERNAL_MANAGED',
8
+ 'EXTERNAL_MANAGED',
9
+ 'REGIONAL_EXTERNAL_NETWORK',
10
+ ] as const;
2
11
 
3
12
  export type GceLoadBalancerType = typeof GCE_LOAD_BALANCER_TYPES[number];
4
13
  export type GceLoadBalancerEditorMode = 'create' | 'edit' | 'pipeline';
@@ -41,6 +50,19 @@ export const GCE_LOAD_BALANCER_CAPABILITIES: Record<GceLoadBalancerType, IGceLoa
41
50
  network: true,
42
51
  subnet: true,
43
52
  }),
53
+ EXTERNAL_MANAGED: capabilities({
54
+ address: true,
55
+ backendServices: true,
56
+ certificates: true,
57
+ healthChecks: true,
58
+ hostRules: true,
59
+ network: true,
60
+ }),
61
+ REGIONAL_EXTERNAL_NETWORK: capabilities({
62
+ address: true,
63
+ backendServices: true,
64
+ healthChecks: true,
65
+ }),
44
66
  };
45
67
 
46
68
  export interface IGceResourceReference {
@@ -56,6 +78,7 @@ export interface IGceLoadBalancerListener {
56
78
  address?: IGceResourceReference;
57
79
  certificate?: IGceResourceReference;
58
80
  certificateMap?: string;
81
+ networkTier?: string;
59
82
  subnet?: IGceResourceReference;
60
83
  }
61
84
 
@@ -66,7 +89,7 @@ export interface IGceLoadBalancerHealthCheck {
66
89
  healthyThreshold?: number;
67
90
  host?: string;
68
91
  name?: string;
69
- port?: number;
92
+ port?: number | string;
70
93
  proxyHeader?: string;
71
94
  requestPath?: string;
72
95
  timeoutSec?: number;
@@ -76,8 +99,15 @@ export interface IGceLoadBalancerHealthCheck {
76
99
  }
77
100
 
78
101
  export interface IGceLoadBalancerBackendService {
102
+ affinityCookieTtlSec?: number;
103
+ backends?: unknown[];
104
+ connectionDrainingTimeoutSec?: number;
105
+ enableCDN?: boolean;
79
106
  name: string;
80
107
  healthCheck?: IGceResourceReference;
108
+ portName?: string;
109
+ protocol?: GceLoadBalancerProtocol;
110
+ sessionAffinity?: string;
81
111
  [key: string]: unknown;
82
112
  }
83
113
 
@@ -115,6 +145,7 @@ interface IGceLoadBalancerCommandBase {
115
145
  defaultService?: IGceResourceReference;
116
146
  network?: IGceResourceReference;
117
147
  original?: IGceLoadBalancerOriginalState;
148
+ networkTier?: string;
118
149
  subnet?: IGceResourceReference;
119
150
  }
120
151
 
@@ -163,6 +194,7 @@ export function normalizeGceLoadBalancerCommand(
163
194
  address: persisted.ipAddress || persisted.address,
164
195
  certificate: persisted.certificate,
165
196
  name: isHttpType(loadBalancerType) ? persisted.loadBalancerName || persisted.name || name : name,
197
+ networkTier: loadBalancerType === 'EXTERNAL_MANAGED' ? persisted.networkTier : undefined,
166
198
  portRange: persisted.portRange || persisted.ports,
167
199
  protocol:
168
200
  persisted.protocol ||
@@ -170,7 +202,7 @@ export function normalizeGceLoadBalancerCommand(
170
202
  subnet: persisted.subnet,
171
203
  };
172
204
  const listeners = asArray(persisted.listeners).length ? asArray(persisted.listeners) : [topLevelListener];
173
- const backendServices = collectBackendServices(persisted);
205
+ const backendServices = collectBackendServices(persisted, loadBalancerType);
174
206
  const healthChecks = collectHealthChecks(persisted, backendServices);
175
207
 
176
208
  const command = {
@@ -185,17 +217,27 @@ export function normalizeGceLoadBalancerCommand(
185
217
  hostRules: asArray(persisted.hostRules).map((hostRule) => normalizeHostRule(asRecord(hostRule))),
186
218
  defaultService: normalizeReference(persisted.defaultService),
187
219
  network: normalizeReference(persisted.network),
220
+ networkTier:
221
+ loadBalancerType === 'REGIONAL_EXTERNAL_NETWORK' ? asString(persisted.networkTier) || undefined : undefined,
188
222
  subnet: normalizeReference(persisted.subnet),
189
223
  } as IGceLoadBalancerCommand;
190
224
 
191
225
  if (mode === 'edit') {
192
- command.original = cloneValue({ backendServices, healthChecks, listeners: command.listeners });
226
+ command.original = cloneValue({
227
+ backendServices,
228
+ healthChecks,
229
+ listeners: command.listeners,
230
+ });
193
231
  }
194
232
  return command;
195
233
  }
196
234
 
197
235
  export function serializeGceLoadBalancerCommand(command: IGceLoadBalancerCommand): IGceSerializedLoadBalancerCommand {
198
- const capabilitiesForType = GCE_LOAD_BALANCER_CAPABILITIES[command.loadBalancerType];
236
+ if (command.loadBalancerType === 'REGIONAL_EXTERNAL_NETWORK') {
237
+ return serializeRegionalExternalNetworkCommand(command);
238
+ }
239
+
240
+ const typeCapabilities = GCE_LOAD_BALANCER_CAPABILITIES[command.loadBalancerType];
199
241
  const listener = command.listeners[0];
200
242
  const serialized: IGceSerializedLoadBalancerCommand = {
201
243
  cloudProvider: 'gce',
@@ -210,34 +252,34 @@ export function serializeGceLoadBalancerCommand(command: IGceLoadBalancerCommand
210
252
 
211
253
  if (isHttpType(command.loadBalancerType)) {
212
254
  serialized.listeners = command.listeners.map((currentListener) =>
213
- serializeListener(currentListener, capabilitiesForType),
255
+ serializeListener(currentListener, typeCapabilities, command.loadBalancerType),
214
256
  );
215
257
  } else if (listener) {
216
258
  serialized.ipProtocol = listener.protocol;
217
259
  serialized.portRange = listener.portRange;
218
260
  }
219
- if (capabilitiesForType.address && listener?.address) {
261
+ if (typeCapabilities.address && listener?.address) {
220
262
  serialized.ipAddress = serializeReservedAddress(listener.address);
221
263
  }
222
- if (capabilitiesForType.certificates && listener?.certificate) {
264
+ if (typeCapabilities.certificates && listener?.certificate && !isHttpType(command.loadBalancerType)) {
223
265
  serialized.certificate = serializeCertificateName(listener.certificate);
224
266
  }
225
- if (capabilitiesForType.healthChecks && command.healthChecks.length) {
267
+ if (typeCapabilities.healthChecks && command.healthChecks.length) {
226
268
  serialized.healthChecks = command.healthChecks.map((healthCheck) => ({ ...healthCheck }));
227
269
  }
228
- if (capabilitiesForType.backendServices && command.backendServices.length) {
270
+ if (typeCapabilities.backendServices && command.backendServices.length) {
229
271
  serialized.backendServices = command.backendServices.map(serializeBackendService);
230
272
  }
231
- if (capabilitiesForType.hostRules && command.hostRules.length) {
273
+ if (typeCapabilities.hostRules && command.hostRules.length) {
232
274
  serialized.hostRules = command.hostRules.map(serializeHostRule);
233
275
  }
234
- if (capabilitiesForType.backendServices && command.defaultService) {
276
+ if (typeCapabilities.backendServices && command.defaultService) {
235
277
  serialized.defaultService = serializeBackendServiceName(command.defaultService);
236
278
  }
237
- if (capabilitiesForType.network && command.network) {
279
+ if (typeCapabilities.network && command.network) {
238
280
  serialized.network = serializeNetworkName(command.network);
239
281
  }
240
- if (capabilitiesForType.subnet && command.subnet) {
282
+ if (typeCapabilities.subnet && command.subnet) {
241
283
  serialized.subnet = serializeSubnetName(command.subnet);
242
284
  }
243
285
 
@@ -259,7 +301,10 @@ function capabilities(overrides: Partial<IGceLoadBalancerCapabilities>): IGceLoa
259
301
 
260
302
  function normalizeLoadBalancerType(value: unknown): GceLoadBalancerType {
261
303
  const normalized = asString(value).toUpperCase() as GceLoadBalancerType;
262
- return GCE_LOAD_BALANCER_TYPES.includes(normalized) ? normalized : 'NETWORK';
304
+ if ((GCE_LOAD_BALANCER_TYPES as readonly string[]).includes(normalized)) {
305
+ return normalized;
306
+ }
307
+ return 'NETWORK';
263
308
  }
264
309
 
265
310
  function normalizeProtocol(value: unknown, type: GceLoadBalancerType): GceLoadBalancerProtocol {
@@ -295,6 +340,7 @@ function normalizeListener(
295
340
  const certificate = normalizeReference(listener.certificate);
296
341
  const certificateMap = asString(listener.certificateMap);
297
342
  const subnet = normalizeReference(listener.subnet);
343
+ const networkTier = asString(listener.networkTier);
298
344
  if (address) {
299
345
  normalized.address = address;
300
346
  }
@@ -304,6 +350,9 @@ function normalizeListener(
304
350
  if (certificateMap) {
305
351
  normalized.certificateMap = certificateMap;
306
352
  }
353
+ if (networkTier && type === 'EXTERNAL_MANAGED') {
354
+ normalized.networkTier = networkTier;
355
+ }
307
356
  if (subnet) {
308
357
  normalized.subnet = subnet;
309
358
  }
@@ -317,7 +366,13 @@ export function normalizeGceHealthCheck(value: unknown): IGceLoadBalancerHealthC
317
366
  if (healthCheckType) {
318
367
  normalized.healthCheckType = normalizeProtocol(healthCheckType, 'NETWORK');
319
368
  }
320
- (['port', 'checkIntervalSec', 'timeoutSec', 'healthyThreshold', 'unhealthyThreshold'] as const).forEach((field) => {
369
+ if (healthCheck.port !== undefined && healthCheck.port !== null && healthCheck.port !== '') {
370
+ normalized.port =
371
+ typeof healthCheck.port === 'string' && !/^\d+$/.test(healthCheck.port)
372
+ ? healthCheck.port
373
+ : Number(healthCheck.port);
374
+ }
375
+ (['checkIntervalSec', 'timeoutSec', 'healthyThreshold', 'unhealthyThreshold'] as const).forEach((field) => {
321
376
  if (healthCheck[field] !== undefined && healthCheck[field] !== null && healthCheck[field] !== '') {
322
377
  normalized[field] = Number(healthCheck[field]);
323
378
  }
@@ -329,17 +384,26 @@ export function normalizeGceHealthCheck(value: unknown): IGceLoadBalancerHealthC
329
384
  return normalized;
330
385
  }
331
386
 
332
- function normalizeBackendService(service: UnknownRecord): IGceLoadBalancerBackendService {
387
+ function normalizeBackendService(
388
+ service: UnknownRecord,
389
+ loadBalancerType: GceLoadBalancerType,
390
+ ): IGceLoadBalancerBackendService {
333
391
  const normalized: IGceLoadBalancerBackendService = { ...service, name: asString(service.name) };
334
392
  const healthCheck = normalizeReference(service.healthCheck || service.healthCheckLink);
335
393
  if (healthCheck) {
336
394
  normalized.healthCheck = healthCheck;
337
395
  }
396
+ if (loadBalancerType === 'EXTERNAL_MANAGED') {
397
+ normalized.protocol = (asString(service.protocol).toUpperCase() || 'HTTP') as GceLoadBalancerProtocol;
398
+ }
338
399
  delete normalized.healthCheckLink;
339
400
  return normalized;
340
401
  }
341
402
 
342
- function collectBackendServices(persisted: UnknownRecord): IGceLoadBalancerBackendService[] {
403
+ function collectBackendServices(
404
+ persisted: UnknownRecord,
405
+ loadBalancerType: GceLoadBalancerType,
406
+ ): IGceLoadBalancerBackendService[] {
343
407
  const candidates = [...asArray(persisted.backendServices), ...asArray(persisted.backendService)];
344
408
  if (persisted.defaultService && typeof persisted.defaultService === 'object') {
345
409
  candidates.push(persisted.defaultService);
@@ -361,6 +425,7 @@ function collectBackendServices(persisted: UnknownRecord): IGceLoadBalancerBacke
361
425
  candidates.forEach((candidate) => {
362
426
  const service = normalizeBackendService(
363
427
  typeof candidate === 'object' ? asRecord(candidate) : { name: asString(candidate) },
428
+ loadBalancerType,
364
429
  );
365
430
  if (service.name && !services.has(service.name)) {
366
431
  services.set(service.name, service);
@@ -429,6 +494,18 @@ function serializeCertificateName(reference: IGceResourceReference): string {
429
494
  return reference.name;
430
495
  }
431
496
 
497
+ function serializeCertificateReference(
498
+ reference: IGceResourceReference,
499
+ loadBalancerType: GceLoadBalancerType,
500
+ ): string {
501
+ // Clouddriver recognizes a Certificate Manager certificate only by its full resource path and
502
+ // resolves a bare name as a regional Compute SSL certificate, so keep the selfLink.
503
+ if (loadBalancerType === 'EXTERNAL_MANAGED' && reference.selfLink) {
504
+ return reference.selfLink;
505
+ }
506
+ return serializeCertificateName(reference);
507
+ }
508
+
432
509
  function serializeNetworkName(reference: IGceResourceReference): string {
433
510
  return reference.name;
434
511
  }
@@ -448,6 +525,7 @@ function serializeHealthCheckName(reference: IGceResourceReference): string {
448
525
  function serializeListener(
449
526
  listener: IGceLoadBalancerListener,
450
527
  capabilitiesForType: IGceLoadBalancerCapabilities,
528
+ loadBalancerType: GceLoadBalancerType,
451
529
  ): UnknownRecord {
452
530
  const serialized: UnknownRecord = {
453
531
  name: listener.name,
@@ -458,11 +536,14 @@ function serializeListener(
458
536
  serialized.ipAddress = serializeReservedAddress(listener.address);
459
537
  }
460
538
  if (capabilitiesForType.certificates && listener.certificate) {
461
- serialized.certificate = serializeCertificateName(listener.certificate);
539
+ serialized.certificate = serializeCertificateReference(listener.certificate, loadBalancerType);
462
540
  }
463
- if (capabilitiesForType.certificates && listener.certificateMap) {
541
+ if (capabilitiesForType.certificates && listener.certificateMap && loadBalancerType !== 'EXTERNAL_MANAGED') {
464
542
  serialized.certificateMap = listener.certificateMap;
465
543
  }
544
+ if (loadBalancerType === 'EXTERNAL_MANAGED' && listener.networkTier) {
545
+ serialized.networkTier = listener.networkTier;
546
+ }
466
547
  if (capabilitiesForType.subnet && listener.subnet) {
467
548
  serialized.subnet = serializeSubnetName(listener.subnet);
468
549
  }
@@ -477,6 +558,37 @@ function serializeBackendService(service: IGceLoadBalancerBackendService): Unkno
477
558
  return serialized;
478
559
  }
479
560
 
561
+ export function serializeGceBackendService(service: IGceLoadBalancerBackendService): UnknownRecord {
562
+ return definedFields({
563
+ affinityCookieTtlSec: service.affinityCookieTtlSec,
564
+ backends: service.backends ? cloneValue(service.backends) : undefined,
565
+ connectionDrainingTimeoutSec: service.connectionDrainingTimeoutSec,
566
+ enableCDN: service.enableCDN,
567
+ healthCheck:
568
+ service.healthCheck && Object.keys(service.healthCheck).some((key) => key !== 'name' && key !== 'selfLink')
569
+ ? serializeGceHealthCheck(service.healthCheck as IGceLoadBalancerHealthCheck)
570
+ : service.healthCheck?.name,
571
+ name: service.name,
572
+ portName: service.portName,
573
+ protocol: service.protocol,
574
+ sessionAffinity: service.sessionAffinity,
575
+ });
576
+ }
577
+
578
+ export function serializeGceHealthCheck(healthCheck: IGceLoadBalancerHealthCheck): UnknownRecord {
579
+ const protocol = healthCheck.healthCheckType;
580
+ return definedFields({
581
+ checkIntervalSec: healthCheck.checkIntervalSec,
582
+ healthCheckType: protocol,
583
+ healthyThreshold: healthCheck.healthyThreshold,
584
+ name: healthCheck.name,
585
+ port: healthCheck.port,
586
+ requestPath: protocol === 'HTTP' || protocol === 'HTTPS' ? healthCheck.requestPath : undefined,
587
+ timeoutSec: healthCheck.timeoutSec,
588
+ unhealthyThreshold: healthCheck.unhealthyThreshold,
589
+ });
590
+ }
591
+
480
592
  function serializeHostRule(hostRule: IGceLoadBalancerHostRule): UnknownRecord {
481
593
  return {
482
594
  hostPatterns: [...hostRule.hostPatterns],
@@ -493,15 +605,63 @@ function serializeHostRule(hostRule: IGceLoadBalancerHostRule): UnknownRecord {
493
605
  }
494
606
 
495
607
  function normalizePortRange(value: unknown): string {
496
- return Array.isArray(value) ? value.map(String).join(',') : asString(value);
608
+ if (Array.isArray(value)) {
609
+ return value.map(asString).join(',');
610
+ }
611
+ return asString(value);
612
+ }
613
+
614
+ function normalizePorts(value: string): string[] {
615
+ return value.split(',');
616
+ }
617
+
618
+ function serializeRegionalExternalNetworkCommand(command: IGceLoadBalancerCommand): IGceSerializedLoadBalancerCommand {
619
+ const listener = command.listeners[0];
620
+ const backendService = command.backendServices[0];
621
+ const address = listener?.address;
622
+ // A passthrough NLB has a single forwarding rule, so its tier lives on the command; EXTERNAL_MANAGED
623
+ // sets it per listener instead (see serializeListener).
624
+ const networkTier = command.networkTier || undefined;
625
+
626
+ return {
627
+ cloudProvider: 'gce',
628
+ credentials: command.credentials,
629
+ loadBalancerName: listener?.name || command.name,
630
+ loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
631
+ name: listener?.name || command.name,
632
+ provider: 'gce',
633
+ region: command.region,
634
+ type: 'upsertLoadBalancer',
635
+ ...(address ? { ipAddress: serializeReservedAddress(address) } : {}),
636
+ ipProtocol: listener?.protocol || 'TCP',
637
+ ...(networkTier ? { networkTier } : {}),
638
+ ports: normalizePorts(listener?.portRange || ''),
639
+ ...(backendService ? { backendService: serializeRegionalExternalNetworkBackendService(backendService) } : {}),
640
+ };
641
+ }
642
+
643
+ function serializeRegionalExternalNetworkBackendService(service: IGceLoadBalancerBackendService): UnknownRecord {
644
+ const serialized = serializeGceBackendService(service);
645
+ if (service.healthCheck && typeof service.healthCheck === 'object') {
646
+ const healthCheck = serializeGceHealthCheck(service.healthCheck as IGceLoadBalancerHealthCheck);
647
+ if (healthCheck.healthCheckType || Object.keys(healthCheck).some((key) => key !== 'name' && key !== 'selfLink')) {
648
+ serialized.healthCheck = healthCheck;
649
+ } else {
650
+ serialized.healthCheck = service.healthCheck.name;
651
+ }
652
+ }
653
+ return serialized;
497
654
  }
498
655
 
499
656
  function defaultRegion(type: GceLoadBalancerType): string {
657
+ if (type === 'INTERNAL_MANAGED' || type === 'EXTERNAL_MANAGED' || type === 'REGIONAL_EXTERNAL_NETWORK') {
658
+ return '';
659
+ }
500
660
  return ['TCP', 'SSL', 'HTTP'].includes(type) ? 'global' : '';
501
661
  }
502
662
 
503
663
  function isHttpType(type: GceLoadBalancerType): boolean {
504
- return type === 'HTTP' || type === 'INTERNAL_MANAGED';
664
+ return type === 'HTTP' || type === 'INTERNAL_MANAGED' || type === 'EXTERNAL_MANAGED';
505
665
  }
506
666
 
507
667
  function asRecord(value: unknown): UnknownRecord {
@@ -532,3 +692,7 @@ function cloneValue<T>(value: T): T {
532
692
  }
533
693
  return value;
534
694
  }
695
+
696
+ function definedFields(fields: UnknownRecord): UnknownRecord {
697
+ return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
698
+ }