@spinnaker/google 2026.0.1 → 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.
- package/dist/domain/loadBalancer.d.ts +4 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/loadBalancer/configure/choice/gceLoadBalancerPipelineModal.d.ts +9 -0
- package/dist/loadBalancer/configure/http/templates.d.ts +3 -0
- package/package.json +3 -3
- package/src/domain/loadBalancer.ts +4 -2
- package/src/gce.module.ts +2 -0
- package/src/help/gce.help.ts +2 -0
- package/src/loadBalancer/configure/choice/gceLoadBalancerChoice.modal.spec.js +66 -0
- package/src/loadBalancer/configure/choice/gceLoadBalancerChoice.modal.ts +20 -5
- package/src/loadBalancer/configure/choice/gceLoadBalancerPipelineModal.ts +47 -0
- package/src/loadBalancer/configure/gceL4LoadBalancerPipeline.spec.js +143 -0
- package/src/loadBalancer/configure/http/createHttpLoadBalancer.controller.js +22 -0
- package/src/loadBalancer/configure/http/createHttpLoadBalancer.controller.spec.js +96 -0
- package/src/loadBalancer/configure/http/listeners/listener.component.html +79 -24
- package/src/loadBalancer/configure/http/listeners/listener.component.js +82 -0
- package/src/loadBalancer/configure/http/listeners/listener.component.spec.js +125 -0
- package/src/loadBalancer/configure/http/templates.ts +3 -0
- package/src/loadBalancer/configure/http/transformer.service.js +16 -1
- package/src/loadBalancer/configure/http/transformer.service.spec.js +182 -0
- package/src/loadBalancer/configure/internal/gceCreateInternalLoadBalancer.controller.ts +8 -0
- package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.spec.js +89 -0
- package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.ts +23 -0
- package/src/loadBalancer/configure/network/createLoadBalancer.controller.js +48 -32
- package/src/loadBalancer/configure/network/createLoadBalancer.controller.spec.js +36 -0
- package/src/loadBalancer/configure/ssl/gceCreateSslLoadBalancer.controller.ts +8 -0
- package/src/loadBalancer/configure/tcp/gceCreateTcpLoadBalancer.controller.ts +8 -0
- package/src/loadBalancer/details/loadBalancerType/loadBalancerType.component.js +3 -1
- package/src/loadBalancer/details/loadBalancerType/loadBalancerType.component.spec.js +85 -0
- package/src/loadBalancer/loadBalancer.setTransformer.ts +1 -0
- package/src/serverGroup/configure/serverGroupConfiguration.service.js +0 -1
- 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: {},
|
package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.spec.js
ADDED
|
@@ -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
|
+
});
|
package/src/loadBalancer/configure/internalhttp/createInternalHttpLoadBalancer.controller.ts
CHANGED
|
@@ -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
|
);
|