@spinnaker/google 2026.0.2 → 2026.1.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 (33) hide show
  1. package/dist/domain/loadBalancer.d.ts +4 -2
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/loadBalancer/configure/choice/gceLoadBalancerPipelineModal.d.ts +9 -0
  5. package/dist/loadBalancer/configure/http/templates.d.ts +3 -0
  6. package/package.json +3 -3
  7. package/src/domain/loadBalancer.ts +4 -2
  8. package/src/gce.module.ts +2 -0
  9. package/src/help/gce.help.ts +2 -0
  10. package/src/loadBalancer/configure/choice/gceLoadBalancerChoice.modal.spec.js +66 -0
  11. package/src/loadBalancer/configure/choice/gceLoadBalancerChoice.modal.ts +20 -5
  12. package/src/loadBalancer/configure/choice/gceLoadBalancerPipelineModal.ts +47 -0
  13. package/src/loadBalancer/configure/gceL4LoadBalancerPipeline.spec.js +143 -0
  14. package/src/loadBalancer/configure/http/createHttpLoadBalancer.controller.js +22 -0
  15. package/src/loadBalancer/configure/http/createHttpLoadBalancer.controller.spec.js +96 -0
  16. package/src/loadBalancer/configure/http/listeners/listener.component.html +79 -24
  17. package/src/loadBalancer/configure/http/listeners/listener.component.js +82 -0
  18. package/src/loadBalancer/configure/http/listeners/listener.component.spec.js +125 -0
  19. package/src/loadBalancer/configure/http/templates.ts +3 -0
  20. package/src/loadBalancer/configure/http/transformer.service.js +16 -1
  21. package/src/loadBalancer/configure/http/transformer.service.spec.js +182 -0
  22. package/src/loadBalancer/configure/internal/gceCreateInternalLoadBalancer.controller.ts +8 -0
  23. package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.spec.js +89 -0
  24. package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.ts +23 -0
  25. package/src/loadBalancer/configure/network/createLoadBalancer.controller.js +48 -32
  26. package/src/loadBalancer/configure/network/createLoadBalancer.controller.spec.js +36 -0
  27. package/src/loadBalancer/configure/ssl/gceCreateSslLoadBalancer.controller.ts +8 -0
  28. package/src/loadBalancer/configure/tcp/gceCreateTcpLoadBalancer.controller.ts +8 -0
  29. package/src/loadBalancer/details/loadBalancerType/loadBalancerType.component.js +3 -1
  30. package/src/loadBalancer/details/loadBalancerType/loadBalancerType.component.spec.js +85 -0
  31. package/src/loadBalancer/loadBalancer.setTransformer.ts +1 -0
  32. package/src/serverGroup/configure/serverGroupConfiguration.service.js +0 -1
  33. package/src/serverGroup/configure/wizard/autoScalingPolicy/autoScalingPolicySelector.component.js +1 -4
