@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
@@ -234,7 +234,7 @@ describe('GCE load balancer submission', () => {
234
234
  },
235
235
  'pipeline',
236
236
  );
237
- const executeTask = jasmine.createSpy('executeTask');
237
+ const executeTask = vi.fn();
238
238
 
239
239
  const result = submitGceLoadBalancerCommand(command, { application: {} as any, executeTask });
240
240
 
@@ -244,7 +244,7 @@ describe('GCE load balancer submission', () => {
244
244
  'internal-api',
245
245
  ]);
246
246
  expect((result as any[])[0]).toEqual(
247
- jasmine.objectContaining({
247
+ expect.objectContaining({
248
248
  ipProtocol: 'TCP',
249
249
  loadBalancerName: 'internal-http',
250
250
  network: 'network-a',
@@ -260,6 +260,302 @@ describe('GCE load balancer submission', () => {
260
260
  expect(executeTask).not.toHaveBeenCalled();
261
261
  });
262
262
 
263
+ it('builds exact EXTERNAL_MANAGED create jobs with regional network, tier, and certificate URL', () => {
264
+ const regionalCertUrl =
265
+ '//certificatemanager.googleapis.com/projects/test/locations/europe-west1/certificates/regional-cert';
266
+ const command = normalizeGceLoadBalancerCommand(
267
+ {
268
+ account: 'account-a',
269
+ backendServices: [
270
+ {
271
+ account: 'account-a',
272
+ backends: [],
273
+ healthCheck: 'check-a',
274
+ kind: 'regionBackendService',
275
+ name: 'backend-a',
276
+ portName: 'http',
277
+ protocol: 'HTTP',
278
+ selfLink: 'projects/test/regions/europe-west1/backendServices/backend-a',
279
+ sessionAffinity: 'NONE',
280
+ },
281
+ ],
282
+ defaultService: 'backend-a',
283
+ healthChecks: [
284
+ {
285
+ checkIntervalSec: 10,
286
+ healthCheckType: 'HTTP',
287
+ healthyThreshold: 2,
288
+ kind: 'healthCheck',
289
+ name: 'check-a',
290
+ port: 80,
291
+ requestPath: '/health',
292
+ selfLink: 'projects/test/regions/europe-west1/healthChecks/check-a',
293
+ timeoutSec: 5,
294
+ unhealthyThreshold: 3,
295
+ },
296
+ ],
297
+ listeners: [
298
+ {
299
+ certificate: regionalCertUrl,
300
+ ipAddress: '203.0.113.10',
301
+ name: 'app-https',
302
+ networkTier: 'STANDARD',
303
+ port: 443,
304
+ protocol: 'HTTPS',
305
+ },
306
+ ],
307
+ loadBalancerType: 'EXTERNAL_MANAGED',
308
+ name: 'app-main',
309
+ network: 'network-a',
310
+ region: 'europe-west1',
311
+ },
312
+ 'create',
313
+ );
314
+
315
+ const jobs = buildGceLoadBalancerJobs(command);
316
+ const backendService = {
317
+ backends: [],
318
+ healthCheck: {
319
+ checkIntervalSec: 10,
320
+ healthCheckType: 'HTTP',
321
+ healthyThreshold: 2,
322
+ name: 'check-a',
323
+ port: 80,
324
+ requestPath: '/health',
325
+ timeoutSec: 5,
326
+ unhealthyThreshold: 3,
327
+ },
328
+ name: 'backend-a',
329
+ portName: 'http',
330
+ protocol: 'HTTP',
331
+ sessionAffinity: 'NONE',
332
+ };
333
+
334
+ expect(jobs).toEqual([
335
+ {
336
+ cloudProvider: 'gce',
337
+ credentials: 'account-a',
338
+ defaultService: backendService,
339
+ hostRules: [],
340
+ ipAddress: '203.0.113.10',
341
+ ipProtocol: 'TCP',
342
+ loadBalancerName: 'app-https',
343
+ loadBalancerType: 'EXTERNAL_MANAGED',
344
+ name: 'app-https',
345
+ network: 'network-a',
346
+ networkTier: 'STANDARD',
347
+ portRange: '443',
348
+ provider: 'gce',
349
+ region: 'europe-west1',
350
+ type: 'upsertLoadBalancer',
351
+ urlMapName: 'app-main',
352
+ certificate: regionalCertUrl,
353
+ },
354
+ ]);
355
+ expect((jobs[0] as any).certificateMap).toBeUndefined();
356
+ expect(jobs[0].listeners).toBeUndefined();
357
+ expect(jobs[0].backendServices).toBeUndefined();
358
+ expect(jobs[0].healthChecks).toBeUndefined();
359
+ });
360
+
361
+ it('computes EXTERNAL_MANAGED listener deletions and backend diffs for edits', () => {
362
+ const command = normalizeGceLoadBalancerCommand(
363
+ {
364
+ account: 'account-a',
365
+ defaultService: {
366
+ backends: [],
367
+ healthCheck: 'default-check',
368
+ name: 'default-backend',
369
+ },
370
+ healthChecks: [
371
+ { healthCheckType: 'HTTP', name: 'default-check', port: 80 },
372
+ { healthCheckType: 'HTTP', name: 'removed-check', port: 8080 },
373
+ ],
374
+ listeners: [
375
+ { name: 'app-http', port: 80, protocol: 'HTTP' },
376
+ {
377
+ certificate: 'regional-cert',
378
+ name: 'app-https',
379
+ networkTier: 'STANDARD',
380
+ port: 443,
381
+ protocol: 'HTTPS',
382
+ },
383
+ ],
384
+ loadBalancerType: 'EXTERNAL_MANAGED',
385
+ name: 'app-main',
386
+ network: 'network-a',
387
+ region: 'europe-west1',
388
+ urlMapName: 'app-main',
389
+ },
390
+ 'edit',
391
+ );
392
+ command.listeners = command.listeners.filter(({ name }) => name === 'app-http');
393
+ command.backendServices = command.backendServices.filter(({ name }) => name === 'default-backend');
394
+ command.healthChecks = command.healthChecks.filter(({ name }) => name === 'default-check');
395
+
396
+ const [job] = buildGceLoadBalancerJobs(command);
397
+
398
+ expect(job.listenersToDelete).toEqual(['app-https']);
399
+ expect(job.backendServiceDiff).toEqual([]);
400
+ expect(job).toEqual(
401
+ expect.objectContaining({
402
+ loadBalancerName: 'app-http',
403
+ loadBalancerType: 'EXTERNAL_MANAGED',
404
+ network: 'network-a',
405
+ networkTier: undefined,
406
+ region: 'europe-west1',
407
+ urlMapName: 'app-main',
408
+ }),
409
+ );
410
+ });
411
+
412
+ it('returns EXTERNAL_MANAGED pipeline operations without executing a task', () => {
413
+ const command = normalizeGceLoadBalancerCommand(
414
+ {
415
+ account: 'account-a',
416
+ backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
417
+ defaultService: 'backend-a',
418
+ healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
419
+ listeners: [
420
+ {
421
+ ipAddress: '203.0.113.10',
422
+ name: 'external-http',
423
+ networkTier: 'STANDARD',
424
+ port: 80,
425
+ protocol: 'HTTP',
426
+ },
427
+ ],
428
+ loadBalancerType: 'EXTERNAL_MANAGED',
429
+ name: 'app-external',
430
+ network: 'network-a',
431
+ region: 'europe-west1',
432
+ },
433
+ 'pipeline',
434
+ );
435
+ const executeTask = vi.fn();
436
+
437
+ const result = submitGceLoadBalancerCommand(command, { application: {} as any, executeTask });
438
+
439
+ expect(result).toEqual(buildGceLoadBalancerJobs(command));
440
+ expect((result as any[])[0]).toEqual(
441
+ expect.objectContaining({
442
+ ipAddress: '203.0.113.10',
443
+ loadBalancerName: 'external-http',
444
+ loadBalancerType: 'EXTERNAL_MANAGED',
445
+ network: 'network-a',
446
+ networkTier: 'STANDARD',
447
+ portRange: '80',
448
+ region: 'europe-west1',
449
+ urlMapName: 'app-external',
450
+ }),
451
+ );
452
+ expect(executeTask).not.toHaveBeenCalled();
453
+ });
454
+
455
+ it('serializes the exact REGIONAL_EXTERNAL_NETWORK Clouddriver contract', () => {
456
+ const command = normalizeGceLoadBalancerCommand(
457
+ {
458
+ account: 'account-a',
459
+ backendService: {
460
+ healthCheck: { healthCheckType: 'TCP', name: 'tcp-check', port: 80 },
461
+ name: 'app-main',
462
+ sessionAffinity: 'CLIENT_IP',
463
+ },
464
+ ipAddress: '35.1.2.3',
465
+ ipProtocol: 'TCP',
466
+ loadBalancerName: 'app-main',
467
+ loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
468
+ networkTier: 'PREMIUM',
469
+ ports: ['80', '443'],
470
+ region: 'europe-west1',
471
+ },
472
+ 'pipeline',
473
+ );
474
+
475
+ const [job] = buildGceLoadBalancerJobs(command);
476
+
477
+ expect(job).toEqual({
478
+ backendService: {
479
+ healthCheck: { healthCheckType: 'TCP', name: 'tcp-check', port: 80 },
480
+ name: 'app-main',
481
+ sessionAffinity: 'CLIENT_IP',
482
+ },
483
+ cloudProvider: 'gce',
484
+ credentials: 'account-a',
485
+ ipAddress: '35.1.2.3',
486
+ ipProtocol: 'TCP',
487
+ loadBalancerName: 'app-main',
488
+ loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
489
+ name: 'app-main',
490
+ networkTier: 'PREMIUM',
491
+ ports: ['80', '443'],
492
+ provider: 'gce',
493
+ region: 'europe-west1',
494
+ type: 'upsertLoadBalancer',
495
+ });
496
+ expect((job as any).portRange).toBeUndefined();
497
+ expect((job as any).backendServices).toBeUndefined();
498
+ expect((job as any).healthChecks).toBeUndefined();
499
+ });
500
+
501
+ it('preserves REGIONAL_EXTERNAL_NETWORK ipAddress and networkTier during edit serialization', () => {
502
+ const command = normalizeGceLoadBalancerCommand(
503
+ {
504
+ account: 'account-a',
505
+ backendService: {
506
+ healthCheck: { healthCheckType: 'UDP', name: 'udp-check', port: 53 },
507
+ name: 'app-main',
508
+ sessionAffinity: 'CLIENT_IP_PROTO',
509
+ },
510
+ ipAddress: '35.1.2.3',
511
+ ipProtocol: 'UDP',
512
+ loadBalancerName: 'app-main',
513
+ loadBalancerType: 'REGIONAL_EXTERNAL_NETWORK',
514
+ networkTier: 'PREMIUM',
515
+ ports: ['53'],
516
+ region: 'europe-west1',
517
+ },
518
+ 'edit',
519
+ );
520
+ const [job] = buildGceLoadBalancerJobs(command);
521
+
522
+ expect(job.ipAddress).toBe('35.1.2.3');
523
+ expect(job.networkTier).toBe('PREMIUM');
524
+ expect(job.ipProtocol).toBe('UDP');
525
+ expect(job.ports).toEqual(['53']);
526
+ });
527
+
528
+ (['HTTP', 'INTERNAL_MANAGED'] as const).forEach((loadBalancerType) => {
529
+ it(`builds ${loadBalancerType} jobs without networkTier`, () => {
530
+ const command = normalizeGceLoadBalancerCommand(
531
+ {
532
+ account: 'account-a',
533
+ backendServices: [{ healthCheck: 'check-a', name: 'backend-a', portName: 'http' }],
534
+ defaultService: 'backend-a',
535
+ healthChecks: [{ healthCheckType: 'HTTP', name: 'check-a', port: 80, requestPath: '/health' }],
536
+ listeners: [
537
+ {
538
+ ipAddress: '203.0.113.10',
539
+ name: 'frontend',
540
+ networkTier: 'STANDARD',
541
+ port: 80,
542
+ protocol: 'HTTP',
543
+ },
544
+ ],
545
+ loadBalancerType,
546
+ name: 'web',
547
+ network: loadBalancerType === 'INTERNAL_MANAGED' ? 'network-a' : undefined,
548
+ region: loadBalancerType === 'INTERNAL_MANAGED' ? 'europe-west1' : 'global',
549
+ subnet: loadBalancerType === 'INTERNAL_MANAGED' ? 'subnet-a' : undefined,
550
+ },
551
+ 'create',
552
+ );
553
+
554
+ const [job] = buildGceLoadBalancerJobs(command);
555
+ expect(job.networkTier).toBeUndefined();
556
+ });
557
+ });
558
+
263
559
  it('round-trips a flat HTTP pipeline operation without nesting or changing its identities', () => {
264
560
  const operation = {
265
561
  cloudProvider: 'gce',
@@ -10,7 +10,11 @@ import type {
10
10
  IGceResourceReference,
11
11
  IGceSerializedLoadBalancerCommand,
12
12
  } from './gceLoadBalancerModels';
13
- import { serializeGceLoadBalancerCommand } from './gceLoadBalancerModels';
13
+ import {
14
+ serializeGceBackendService,
15
+ serializeGceHealthCheck,
16
+ serializeGceLoadBalancerCommand,
17
+ } from './gceLoadBalancerModels';
14
18
 
15
19
  export interface IGceLoadBalancerSubmissionDependencies {
16
20
  application: Application;
@@ -23,9 +27,15 @@ export type GceLoadBalancerSubmissionResult =
23
27
  | GceLoadBalancerOperationList
24
28
  | Promise<ITask>;
25
29
 
30
+ function isFlattenedHttpLoadBalancerType(loadBalancerType: IGceLoadBalancerCommand['loadBalancerType']): boolean {
31
+ return (
32
+ loadBalancerType === 'HTTP' || loadBalancerType === 'INTERNAL_MANAGED' || loadBalancerType === 'EXTERNAL_MANAGED'
33
+ );
34
+ }
35
+
26
36
  export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceLoadBalancerOperationList {
27
37
  const serialized = serializeGceLoadBalancerCommand(command);
28
- if (command.loadBalancerType !== 'HTTP' && command.loadBalancerType !== 'INTERNAL_MANAGED') {
38
+ if (!isFlattenedHttpLoadBalancerType(command.loadBalancerType)) {
29
39
  return [serialized];
30
40
  }
31
41
 
@@ -37,7 +47,10 @@ export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceL
37
47
  ...shared
38
48
  } = serialized;
39
49
  const backendServices = new Map(
40
- command.backendServices.map((service) => [service.name, serializeBackendService(service, command.healthChecks)]),
50
+ command.backendServices.map((service) => [
51
+ service.name,
52
+ serializeBackendService(service, command.healthChecks, command.loadBalancerType === 'EXTERNAL_MANAGED'),
53
+ ]),
41
54
  );
42
55
  const defaultService = resolveBackendService(command.defaultService, backendServices);
43
56
  const hostRules = command.hostRules.flatMap((hostRule) =>
@@ -59,6 +72,7 @@ export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceL
59
72
 
60
73
  const jobs = ((listeners || []) as Array<Record<string, unknown>>).map((listener) => {
61
74
  const name = String(listener.name);
75
+ const networkTier = listener.networkTier as string | undefined;
62
76
  return {
63
77
  ...shared,
64
78
  certificate: listener.certificate || null,
@@ -70,6 +84,7 @@ export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceL
70
84
  name,
71
85
  portRange: listener.portRange,
72
86
  ...(listener.subnet ? { subnet: listener.subnet } : {}),
87
+ ...(command.loadBalancerType === 'EXTERNAL_MANAGED' ? { networkTier } : {}),
73
88
  urlMapName: command.name,
74
89
  } as IGceSerializedLoadBalancerCommand;
75
90
  });
@@ -82,7 +97,13 @@ export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceL
82
97
  .filter((name) => !listenerNames.has(name));
83
98
  jobs[0].backendServiceDiff = command.original.backendServices
84
99
  .filter((service) => !isEqual(service, currentBackendServices.get(service.name)))
85
- .map((service) => serializeBackendService(service, command.original!.healthChecks));
100
+ .map((service) =>
101
+ serializeBackendService(
102
+ service,
103
+ command.original!.healthChecks,
104
+ command.loadBalancerType === 'EXTERNAL_MANAGED',
105
+ ),
106
+ );
86
107
  }
87
108
 
88
109
  return jobs;
@@ -91,10 +112,19 @@ export function buildGceLoadBalancerJobs(command: IGceLoadBalancerCommand): GceL
91
112
  function serializeBackendService(
92
113
  service: IGceLoadBalancerBackendService,
93
114
  healthChecks: IGceLoadBalancerHealthCheck[],
115
+ editorFieldsOnly = false,
94
116
  ): Record<string, unknown> {
95
117
  const healthCheck = service.healthCheck
96
118
  ? healthChecks.find(({ name }) => name === service.healthCheck?.name) || service.healthCheck
97
119
  : undefined;
120
+ // EXTERNAL_MANAGED sends only the backend-service and health-check fields its editor supports
121
+ // instead of echoing every cached field; other types send the cached object.
122
+ if (editorFieldsOnly) {
123
+ return {
124
+ ...serializeGceBackendService(service),
125
+ ...(healthCheck ? { healthCheck: serializeGceHealthCheck(healthCheck as IGceLoadBalancerHealthCheck) } : {}),
126
+ };
127
+ }
98
128
  return {
99
129
  ...service,
100
130
  ...(healthCheck ? { healthCheck: { ...healthCheck } } : {}),
@@ -114,7 +144,7 @@ export function submitGceLoadBalancerCommand(
114
144
  ): GceLoadBalancerSubmissionResult {
115
145
  const jobs = buildGceLoadBalancerJobs(command);
116
146
  if (command.mode === 'pipeline') {
117
- return command.loadBalancerType === 'HTTP' || command.loadBalancerType === 'INTERNAL_MANAGED' ? jobs : jobs[0];
147
+ return isFlattenedHttpLoadBalancerType(command.loadBalancerType) ? jobs : jobs[0];
118
148
  }
119
149
 
120
150
  return Promise.resolve(
@@ -1,68 +1,59 @@
1
- import React from 'react';
2
- import { mount } from 'enzyme';
1
+ import type { Mocked } from 'vitest';
2
+ import { act } from '@testing-library/react';
3
3
 
4
- import type { IGceLoadBalancerDataReaders, IGceLoadBalancerDataState } from './gceLoadBalancerData';
4
+ import { renderHookHarness } from '../../../../../core/src/utils/testUtils/hookHarness';
5
+ import type { IGceLoadBalancerDataReaders } from './gceLoadBalancerData';
5
6
  import { useGceLoadBalancerData } from './useGceLoadBalancerData';
6
7
 
7
8
  describe('useGceLoadBalancerData', () => {
8
9
  it('reloads data when the account changes', async () => {
9
10
  const readers = testReaders();
10
- const states: IGceLoadBalancerDataState[] = [];
11
- const wrapper = mount(<Harness account="first" readers={readers} onState={(state) => states.push(state)} />);
11
+ const hook = renderHookHarness(
12
+ ({ account, dataReaders }: { account: string; dataReaders: IGceLoadBalancerDataReaders }) =>
13
+ useGceLoadBalancerData(account, dataReaders),
14
+ { account: 'first', dataReaders: readers },
15
+ );
12
16
 
13
- await settle();
14
- wrapper.setProps({ account: 'second' });
15
- await settle();
17
+ await act(settle);
18
+ hook.rerenderHook({ account: 'second', dataReaders: readers });
19
+ await act(settle);
16
20
 
17
- expect(readers.regions.calls.allArgs()).toEqual([['first'], ['second']]);
18
- expect(states[states.length - 1].status).toBe('ready');
19
- wrapper.unmount();
21
+ expect(readers.regions.mock.calls).toEqual([['first'], ['second']]);
22
+ expect(hook.result.current.status).toBe('ready');
23
+ hook.unmount();
20
24
  });
21
25
 
22
26
  it('does not publish a request result after unmount', async () => {
23
27
  const regions = deferred<unknown[]>();
24
28
  const readers = testReaders();
25
- readers.regions.and.returnValue(regions.promise);
26
- const onState = jasmine.createSpy('onState');
27
- const wrapper = mount(<Harness account="test-account" readers={readers} onState={onState} />);
28
- await settle();
29
- const callsBeforeUnmount = onState.calls.count();
29
+ readers.regions.mockReturnValue(regions.promise);
30
+ const hook = renderHookHarness(
31
+ ({ account, dataReaders }: { account: string; dataReaders: IGceLoadBalancerDataReaders }) =>
32
+ useGceLoadBalancerData(account, dataReaders),
33
+ { account: 'test-account', dataReaders: readers },
34
+ );
35
+ await act(settle);
36
+ const stateBeforeUnmount = hook.result.current;
30
37
 
31
- wrapper.unmount();
38
+ hook.unmount();
32
39
  regions.resolve([{ name: 'late-region' }]);
33
- await settle();
40
+ await act(settle);
34
41
 
35
- expect(onState.calls.count()).toBe(callsBeforeUnmount);
42
+ expect(hook.result.current).toBe(stateBeforeUnmount);
36
43
  });
37
44
  });
38
45
 
39
- function Harness({
40
- account,
41
- readers,
42
- onState,
43
- }: {
44
- account: string;
45
- readers: IGceLoadBalancerDataReaders;
46
- onState: (state: IGceLoadBalancerDataState) => void;
47
- }) {
48
- const state = useGceLoadBalancerData(account, readers);
49
- React.useEffect(() => {
50
- onState(state);
51
- }, [state.status, state.data, state.error]);
52
- return null;
53
- }
54
-
55
- function testReaders(): jasmine.SpyObj<IGceLoadBalancerDataReaders> {
56
- return jasmine.createSpyObj<IGceLoadBalancerDataReaders>('readers', {
57
- accounts: Promise.resolve([]),
58
- addresses: Promise.resolve([]),
59
- backendServices: Promise.resolve([]),
60
- certificates: Promise.resolve([]),
61
- healthChecks: Promise.resolve([]),
62
- networks: Promise.resolve([]),
63
- regions: Promise.resolve([]),
64
- subnets: Promise.resolve([]),
65
- });
46
+ function testReaders(): Mocked<IGceLoadBalancerDataReaders> {
47
+ return {
48
+ accounts: vi.fn().mockReturnValue(Promise.resolve([])),
49
+ addresses: vi.fn().mockReturnValue(Promise.resolve([])),
50
+ backendServices: vi.fn().mockReturnValue(Promise.resolve([])),
51
+ certificates: vi.fn().mockReturnValue(Promise.resolve([])),
52
+ healthChecks: vi.fn().mockReturnValue(Promise.resolve([])),
53
+ networks: vi.fn().mockReturnValue(Promise.resolve([])),
54
+ regions: vi.fn().mockReturnValue(Promise.resolve([])),
55
+ subnets: vi.fn().mockReturnValue(Promise.resolve([])),
56
+ };
66
57
  }
67
58
 
68
59
  function deferred<T>() {