@@ -64,11 +64,93 @@ module(GOOGLE_LOADBALANCER_CONFIGURE_HTTP_LISTENERS_LISTENER_COMPONENT, [GCE_ADD
64
64
  };
65
65
 
66
66
  this.isHttps = (port) => port === 443 || port === '443';
67
+ // certificateMap writes are only supported on global external/classic HTTPS proxies (HTTP
68
+ // type). INTERNAL_MANAGED regional proxies do not support certificateMap per GCP docs.
69
+ this.supportsCertificateMap = () => this.command.loadBalancer.loadBalancerType === 'HTTP';
70
+ this.certificateMapPattern = /^[a-z]([-a-z0-9]*[a-z0-9])?$/;
71
+ // Extracts the short resource name from a certificateMap value. Users may paste a full
72
+ // Certificate Manager URL (e.g. //certificatemanager.googleapis.com/projects/p/locations/
73
+ // global/certificateMaps/my-map); only the last path segment (the map name) is stored.
74
+ // The server reconstructs the full URL using GCEUtil.buildCertificateMapUrl.
75
+ this.getCertificateMapName = (certificateMap) => {
76
+ if (!_.isString(certificateMap)) {
77
+ return certificateMap;
78
+ }
79
+ const normalized = certificateMap.trim();
80
+ return normalized ? _.last(normalized.split('/')) : normalized;
81
+ };
82
+
83
+ // Reconcile the XOR between certificate and certificateMap on a listener. Ensures only
84
+ // one is active at a time. Also infers certificateSource for existing listeners that were
85
+ // created before the certificateSource field existed (backward compat).
86
+ this.syncCertificateState = (listener) => {
87
+ if (!this.supportsCertificateMap()) {
88
+ listener.certificateSource = 'certificate';
89
+ listener.certificateMap = null;
90
+ return;
91
+ }
92
+
93
+ if (!listener.certificateSource) {
94
+ listener.certificateSource = listener.certificateMap ? 'certificateMap' : 'certificate';
95
+ }
96
+
97
+ if (listener.certificateSource === 'certificateMap') {
98
+ listener.certificate = null;
99
+ if (_.isString(listener.certificateMap)) {
100
+ listener.certificateMap = this.getCertificateMapName(listener.certificateMap);
101
+ }
102
+ } else {
103
+ listener.certificateMap = null;
104
+ }
105
+ };
106
+
107
+ this.onCertificateSourceChanged = (listener) => {
108
+ if (!this.supportsCertificateMap()) {
109
+ listener.certificateSource = 'certificate';
110
+ listener.certificateMap = null;
111
+ return;
112
+ }
113
+
114
+ if (listener.certificateSource === 'certificateMap') {
115
+ listener.certificate = null;
116
+ } else {
117
+ listener.certificateMap = null;
118
+ }
119
+ };
120
+
121
+ this.onCertificateSelected = (listener) => {
122
+ if (listener.certificate) {
123
+ listener.certificateSource = 'certificate';
124
+ listener.certificateMap = null;
125
+ }
126
+ };
127
+
128
+ this.onCertificateMapChanged = (listener) => {
129
+ if (_.isString(listener.certificateMap)) {
130
+ listener.certificateMap = this.getCertificateMapName(listener.certificateMap);
131
+ }
132
+ if (listener.certificateMap) {
133
+ listener.certificateSource = 'certificateMap';
134
+ listener.certificate = null;
135
+ }
136
+ };
137
+
138
+ this.onPortChanged = (listener) => {
139
+ if (!this.isHttps(listener.port)) {
140
+ listener.certificate = null;
141
+ listener.certificateMap = null;
142
+ listener.certificateSource = 'certificate';
143
+ } else {
144
+ this.syncCertificateState(listener);
145
+ }
146
+ };
67
147
 
68
148
  if (!this.listener.name) {
69
149
  this.updateName(this.listener, this.application.name);
70
150
  }
71
151
 
152
+ this.onPortChanged(this.listener);
153
+
72
154
  this.onAddressSelect = (address) => {
73
155
  if (address) {
74
156
  this.listener.ipAddress = address.address;
@@ -0,0 +1,125 @@
1
+ import { mock } from 'angular';
2
+
3
+ describe('Component: gceListener', () => {
4
+ let $componentController;
5
+
6
+ beforeEach(mock.module(require('./listener.component').name));
7
+
8
+ beforeEach(
9
+ mock.inject((_$componentController_) => {
10
+ $componentController = _$componentController_;
11
+ }),
12
+ );
13
+
14
+ function buildBindings(listenerOverrides = {}, loadBalancerOverrides = {}) {
15
+ return {
16
+ command: {
17
+ backingData: {
18
+ certificates: [],
19
+ loadBalancerMap: {},
20
+ subnetMap: { default: [] },
21
+ addresses: [],
22
+ },
23
+ loadBalancer: {
24
+ credentials: 'test-account',
25
+ loadBalancerType: 'HTTP',
26
+ network: 'default',
27
+ region: 'us-central1',
28
+ ...loadBalancerOverrides,
29
+ },
30
+ },
31
+ listener: {
32
+ name: 'app-main',
33
+ port: 443,
34
+ certificate: null,
35
+ certificateMap: null,
36
+ created: false,
37
+ ...listenerOverrides,
38
+ },
39
+ deleteListener: () => {},
40
+ index: 0,
41
+ application: { name: 'app' },
42
+ };
43
+ }
44
+
45
+ function buildController(listenerOverrides = {}, loadBalancerOverrides = {}) {
46
+ const ctrl = $componentController('gceListener', {}, buildBindings(listenerOverrides, loadBalancerOverrides));
47
+ ctrl.$onInit();
48
+ return ctrl;
49
+ }
50
+
51
+ it('clears certificate when switching to certificateMap source', () => {
52
+ const ctrl = buildController({ certificate: 'legacy-cert', certificateSource: 'certificate' });
53
+
54
+ ctrl.listener.certificateSource = 'certificateMap';
55
+ ctrl.onCertificateSourceChanged(ctrl.listener);
56
+
57
+ expect(ctrl.listener.certificate).toBeNull();
58
+ });
59
+
60
+ it('clears certificateMap when switching to certificate source', () => {
61
+ const ctrl = buildController({
62
+ certificate: null,
63
+ certificateMap: 'cm',
64
+ certificateSource: 'certificateMap',
65
+ });
66
+
67
+ ctrl.listener.certificateSource = 'certificate';
68
+ ctrl.onCertificateSourceChanged(ctrl.listener);
69
+
70
+ expect(ctrl.listener.certificateMap).toBeNull();
71
+ });
72
+
73
+ it('forces certificate source for internal managed load balancers', () => {
74
+ const ctrl = buildController(
75
+ {
76
+ certificateMap: 'cm',
77
+ certificateSource: 'certificateMap',
78
+ },
79
+ { loadBalancerType: 'INTERNAL_MANAGED' },
80
+ );
81
+
82
+ expect(ctrl.listener.certificateSource).toBe('certificate');
83
+ expect(ctrl.listener.certificateMap).toBeNull();
84
+ });
85
+
86
+ it('clears cert fields when listener port changes away from HTTPS', () => {
87
+ const ctrl = buildController({
88
+ certificate: 'legacy-cert',
89
+ certificateMap: 'cm',
90
+ certificateSource: 'certificateMap',
91
+ });
92
+
93
+ ctrl.listener.port = 80;
94
+ ctrl.onPortChanged(ctrl.listener);
95
+
96
+ expect(ctrl.listener.certificate).toBeNull();
97
+ expect(ctrl.listener.certificateMap).toBeNull();
98
+ expect(ctrl.listener.certificateSource).toBe('certificate');
99
+ });
100
+
101
+ it('normalizes certificateMap URLs to map names', () => {
102
+ const ctrl = buildController({
103
+ certificate: null,
104
+ certificateMap: '//certificatemanager.googleapis.com/projects/p/locations/global/certificateMaps/cm',
105
+ certificateSource: 'certificateMap',
106
+ });
107
+
108
+ expect(ctrl.listener.certificateMap).toBe('cm');
109
+ });
110
+
111
+ it('validates certificateMap names with expected regex edge cases', () => {
112
+ const ctrl = buildController();
113
+
114
+ const validNames = ['a', 'my-map', 'm1', 'map-123', 'a-b-c-1'];
115
+ const invalidNames = ['', '1map', '-map', 'map-', 'map_name', 'map.name', 'Map'];
116
+
117
+ validNames.forEach((name) => {
118
+ expect(ctrl.certificateMapPattern.test(name)).toBe(true);
119
+ });
120
+
121
+ invalidNames.forEach((name) => {
122
+ expect(ctrl.certificateMapPattern.test(name)).toBe(false);
123
+ });
124
+ });
125
+ });
@@ -5,6 +5,7 @@ export class HttpLoadBalancerTemplate {
5
5
  public region = 'global';
6
6
  public loadBalancerType = 'HTTP';
7
7
  public certificate = '';
8
+ public certificateMap = '';
8
9
  public defaultService: BackendServiceTemplate;
9
10
  public hostRules: HostRuleTemplate[] = [];
10
11
  public listeners: ListenerTemplate[] = [];
@@ -45,4 +46,6 @@ export class ListenerTemplate {
45
46
  public name: string;
46
47
  public port: number;
47
48
  public certificate: string | null = null;
49
+ public certificateMap: string | null = null;
50
+ public certificateSource: 'certificate' | 'certificateMap' = 'certificate';
48
51
  }
@@ -81,9 +81,16 @@ module(GOOGLE_LOADBALANCER_CONFIGURE_HTTP_TRANSFORMER_SERVICE, []).factory(
81
81
  return loadBalancer.listeners.map((listener) => {
82
82
  let command = _.cloneDeep(loadBalancer);
83
83
  command = _.omit(command, keysToOmit);
84
+ // Determine the authoritative cert source. The certificateSource field is explicit when
85
+ // present, but listeners deserialized from LBs created before this field existed may only
86
+ // have certificate/certificateMap values without a source. The fallback condition handles
87
+ // that: no certificate + truthy certificateMap implies certificateMap mode.
88
+ const useCertificateMap =
89
+ listener.certificateSource === 'certificateMap' || (!listener.certificate && !!listener.certificateMap);
84
90
  command.name = listener.name;
85
91
  command.portRange = listener.port;
86
- command.certificate = listener.certificate || null;
92
+ command.certificate = useCertificateMap ? null : listener.certificate || null;
93
+ command.certificateMap = useCertificateMap ? listener.certificateMap || null : null;
87
94
  command.ipAddress = listener.ipAddress;
88
95
  command.subnet = listener.subnet;
89
96
 
@@ -151,6 +158,14 @@ module(GOOGLE_LOADBALANCER_CONFIGURE_HTTP_TRANSFORMER_SERVICE, []).factory(
151
158
  listener.stack = stack;
152
159
  listener.detail = freeFormDetails;
153
160
  listener.created = true;
161
+ listener.certificate = listener.certificate || null;
162
+ // Normalize certificateMap from the full Certificate Manager URL returned by the server
163
+ // (e.g. //certificatemanager.googleapis.com/projects/p/locations/global/certificateMaps/cm)
164
+ // to its short resource name. This mirrors getCertificateMapName in listener.component.js.
165
+ listener.certificateMap = listener.certificateMap ? _.last(listener.certificateMap.split('/')) : null;
166
+ // Infer certificateSource for listeners deserialized from LBs created before this field
167
+ // existed. The presence of certificateMap implies certificateMap mode.
168
+ listener.certificateSource = listener.certificateMap ? 'certificateMap' : 'certificate';
154
169
  });
155
170
 
156
171
  return loadBalancer.listeners;
@@ -0,0 +1,182 @@
1
+ import { mock } from 'angular';
2
+
3
+ describe('Service: gceHttpLoadBalancerTransformer', () => {
4
+ let gceHttpLoadBalancerTransformer;
5
+
6
+ beforeEach(mock.module(require('./transformer.service').name));
7
+
8
+ beforeEach(
9
+ mock.inject((_gceHttpLoadBalancerTransformer_) => {
10
+ gceHttpLoadBalancerTransformer = _gceHttpLoadBalancerTransformer_;
11
+ }),
12
+ );
13
+
14
+ function buildBaseCommand(listener) {
15
+ return {
16
+ loadBalancer: {
17
+ listeners: [listener],
18
+ hostRules: [],
19
+ backendServices: [],
20
+ healthChecks: [],
21
+ defaultService: 'default-service',
22
+ },
23
+ backingData: {
24
+ healthChecksKeyedByName: {
25
+ 'hc-1': { name: 'hc-1' },
26
+ },
27
+ backendServicesKeyedByName: {
28
+ 'default-service': {
29
+ name: 'default-service',
30
+ sessionAffinity: 'NONE',
31
+ healthCheck: 'hc-1',
32
+ },
33
+ },
34
+ },
35
+ };
36
+ }
37
+
38
+ it('serializes certificateMap listeners as certificateMap-only commands', () => {
39
+ const command = buildBaseCommand({
40
+ name: 'app-main',
41
+ port: 443,
42
+ certificate: null,
43
+ certificateMap: 'cm',
44
+ certificateSource: 'certificateMap',
45
+ ipAddress: '10.0.0.1',
46
+ subnet: null,
47
+ });
48
+
49
+ const commands = gceHttpLoadBalancerTransformer.serialize(command, null);
50
+
51
+ expect(commands.length).toBe(1);
52
+ expect(commands[0].certificate).toBeNull();
53
+ expect(commands[0].certificateMap).toBe('cm');
54
+ });
55
+
56
+ it('serializes legacy certificate listeners without certificateMap', () => {
57
+ const command = buildBaseCommand({
58
+ name: 'app-main',
59
+ port: 443,
60
+ certificate: 'legacy-cert',
61
+ certificateMap: 'cm',
62
+ certificateSource: 'certificate',
63
+ ipAddress: '10.0.0.1',
64
+ subnet: null,
65
+ });
66
+
67
+ const commands = gceHttpLoadBalancerTransformer.serialize(command, null);
68
+
69
+ expect(commands.length).toBe(1);
70
+ expect(commands[0].certificate).toBe('legacy-cert');
71
+ expect(commands[0].certificateMap).toBeNull();
72
+ });
73
+
74
+ it('deserializes certificateMap listeners with certificateMap mode', () => {
75
+ const loadBalancer = {
76
+ defaultService: {
77
+ name: 'default-service',
78
+ healthCheck: { name: 'hc-1' },
79
+ sessionAffinity: 'NONE',
80
+ },
81
+ hostRules: [],
82
+ listeners: [
83
+ {
84
+ name: 'app-main',
85
+ certificate: null,
86
+ certificateMap: '//certificatemanager.googleapis.com/projects/p/locations/global/certificateMaps/cm',
87
+ },
88
+ ],
89
+ network: 'default',
90
+ region: 'global',
91
+ urlMapName: 'app',
92
+ account: 'test-account',
93
+ };
94
+
95
+ const commandLoadBalancer = gceHttpLoadBalancerTransformer.deserialize(loadBalancer);
96
+
97
+ expect(commandLoadBalancer.listeners.length).toBe(1);
98
+ expect(commandLoadBalancer.listeners[0].certificateSource).toBe('certificateMap');
99
+ expect(commandLoadBalancer.listeners[0].certificate).toBeNull();
100
+ expect(commandLoadBalancer.listeners[0].certificateMap).toBe('cm');
101
+ });
102
+
103
+ // The serialize fallback path: when certificateSource is absent, the
104
+ // condition `!listener.certificate && !!listener.certificateMap` at line 89
105
+ // of transformer.service.js infers certificateMap mode. This covers LBs
106
+ // created before the certificateSource field existed.
107
+ it('serializes certificateMap listener without explicit certificateSource via fallback', () => {
108
+ const command = buildBaseCommand({
109
+ name: 'app-main',
110
+ port: 443,
111
+ certificate: null,
112
+ certificateMap: 'cm',
113
+ ipAddress: '10.0.0.1',
114
+ subnet: null,
115
+ });
116
+
117
+ const commands = gceHttpLoadBalancerTransformer.serialize(command, null);
118
+
119
+ expect(commands.length).toBe(1);
120
+ expect(commands[0].certificate).toBeNull();
121
+ expect(commands[0].certificateMap).toBe('cm');
122
+ });
123
+
124
+ it('deserializes cert-only listener and infers certificateSource as certificate', () => {
125
+ const loadBalancer = {
126
+ defaultService: {
127
+ name: 'default-service',
128
+ healthCheck: { name: 'hc-1' },
129
+ sessionAffinity: 'NONE',
130
+ },
131
+ hostRules: [],
132
+ listeners: [
133
+ {
134
+ name: 'app-main',
135
+ certificate: 'legacy-cert',
136
+ certificateMap: null,
137
+ },
138
+ ],
139
+ network: 'default',
140
+ region: 'global',
141
+ urlMapName: 'app',
142
+ account: 'test-account',
143
+ };
144
+
145
+ const commandLoadBalancer = gceHttpLoadBalancerTransformer.deserialize(loadBalancer);
146
+
147
+ expect(commandLoadBalancer.listeners.length).toBe(1);
148
+ expect(commandLoadBalancer.listeners[0].certificateSource).toBe('certificate');
149
+ expect(commandLoadBalancer.listeners[0].certificate).toBe('legacy-cert');
150
+ expect(commandLoadBalancer.listeners[0].certificateMap).toBeNull();
151
+ });
152
+
153
+ it('deserializes listener with neither cert nor map and defaults to certificate source', () => {
154
+ const loadBalancer = {
155
+ defaultService: {
156
+ name: 'default-service',
157
+ healthCheck: { name: 'hc-1' },
158
+ sessionAffinity: 'NONE',
159
+ },
160
+ hostRules: [],
161
+ listeners: [
162
+ {
163
+ name: 'app-main',
164
+ certificate: null,
165
+ certificateMap: null,
166
+ },
167
+ ],
168
+ network: 'default',
169
+ region: 'global',
170
+ urlMapName: 'app',
171
+ account: 'test-account',
172
+ };
173
+
174
+ const commandLoadBalancer = gceHttpLoadBalancerTransformer.deserialize(loadBalancer);
175
+
176
+ expect(commandLoadBalancer.listeners.length).toBe(1);
177
+ // With neither field set, certificateSource defaults to 'certificate' (HTTP mode).
178
+ expect(commandLoadBalancer.listeners[0].certificateSource).toBe('certificate');
179
+ expect(commandLoadBalancer.listeners[0].certificate).toBeNull();
180
+ expect(commandLoadBalancer.listeners[0].certificateMap).toBeNull();
181
+ });
182
+ });
@@ -91,6 +91,7 @@ class InternalLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements ICon
91
91
  'loadBalancer',
92
92
  'gceCommonLoadBalancerCommandBuilder',
93
93
  'isNew',
94
+ 'forPipelineConfig',
94
95
  'wizardSubFormValidation',
95
96
  'gceXpnNamingService',
96
97
  '$state',
@@ -102,6 +103,7 @@ class InternalLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements ICon
102
103
  private loadBalancer: InternalLoadBalancer,
103
104
  private gceCommonLoadBalancerCommandBuilder: GceCommonLoadBalancerCommandBuilder,
104
105
  private isNew: boolean,
106
+ private forPipelineConfig: boolean,
105
107
  private wizardSubFormValidation: any,
106
108
  private gceXpnNamingService: any,
107
109
  $state: StateService,
@@ -244,6 +246,12 @@ class InternalLoadBalancerCtrl extends CommonGceLoadBalancerCtrl implements ICon
244
246
  toSubmitLoadBalancer.backendService.name = toSubmitLoadBalancer.loadBalancerName;
245
247
  delete toSubmitLoadBalancer.instances;
246
248
 
249
+ if (this.forPipelineConfig) {
250
+ Object.assign(toSubmitLoadBalancer, { healthCheck: {} });
251
+ this.$uibModalInstance.close(toSubmitLoadBalancer);
252
+ return;
253
+ }
254
+
247
255
  this.taskMonitor.submit(() =>
248
256
  LoadBalancerWriter.upsertLoadBalancer(toSubmitLoadBalancer, this.application, descriptor, {
249
257
  healthCheck: {},
@@ -0,0 +1,89 @@
1
+ import { ApplicationModelBuilder } from '@spinnaker/core';
2
+ import { GOOGLE_LOADBALANCER_CONFIGURE_INTERNAL_HTTP_CREATEHTTPLOADBALANCER_CONTROLLER } from './createInternalHttpLoadBalancer.controller';
3
+
4
+ describe('Controller: gceCreateInternalHttpLoadBalancerCtrl (pipeline mode)', function () {
5
+ beforeEach(function () {
6
+ this.serializedCommands = [
7
+ {
8
+ name: 'app-internal-http',
9
+ portRange: '443',
10
+ certificate: null,
11
+ certificateMap: null,
12
+ ipAddress: '10.0.0.1',
13
+ subnet: 'subnet-1',
14
+ urlMapName: 'app-internal-http',
15
+ credentials: 'test',
16
+ region: 'us-west-2',
17
+ loadBalancerType: 'INTERNAL_MANAGED',
18
+ },
19
+ ];
20
+ this.transformer = {
21
+ serialize: jasmine.createSpy('serialize').and.returnValue(this.serializedCommands),
22
+ };
23
+ this.writer = {
24
+ upsertLoadBalancers: jasmine.createSpy('upsertLoadBalancers'),
25
+ };
26
+ });
27
+
28
+ beforeEach(function () {
29
+ window.module(GOOGLE_LOADBALANCER_CONFIGURE_INTERNAL_HTTP_CREATEHTTPLOADBALANCER_CONTROLLER, ($provide) => {
30
+ $provide.value('gceHttpLoadBalancerCommandBuilder', {});
31
+ $provide.value('gceHttpLoadBalancerTransformer', this.transformer);
32
+ $provide.value('gceHttpLoadBalancerWriter', this.writer);
33
+ });
34
+ });
35
+
36
+ beforeEach(
37
+ window.inject(function ($controller, $rootScope) {
38
+ this.$scope = $rootScope.$new();
39
+ this.modalInstance = {
40
+ close: jasmine.createSpy('close'),
41
+ dismiss: jasmine.createSpy('dismiss'),
42
+ result: { then: () => {} },
43
+ };
44
+ this.wizardSubFormValidation = {
45
+ config: () => this.wizardSubFormValidation,
46
+ register: () => this.wizardSubFormValidation,
47
+ };
48
+ const app = ApplicationModelBuilder.createApplicationForTests('app', {
49
+ key: 'loadBalancers',
50
+ lazy: true,
51
+ defaultData: [],
52
+ });
53
+ this.ctrl = $controller('gceCreateInternalHttpLoadBalancerCtrl', {
54
+ $scope: this.$scope,
55
+ application: app,
56
+ $uibModalInstance: this.modalInstance,
57
+ loadBalancer: {},
58
+ gceHttpLoadBalancerCommandBuilder: {},
59
+ isNew: true,
60
+ forPipelineConfig: true,
61
+ wizardSubFormValidation: this.wizardSubFormValidation,
62
+ gceHttpLoadBalancerTransformer: this.transformer,
63
+ gceHttpLoadBalancerWriter: this.writer,
64
+ $state: {},
65
+ });
66
+ this.ctrl.command = {};
67
+ }),
68
+ );
69
+
70
+ it('returns pipeline commands without submitting', function () {
71
+ this.ctrl.submit();
72
+
73
+ expect(this.writer.upsertLoadBalancers).not.toHaveBeenCalled();
74
+ expect(this.modalInstance.close).toHaveBeenCalledWith([
75
+ jasmine.objectContaining({
76
+ name: 'app-internal-http',
77
+ loadBalancerName: 'app-internal-http',
78
+ cloudProvider: 'gce',
79
+ listeners: [
80
+ jasmine.objectContaining({
81
+ name: 'app-internal-http',
82
+ port: '443',
83
+ subnet: 'subnet-1',
84
+ }),
85
+ ],
86
+ }),
87
+ ]);
88
+ });
89
+ });
@@ -54,6 +54,7 @@ class CreateInternalHttpLoadBalancerController implements ng.IComponentControlle
54
54
  'loadBalancer',
55
55
  'gceHttpLoadBalancerCommandBuilder',
56
56
  'isNew',
57
+ 'forPipelineConfig',
57
58
  'wizardSubFormValidation',
58
59
  'gceHttpLoadBalancerTransformer',
59
60
  'gceHttpLoadBalancerWriter',
@@ -67,6 +68,7 @@ class CreateInternalHttpLoadBalancerController implements ng.IComponentControlle
67
68
  private loadBalancer: IGceHttpLoadBalancer,
68
69
  private gceHttpLoadBalancerCommandBuilder: any,
69
70
  private isNew: boolean,
71
+ private forPipelineConfig: boolean,
70
72
  private wizardSubFormValidation: any,
71
73
  private gceHttpLoadBalancerTransformer: any,
72
74
  private gceHttpLoadBalancerWriter: any,
@@ -130,6 +132,27 @@ class CreateInternalHttpLoadBalancerController implements ng.IComponentControlle
130
132
  public submit(): void {
131
133
  const serializedCommands = this.gceHttpLoadBalancerTransformer.serialize(this.command, this.loadBalancer);
132
134
  const descriptor = this.isNew ? 'Create' : 'Update';
135
+
136
+ if (this.forPipelineConfig) {
137
+ const pipelineCommands = serializedCommands.map((command: any) => ({
138
+ ...command,
139
+ cloudProvider: 'gce',
140
+ loadBalancerName: command.name,
141
+ listeners: [
142
+ {
143
+ name: command.name,
144
+ port: command.portRange,
145
+ certificate: command.certificate || null,
146
+ certificateMap: command.certificateMap || null,
147
+ ipAddress: command.ipAddress,
148
+ subnet: command.subnet,
149
+ },
150
+ ],
151
+ }));
152
+ this.$uibModalInstance.close(pipelineCommands);
153
+ return;
154
+ }
155
+
133
156
  this.taskMonitor.submit(() =>
134
157
  this.gceHttpLoadBalancerWriter.upsertLoadBalancers(serializedCommands, this.application, descriptor),
135
158
  );