@sap/cds-dk 7.8.2 → 7.9.1
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/bin/add.js +28 -7
- package/bin/bind.js +64 -21
- package/bin/build.js +35 -3
- package/bin/cds.js +31 -13
- package/bin/compile/index.js +44 -3
- package/bin/completion/completionFs.js +61 -0
- package/bin/completion/index.js +212 -0
- package/bin/completion/scripts/cds-bash.sh +17 -0
- package/bin/completion/scripts/cds-ps.ps1 +18 -0
- package/bin/completion/scripts/cds-zsh.sh +12 -0
- package/bin/help.js +8 -2
- package/bin/init.js +34 -4
- package/bin/subscribe.js +5 -1
- package/bin/watch.js +4 -2
- package/lib/bind/KubernetesApiAccessor.js +3 -2
- package/lib/bind/bindingResolver.js +3 -2
- package/lib/bind/bindingUtil.js +10 -8
- package/lib/bind/cfKeyProvider.js +58 -153
- package/lib/bind/index.js +46 -36
- package/lib/bind/kubernetesCredentialsProvider.js +6 -5
- package/lib/bind/userCdsrcJsonStorageProvider.js +25 -3
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/provider/hana/index.js +1 -1
- package/lib/build/provider/hana/template/undeploy.json +3 -1
- package/lib/build/provider/nodejs/index.js +1 -1
- package/lib/compile/openapi/index.js +5 -0
- package/lib/deploy/to-hana/hana.js +25 -7
- package/lib/env/schemas/cds-rc.js +29 -2
- package/lib/import/odata/v4parser.js +78 -25
- package/lib/init/constants.js +2 -1
- package/lib/init/index.js +18 -13
- package/lib/init/merge.js +1 -0
- package/lib/init/mvn.js +8 -47
- package/lib/init/plugin.js +0 -4
- package/lib/init/projectReader.js +17 -10
- package/lib/init/template/approuter/files/values.yaml.hbs +0 -12
- package/lib/init/template/completion/completionSetup.js +184 -0
- package/lib/init/template/completion/index.js +22 -0
- package/lib/init/template/data/as-csv.js +25 -17
- package/lib/init/template/data/as-json.js +115 -30
- package/lib/init/template/data/index.js +28 -15
- package/lib/init/template/hana/files/undeploy.json +2 -1
- package/lib/init/template/helm/files/chart/values.schema.json +1 -1
- package/lib/init/template/helm/files/content-deployment/Chart.yaml +0 -2
- package/lib/init/template/helm/files/content-deployment/values.schema.json +0 -1
- package/lib/init/template/helm/files/service-instance/Chart.yaml +0 -2
- package/lib/init/template/helm/files/service-instance/values.schema.json +0 -1
- package/lib/init/template/helm/files/web-application/Chart.yaml +0 -2
- package/lib/init/template/helm/files/web-application/values.schema.json +0 -1
- package/lib/init/template/helm-unified-runtime/files/values.schema.json +1 -1
- package/lib/init/template/http/index.js +150 -104
- package/lib/init/template/kafka/index.js +6 -0
- package/lib/init/template/lint/index.js +12 -13
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
- package/lib/init/template/multitenancy/files/values.yaml.hbs +0 -5
- package/lib/init/template/multitenancy/index.js +13 -5
- package/lib/init/template/nodejs/files/.vscode/settings.json +11 -0
- package/lib/init/template/nodejs/files/eslint.config.js +13 -0
- package/lib/init/template/nodejs/files/package.json.hbs +3 -1
- package/lib/init/template/sample/index.js +18 -20
- package/lib/init/template/tiny-sample/index.js +1 -2
- package/lib/init/template/typer/index.js +2 -2
- package/lib/lint/Linter.js +133 -55
- package/lib/lint/checks.js +5 -6
- package/lib/lint/io.js +119 -10
- package/lib/mtx/auth_manager.js +5 -5
- package/lib/mtx/params.js +4 -2
- package/lib/mtx/settings_manager.js +1 -1
- package/lib/mtx/tenant_action.js +12 -2
- package/lib/mtx/util/logging.js +10 -4
- package/lib/util/axios.js +2 -1
- package/lib/util/cf.js +32 -37
- package/lib/util/git.js +1 -1
- package/lib/util/term.js +1 -1
- package/{bin → lib/watch}/watched.js +2 -2
- package/npm-shrinkwrap.json +34 -34
- package/package.json +2 -2
- package/lib/init/template/nodejs/files/.eslintrc +0 -26
|
@@ -2,109 +2,102 @@ const cds = require('../cds');
|
|
|
2
2
|
const DEBUG = cds.debug('cli')
|
|
3
3
|
|
|
4
4
|
const cfUtil = require('../util/cf');
|
|
5
|
-
|
|
6
|
-
const
|
|
7
|
-
require('../util/axios').pruneErrors();
|
|
8
|
-
const { URL } = require('url');
|
|
9
|
-
const { bold, highlight } = require('../util/term');
|
|
5
|
+
const { bold } = require('../util/term');
|
|
6
|
+
const { mergeCredentials } = require('./index');
|
|
10
7
|
|
|
11
8
|
module.exports = new class CFKeyProvider {
|
|
12
|
-
async
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const target = await cfUtil.getCfTarget();
|
|
16
|
-
const authorization = await cfUtil.getCfAuthorization();
|
|
17
|
-
const targetWithAuth = Object.assign({}, target, { authorization });
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { org, space } = target;
|
|
21
|
-
const orgs = await BatchRequest.req(targetWithAuth, `/v3/organizations?names=${encodeURIComponent(encodeURIComponent(org))}`);
|
|
22
|
-
if (!(orgs && orgs.resources.length === 1)) {
|
|
23
|
-
throw `CF org ${org} not found!`;
|
|
9
|
+
async init() {
|
|
10
|
+
if (!this.target) {
|
|
11
|
+
this.target = await cfUtil.getCfSpaceInfo();
|
|
24
12
|
}
|
|
13
|
+
}
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
15
|
+
async getOrg(orgName) {
|
|
16
|
+
const result = await cfUtil.cfRequest('/v3/organizations', { names: orgName });
|
|
17
|
+
return result?.resources?.[0];
|
|
18
|
+
}
|
|
31
19
|
|
|
32
|
-
|
|
20
|
+
async getSpace(orgGuid, spaceName) {
|
|
21
|
+
const result = await cfUtil.cfRequest('/v3/spaces', { names: spaceName, organization_guids: orgGuid });
|
|
22
|
+
return result?.resources?.find(r => r.relationships.organization.data.guid === orgGuid);
|
|
23
|
+
}
|
|
33
24
|
|
|
34
|
-
|
|
25
|
+
async getInstance(spaceGuid, instanceName) {
|
|
26
|
+
const result = await cfUtil.cfRequest('/v3/service_instances', { names: instanceName, space_guids: spaceGuid });
|
|
27
|
+
return result?.resources?.find(r => !!r.relationships.space.data.guid);
|
|
35
28
|
}
|
|
36
29
|
|
|
37
|
-
async
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
this.getInstance = BatchRequest.create({
|
|
46
|
-
target, url: '/v3/service_instances', extractors: {
|
|
47
|
-
space_guids: resource => resource.relationships.space.data.guid
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
this.getKey = BatchRequest.create({
|
|
51
|
-
target, url: '/v3/service_credential_bindings', extractors: {
|
|
52
|
-
service_instance_guids: resource => resource.relationships.service_instance.data.guid
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
this.getPlans = BatchRequest.create({ target, url: '/v3/service_plans' });
|
|
56
|
-
this.getOfferings = BatchRequest.create({ target, url: '/v3/service_offerings' });
|
|
30
|
+
async getPlan(planGuid) {
|
|
31
|
+
const result = await cfUtil.cfRequest('/v3/service_plans', { guids: planGuid });
|
|
32
|
+
return result?.resources?.[0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getOffering(offeringGuid) {
|
|
36
|
+
const result = await cfUtil.cfRequest('/v3/service_offerings', { guids: offeringGuid });
|
|
37
|
+
return result?.resources?.[0];
|
|
57
38
|
}
|
|
58
39
|
|
|
59
40
|
async resolve(name, binding) {
|
|
60
|
-
|
|
61
|
-
this.initPromise = this.init();
|
|
62
|
-
}
|
|
63
|
-
await this.initPromise;
|
|
41
|
+
await this.init();
|
|
64
42
|
this._checkApiEndpoint(name, binding)
|
|
65
43
|
|
|
66
44
|
let { instance, org, space } = binding;
|
|
67
|
-
let spaceGuid;
|
|
45
|
+
let spaceGuid, orgGuid;
|
|
68
46
|
if (org && space) {
|
|
69
47
|
// org and space can be overwritten by currently logged in org and space
|
|
70
|
-
const orgObj = await this.getOrg(
|
|
71
|
-
const spaceObj = await this.getSpace(
|
|
48
|
+
const orgObj = await this.getOrg(org);
|
|
49
|
+
const spaceObj = await this.getSpace(orgObj.guid, space);
|
|
72
50
|
spaceGuid = spaceObj.guid;
|
|
51
|
+
orgGuid = orgObj.guid;
|
|
73
52
|
} else {
|
|
74
53
|
org = this.target.org;
|
|
75
54
|
space = this.target.space;
|
|
76
55
|
spaceGuid = this.target.spaceGuid;
|
|
56
|
+
orgGuid = this.target.orgGuid;
|
|
77
57
|
}
|
|
78
58
|
|
|
79
59
|
let instanceObj;
|
|
80
60
|
try {
|
|
81
|
-
instanceObj = await this.getInstance(
|
|
61
|
+
instanceObj = await this.getInstance(spaceGuid, instance);
|
|
62
|
+
const instanceSpaceGuid = instanceObj.relationships.space.data.guid;
|
|
63
|
+
if (instanceSpaceGuid !== spaceGuid) {
|
|
64
|
+
// service instance is shared from another space, service key must be created in that space
|
|
65
|
+
const sharedFromSpaceObj = await cfUtil.cfRequest('/v3/spaces/' + encodeURIComponent(instanceSpaceGuid));
|
|
66
|
+
space = sharedFromSpaceObj.name;
|
|
67
|
+
spaceGuid = sharedFromSpaceObj.guid;
|
|
68
|
+
const instanceOrgGuid = sharedFromSpaceObj.relationships.organization.data.guid;
|
|
69
|
+
if (instanceOrgGuid !== orgGuid) {
|
|
70
|
+
const sharedFromOrgObj = await cfUtil.cfRequest('/v3/organizations/' + encodeURIComponent(instanceOrgGuid));
|
|
71
|
+
org = sharedFromOrgObj.name;
|
|
72
|
+
orgGuid = sharedFromOrgObj.guid;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
82
75
|
} catch (error) {
|
|
83
|
-
throw `Service instance
|
|
76
|
+
throw `Service instance ${bold(instance)} not found.`;
|
|
84
77
|
}
|
|
85
78
|
switch (instanceObj.type) {
|
|
86
79
|
case 'managed':
|
|
87
|
-
return this._resolveManagedService(instanceObj, instance, binding.key || `${instance}-key`, org, space);
|
|
80
|
+
return this._resolveManagedService(instanceObj, instance, binding.key || `${instance}-key`, org, space, binding.credentials);
|
|
88
81
|
case 'user-provided':
|
|
89
|
-
return this._resolveUserProvidedService(instanceObj, instance, org, space);
|
|
82
|
+
return this._resolveUserProvidedService(instanceObj, instance, org, space, binding.credentials);
|
|
90
83
|
default:
|
|
91
|
-
throw `Service type ${instanceObj.type} not supported`;
|
|
84
|
+
throw `Service type ${bold(instanceObj.type)} not supported`;
|
|
92
85
|
}
|
|
93
86
|
}
|
|
94
87
|
|
|
95
|
-
async _resolveManagedService(instanceObj, instance, key, org, space) {
|
|
88
|
+
async _resolveManagedService(instanceObj, instance, key, org, space, customCredentials) {
|
|
96
89
|
const create = (cds.cli.command === 'bind' || cds.cli.command === 'deploy') && !cds.cli.options['no-create-service-key']
|
|
97
90
|
const silent = (cds.cli.options.json || cds.cli.command === 'env') && !DEBUG
|
|
98
91
|
const credentials = await cfUtil.getOrCreateServiceKey(instanceObj, key, {}, { create, silent });
|
|
99
92
|
|
|
100
93
|
if (!credentials) {
|
|
101
|
-
let message = `No service key ${
|
|
94
|
+
let message = `No service key ${bold(key)} found for service instance ${bold(instance)}.\n\n`;
|
|
102
95
|
message += `Use ${bold(`cf create-service-key ${instanceObj.name} ${key} [-c ...]`)} to create the required service key.`;
|
|
103
96
|
throw message;
|
|
104
97
|
}
|
|
105
98
|
|
|
106
|
-
const planObj = await this.
|
|
107
|
-
const offeringObj = await this.
|
|
99
|
+
const planObj = await this.getPlan(instanceObj.relationships.service_plan.data.guid);
|
|
100
|
+
const offeringObj = await this.getOffering(planObj.relationships.service_offering.data.guid);
|
|
108
101
|
const resolvedBinding = {
|
|
109
102
|
binding: {
|
|
110
103
|
type: "cf",
|
|
@@ -120,14 +113,14 @@ module.exports = new class CFKeyProvider {
|
|
|
120
113
|
},
|
|
121
114
|
resolved: true,
|
|
122
115
|
},
|
|
123
|
-
credentials
|
|
116
|
+
credentials: mergeCredentials(credentials, customCredentials)
|
|
124
117
|
};
|
|
125
118
|
|
|
126
119
|
return resolvedBinding;
|
|
127
120
|
}
|
|
128
121
|
|
|
129
|
-
async _resolveUserProvidedService(instanceObj, instance, org, space) {
|
|
130
|
-
const
|
|
122
|
+
async _resolveUserProvidedService(instanceObj, instance, org, space, customCredentials) {
|
|
123
|
+
const credentials = await cfUtil.cfRequest(`/v3/service_instances/${encodeURIComponent(instanceObj.guid)}/credentials`);
|
|
131
124
|
const resolvedBinding = {
|
|
132
125
|
binding: {
|
|
133
126
|
type: "cf",
|
|
@@ -141,7 +134,7 @@ module.exports = new class CFKeyProvider {
|
|
|
141
134
|
},
|
|
142
135
|
resolved: true,
|
|
143
136
|
},
|
|
144
|
-
credentials:
|
|
137
|
+
credentials: mergeCredentials(credentials, customCredentials)
|
|
145
138
|
};
|
|
146
139
|
return resolvedBinding;
|
|
147
140
|
}
|
|
@@ -152,97 +145,9 @@ module.exports = new class CFKeyProvider {
|
|
|
152
145
|
// if no api endpoint is configured the currently logged in endpoint will be used
|
|
153
146
|
if (apiEndpoint && apiEndpoint !== this.target.apiEndpoint) {
|
|
154
147
|
name = `${name || instance}:${key || instance + '-key'}`;
|
|
155
|
-
let message = `Current Cloud Foundry API endpoint ${
|
|
148
|
+
let message = `Current Cloud Foundry API endpoint ${bold(this.target.apiEndpoint)} differs from API endpoint ${bold(apiEndpoint)} for service binding ${bold(name)}.\n\n`;
|
|
156
149
|
message += `Use ${bold(`cf login -a ${apiEndpoint}`)} to log in to the target API endpoint.`;
|
|
157
150
|
throw message;
|
|
158
151
|
}
|
|
159
152
|
}
|
|
160
153
|
}
|
|
161
|
-
|
|
162
|
-
class BatchRequest {
|
|
163
|
-
constructor({ target, url, extractors = {} }) {
|
|
164
|
-
this.target = target;
|
|
165
|
-
this.url = url;
|
|
166
|
-
this.filters = [];
|
|
167
|
-
this.timeout = undefined;
|
|
168
|
-
this.extractors = Object.assign({ names: resource => resource.name, guids: resource => resource.guid }, extractors);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
static create(options) {
|
|
172
|
-
const obj = new BatchRequest(options);
|
|
173
|
-
return (...args) => obj.request(...args);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
async request(filter) {
|
|
177
|
-
let resolve;
|
|
178
|
-
let reject;
|
|
179
|
-
const promise = new Promise((_resolve, _reject) => {
|
|
180
|
-
resolve = _resolve;
|
|
181
|
-
reject = _reject;
|
|
182
|
-
});
|
|
183
|
-
this.filters.push({ filter, resolve, reject });
|
|
184
|
-
|
|
185
|
-
if (this.timeout) clearTimeout(this.timeout);
|
|
186
|
-
this.timeout = setTimeout(() => this.executeRequest(), 0);
|
|
187
|
-
|
|
188
|
-
return promise;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async executeRequest() {
|
|
192
|
-
const filters = this.filters;
|
|
193
|
-
this.filters = [];
|
|
194
|
-
const filterParams = {};
|
|
195
|
-
for (const filter of filters) {
|
|
196
|
-
for (const name of Object.keys(filter.filter)) {
|
|
197
|
-
if (!filterParams[name]) filterParams[name] = [];
|
|
198
|
-
filterParams[name].push(filter.filter[name]);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const params = Object.keys(filterParams).map(
|
|
203
|
-
name => `${name}=${encodeURIComponent(filterParams[name].map(encodeURIComponent).join(','))}`
|
|
204
|
-
).join("&");
|
|
205
|
-
|
|
206
|
-
const url = this.url + (this.url.match(/\?/) ? '&' : '?') + params;
|
|
207
|
-
|
|
208
|
-
let data;
|
|
209
|
-
try {
|
|
210
|
-
data = await this.httpRequest(url);
|
|
211
|
-
} catch (error) {
|
|
212
|
-
for (const filter of filters) {
|
|
213
|
-
filter.reject(error);
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const fmt = filter => Object.keys(filter).map(key => `${key}: ${filter[key]}`).join(', ');
|
|
218
|
-
const resources = data.resources;
|
|
219
|
-
for (const filter of filters) {
|
|
220
|
-
let filteredResources = resources;
|
|
221
|
-
for (const name of Object.keys(filter.filter)) {
|
|
222
|
-
filteredResources = filteredResources.filter(resource => this.extractors[name](resource) === filter.filter[name]);
|
|
223
|
-
}
|
|
224
|
-
if (filteredResources.length === 1) {
|
|
225
|
-
filter.resolve(filteredResources[0]);
|
|
226
|
-
} else if (filteredResources.length === 0) {
|
|
227
|
-
filter.reject(`Couldn't find resource for url "${this.url}" with filter "${fmt(filter.filter)}"`);
|
|
228
|
-
} else {
|
|
229
|
-
filter.reject(`Found more than one resources for url "${this.url}" with filter "${fmt(filter.filter)}"`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async httpRequest(urlPath) {
|
|
235
|
-
return BatchRequest.req(this.target, urlPath);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
static async req(target, urlPath) {
|
|
239
|
-
const url = new URL(urlPath, target.apiEndpoint);
|
|
240
|
-
DEBUG?.(`_req: ${url.toString()}`);
|
|
241
|
-
const result = await axios.get(url.toString(), {
|
|
242
|
-
headers: {
|
|
243
|
-
authorization: target.authorization
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
return result.data;
|
|
247
|
-
}
|
|
248
|
-
}
|
package/lib/bind/index.js
CHANGED
|
@@ -2,10 +2,10 @@ const cds = require('../cds');
|
|
|
2
2
|
|
|
3
3
|
const BindingResolver = require('./bindingResolver');
|
|
4
4
|
const { determineKinds, getRequiresNameFromKind, checkIsJavaProject, platformText, bindingText, logger } = require('./bindingUtil');
|
|
5
|
-
const {
|
|
5
|
+
const { bold } = require('../util/term');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
module.exports = { bind, parseProfile }
|
|
8
|
+
module.exports = { bind, storeBindings, parseProfile, mergeCredentials }
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Executes cds build in the directory defined by cds.root.
|
|
@@ -13,11 +13,8 @@ module.exports = { bind, parseProfile }
|
|
|
13
13
|
* @param {object} options - command options as defined by build command.
|
|
14
14
|
*/
|
|
15
15
|
async function bind(options) {
|
|
16
|
-
|
|
17
|
-
options.for = options.for || 'hybrid';
|
|
18
|
-
|
|
19
16
|
let on;
|
|
20
|
-
switch(options.on) {
|
|
17
|
+
switch (options.on) {
|
|
21
18
|
case 'k8s':
|
|
22
19
|
case 'kubernetes':
|
|
23
20
|
on = 'k8s';
|
|
@@ -33,36 +30,26 @@ async function bind(options) {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
const onText = { 'cf': 'Cloud Foundry', 'k8s': 'Kubernetes' }[on];
|
|
36
|
-
if (!options['to-app-services']) logger.log(`Retrieving data from ${onText}...`);
|
|
33
|
+
if (!options['to-app-services']) logger.log(`Retrieving data from ${bold(onText)}...`);
|
|
37
34
|
let resolvedServices
|
|
38
35
|
|
|
39
36
|
const targets = options.to.split(/,/g);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
throw new Error('Internal Error');
|
|
50
|
-
}
|
|
51
|
-
const resolvedService = await BindingResolver.resolveBinding(undefined, binding);
|
|
52
|
-
resolvedService._target = target;
|
|
53
|
-
delete resolvedService.credentials;
|
|
54
|
-
resolvedService.binding.resolved = false;
|
|
55
|
-
return resolvedService;
|
|
56
|
-
}));
|
|
57
|
-
} catch (error) {
|
|
58
|
-
if (error.name === 'AxiosError') {
|
|
59
|
-
const errors = error.response?.data?.errors ?? []
|
|
60
|
-
const responseInfo = errors.map(e => `${e.title}: ${e.detail} (${e.code})`).join(' \n')
|
|
61
|
-
const requestInfo = error.request?.path
|
|
62
|
-
throw new Error(`${error.toString()}\nResponse: ${responseInfo}\nRequest:${requestInfo}}`)
|
|
37
|
+
resolvedServices = await Promise.all(targets.map(async target => {
|
|
38
|
+
let binding;
|
|
39
|
+
if (on === 'k8s') {
|
|
40
|
+
binding = { type: on, name: target };
|
|
41
|
+
} else if (on === 'cf') {
|
|
42
|
+
const [instance, key] = target.split(/:/);
|
|
43
|
+
binding = { type: on, instance, key };
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error('Internal Error');
|
|
63
46
|
}
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
const resolvedService = await BindingResolver.resolveBinding(undefined, binding);
|
|
48
|
+
resolvedService._target = target;
|
|
49
|
+
delete resolvedService.credentials;
|
|
50
|
+
resolvedService.binding.resolved = false;
|
|
51
|
+
return resolvedService;
|
|
52
|
+
}));
|
|
66
53
|
|
|
67
54
|
if (options.kind) {
|
|
68
55
|
resolvedServices.forEach(service => service.kind = options.kind);
|
|
@@ -80,12 +67,16 @@ async function bind(options) {
|
|
|
80
67
|
let { kind, name, binding } = resolvedService;
|
|
81
68
|
name = name ?? `custom-service:${binding.instance || binding.secret}`
|
|
82
69
|
resolvedService.name = resolvedService.name ?? name
|
|
83
|
-
const kindText = kind ? ` with kind ${
|
|
84
|
-
const nameText =
|
|
70
|
+
const kindText = kind ? ` with kind ${bold(kind)}` : ''
|
|
71
|
+
const nameText = bold(name)
|
|
85
72
|
|
|
86
|
-
logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding
|
|
73
|
+
logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding)}${kindText}`);
|
|
87
74
|
}
|
|
88
75
|
|
|
76
|
+
return resolvedServices;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function storeBindings(resolvedServices, options) {
|
|
89
80
|
// prepare for different providers
|
|
90
81
|
const userCdsrcJsonStorageProvider = require('./userCdsrcJsonStorageProvider');
|
|
91
82
|
|
|
@@ -95,7 +86,7 @@ async function bind(options) {
|
|
|
95
86
|
|
|
96
87
|
const isJavaProject = await checkIsJavaProject(cds.root);
|
|
97
88
|
if (!cds.cli.options['to-app-services']) {
|
|
98
|
-
logger.log(
|
|
89
|
+
logger.log(`\nTo run with cloud bindings use ${bold(runCmd(options.for, isJavaProject))}`);
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
|
|
@@ -115,3 +106,22 @@ function parseProfile(profile) {
|
|
|
115
106
|
else if (profile === '-') return 'development';
|
|
116
107
|
else return profile;
|
|
117
108
|
}
|
|
109
|
+
|
|
110
|
+
function mergeCredentials(credentials, customCredentials) {
|
|
111
|
+
if (!customCredentials) return credentials;
|
|
112
|
+
|
|
113
|
+
for (const key in customCredentials) {
|
|
114
|
+
if (typeof customCredentials[key] === 'object') {
|
|
115
|
+
if (credentials[key] && typeof credentials[key] !== 'object') {
|
|
116
|
+
throw `Cannot merge credentials: '${key}' is not an object`;
|
|
117
|
+
}
|
|
118
|
+
if (!credentials[key]) {
|
|
119
|
+
credentials[key] = {};
|
|
120
|
+
}
|
|
121
|
+
credentials[key] = this._mergeCredentials(credentials[key], customCredentials[key]);
|
|
122
|
+
} else {
|
|
123
|
+
credentials[key] = customCredentials[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return credentials;
|
|
127
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const KubernetesApiAccessor = require('./KubernetesApiAccessor');
|
|
2
|
+
const { mergeCredentials } = require('./index');
|
|
2
3
|
|
|
3
4
|
class KubernetesCredentialsProvider {
|
|
4
5
|
async init() {
|
|
@@ -12,14 +13,14 @@ class KubernetesCredentialsProvider {
|
|
|
12
13
|
await this.initPromise;
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
const {context, namespace, cluster} = await this.api.context({cluster: binding.cluster, namespace: binding.namespace});
|
|
16
|
-
let {name: bindingName, secret} = binding;
|
|
16
|
+
const { context, namespace, cluster } = await this.api.context({ cluster: binding.cluster, namespace: binding.namespace });
|
|
17
|
+
let { name: bindingName, secret, credentials: customCredentials } = binding;
|
|
17
18
|
|
|
18
19
|
let instance;
|
|
19
20
|
let secretOrBindingResource = 'Secret';
|
|
20
21
|
|
|
21
22
|
if (bindingName) {
|
|
22
|
-
const bindingResource = await this.api.get({context, namespace, kind: 'servicebindings', name: bindingName});
|
|
23
|
+
const bindingResource = await this.api.get({ context, namespace, kind: 'servicebindings', name: bindingName });
|
|
23
24
|
if (bindingResource === null) {
|
|
24
25
|
// Fallback to secret instead of service binding;
|
|
25
26
|
secret = bindingName;
|
|
@@ -35,7 +36,7 @@ class KubernetesCredentialsProvider {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
const secretResource = await this.api.get({context, namespace, kind: 'secrets', name: secret});
|
|
39
|
+
const secretResource = await this.api.get({ context, namespace, kind: 'secrets', name: secret });
|
|
39
40
|
if (secretResource === null) throw new Error(`${secretOrBindingResource} '${secret}' not found.`);
|
|
40
41
|
|
|
41
42
|
const serviceBinding = this._readBinding(name, secretResource);
|
|
@@ -53,7 +54,7 @@ class KubernetesCredentialsProvider {
|
|
|
53
54
|
resolved: true,
|
|
54
55
|
vcap
|
|
55
56
|
},
|
|
56
|
-
credentials
|
|
57
|
+
credentials: mergeCredentials(credentials, customCredentials)
|
|
57
58
|
};
|
|
58
59
|
|
|
59
60
|
return resolvedBinding;
|
|
@@ -3,7 +3,7 @@ const path = require('path');
|
|
|
3
3
|
const readline = require('readline');
|
|
4
4
|
|
|
5
5
|
const fs = require('fs').promises;
|
|
6
|
-
const {
|
|
6
|
+
const { bold } = require('../util/term');
|
|
7
7
|
const { logger } = require('./bindingUtil');
|
|
8
8
|
|
|
9
9
|
const gitUtil = require('../util/git');
|
|
@@ -38,7 +38,6 @@ class UserCdsrcJsonStorageProvider {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
jsonPath = jsonPath.replace(/~/g, os.homedir());
|
|
41
|
-
// const stat = await .stat(jsonPath);
|
|
42
41
|
if (path.extname(jsonPath) !== '.json') {
|
|
43
42
|
jsonPath = path.join(jsonPath, '.cdsrc.json');
|
|
44
43
|
}
|
|
@@ -63,12 +62,14 @@ class UserCdsrcJsonStorageProvider {
|
|
|
63
62
|
for (const service of services) {
|
|
64
63
|
const { name, kind, binding, credentials } = service;
|
|
65
64
|
const requireService = section[name] = section[name] || {};
|
|
65
|
+
// store optional custom credentials
|
|
66
|
+
binding.credentials = this._getServiceCredentials(binding, services, options);
|
|
66
67
|
|
|
67
68
|
Object.assign(requireService, { binding: { ...binding, vcap: undefined }, credentials, kind, vcap: { name } });
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
await this.writeJsonFile(jsonPath, jsonObject);
|
|
71
|
-
logger.log(`Saving bindings to ${
|
|
72
|
+
logger.log(`Saving bindings to ${bold(jsonPath)} in profile ${bold(options.for)}`);
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
async readJsonSafely(filePath) {
|
|
@@ -84,6 +85,27 @@ class UserCdsrcJsonStorageProvider {
|
|
|
84
85
|
await fs.writeFile(filePath, JSON.stringify(content, null, 2));
|
|
85
86
|
await fs.chmod(filePath, 0o600); // -rw-------
|
|
86
87
|
}
|
|
88
|
+
|
|
89
|
+
_getServiceCredentials(binding, services, options) {
|
|
90
|
+
const { to, 'to-app-services': app, credentials } = options;
|
|
91
|
+
if (!credentials) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
// toService has format <service-name>[:<key>]
|
|
95
|
+
const toServices = to ? to.split(',') : services.map(s => s.binding.instance);
|
|
96
|
+
const toService = toServices.find(toService => binding.type === 'cf' ?
|
|
97
|
+
toService === binding.instance || toService === binding.instance + ':' + binding.key :
|
|
98
|
+
toService === binding.secret);
|
|
99
|
+
if (!toService) {
|
|
100
|
+
// must never happen
|
|
101
|
+
throw new Error(`Internal error: Service ${binding.instance}:${binding.key} not found`);
|
|
102
|
+
}
|
|
103
|
+
// app based binding always requires service name as key in credentials
|
|
104
|
+
if (!app && toServices.length === 1) {
|
|
105
|
+
return credentials[toService] || credentials;
|
|
106
|
+
}
|
|
107
|
+
return credentials[toService];
|
|
108
|
+
}
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
module.exports = new UserCdsrcJsonStorageProvider();
|
|
@@ -227,7 +227,7 @@ class BuildTaskEngine {
|
|
|
227
227
|
files.length = LOG_LIMIT
|
|
228
228
|
files.push(`... ${length - files.length} more. Run with DEBUG=build to show all files.`)
|
|
229
229
|
}
|
|
230
|
-
console.log(`done > wrote output to:\n ${term.
|
|
230
|
+
console.log(`done > wrote output to:\n ${term.dim(files.join("\n "))}\n`)
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
|
|
@@ -398,7 +398,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
398
398
|
* Create .hdiconfig file in <dest>src/gen folder of db module.
|
|
399
399
|
*/
|
|
400
400
|
async _writeHdiConfig(compileHdiPlugins) {
|
|
401
|
-
const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint"]
|
|
401
|
+
const DEFAULT_HDI_PLUGINS = [FILE_EXT_CSV, FILE_EXT_HDBTABLEDATA, FILE_EXT_HDBTABLE, ".hdbview", ".hdbindex", ".hdbconstraint", ".hdbcalculationview"]
|
|
402
402
|
const undeployHdiPlugins = await this._readTypesFromUndeployJson() // see CAP issue #6222
|
|
403
403
|
const hdiPlugins = new Set([...DEFAULT_HDI_PLUGINS, ...undeployHdiPlugins, ...compileHdiPlugins])
|
|
404
404
|
let hdiConfigPath
|
|
@@ -92,7 +92,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
92
92
|
*/
|
|
93
93
|
async copySrvContent(src, destRoot, destSrv) {
|
|
94
94
|
const srvRootBlockList = RegExp('package\\.json$|package-lock\\.json$|\\.npmrc$|\\.cdsrc\\.json$')
|
|
95
|
-
const srvBlockList = RegExp('\\.cds$|csn\\.json$|\\.csn$|manifest\\.y.?ml$|\\.env($|\\..*$)|default-env\\.json$')
|
|
95
|
+
const srvBlockList = RegExp('\\.cds$|csn\\.json$|\\.csn$|manifest\\.y.?ml$|\\.env($|\\..*$)|default-env\\.json$|\\.cdsrc-private.json$')
|
|
96
96
|
const srvRootFileNames = []
|
|
97
97
|
|
|
98
98
|
// 1. copy all files to 'destSrv' except those contained in the blocklist (including node_modules)
|
|
@@ -58,6 +58,11 @@ function servicePath(csdl, csn) {
|
|
|
58
58
|
if (csdl.$EntityContainer) {
|
|
59
59
|
const serviceName = csdl.$EntityContainer.replace(/\.[^.]+$/, '')
|
|
60
60
|
const service = csn.definitions[serviceName]
|
|
61
|
+
|
|
62
|
+
// if @protocol is 'none' then throw an error
|
|
63
|
+
if (service['@protocol'] === 'none') {
|
|
64
|
+
throw new Error(`Service "${serviceName}" is annotated with @protocol:'none' which is not supported in openAPI generation.`)
|
|
65
|
+
}
|
|
61
66
|
return cds.service.path4?.(service) || cds.serve.path4(service)
|
|
62
67
|
}
|
|
63
68
|
}
|
|
@@ -13,9 +13,9 @@ const cfUtil = require('../../util/cf');
|
|
|
13
13
|
const gitUtil = require('../../util/git');
|
|
14
14
|
const mtaUtil = require('./mtaUtil');
|
|
15
15
|
|
|
16
|
-
const { bold, info } = require('../../util/term');
|
|
16
|
+
const { bold, info, warn } = require('../../util/term');
|
|
17
17
|
|
|
18
|
-
const { bind } = require('../../bind');
|
|
18
|
+
const { bind, storeBindings } = require('../../bind');
|
|
19
19
|
|
|
20
20
|
const DEBUG = cds.debug('deploy');
|
|
21
21
|
|
|
@@ -37,6 +37,9 @@ module.exports = new class HanaDeployer {
|
|
|
37
37
|
|
|
38
38
|
async deploy(options = {}) {
|
|
39
39
|
let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
|
|
40
|
+
|
|
41
|
+
options.for = options.for || 'hybrid';
|
|
42
|
+
|
|
40
43
|
if (model) {
|
|
41
44
|
throw `Specifying a CDS model for HANA deployment is not supported.
|
|
42
45
|
'cds deploy --to hana' performs 'cds build --for hana' which in turn uses the configured HANA build task.
|
|
@@ -51,13 +54,22 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
51
54
|
console.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
const
|
|
57
|
+
const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
|
|
58
|
+
const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
|
|
59
|
+
const vcapEnv = { ...fromEnv, ...fromFile };
|
|
60
|
+
|
|
61
|
+
const warnings = [];
|
|
62
|
+
if (serviceName && vcapEnv.VCAP_SERVICES?.hana?.[0].name !== serviceName) {
|
|
63
|
+
if (vcapFile) {
|
|
64
|
+
warnings.push(`The specified service name '${serviceName}' was used; the information coming from --vcap-service file was ignored.`);
|
|
65
|
+
} else if (Object.keys(fromEnv).length > 0) {
|
|
66
|
+
warnings.push(`The specified service name '${serviceName}' was used; the information coming from environment variable VCAP_SERVICES was ignored.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
55
69
|
|
|
70
|
+
const projectPath = cds.root;
|
|
56
71
|
const buildResults = await this._build();
|
|
57
72
|
|
|
58
|
-
const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
|
|
59
|
-
const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
|
|
60
|
-
let vcapEnv = { ...fromEnv, ...fromFile };
|
|
61
73
|
if (process.env.TARGET_CONTAINER) {
|
|
62
74
|
vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
|
|
63
75
|
}
|
|
@@ -111,7 +123,8 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
111
123
|
for: options.for,
|
|
112
124
|
kind: task.for
|
|
113
125
|
}
|
|
114
|
-
await bind(bindOptions);
|
|
126
|
+
const services = await bind(bindOptions);
|
|
127
|
+
await storeBindings(services, bindOptions);
|
|
115
128
|
}
|
|
116
129
|
}
|
|
117
130
|
|
|
@@ -124,6 +137,11 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
|
|
|
124
137
|
|
|
125
138
|
console.log(`Done.`);
|
|
126
139
|
|
|
140
|
+
if (warnings.length > 0) {
|
|
141
|
+
console.warn(warn('\nWARNING:'));
|
|
142
|
+
console.warn(warn(warnings.join('\n')));
|
|
143
|
+
}
|
|
144
|
+
|
|
127
145
|
return { buildResults };
|
|
128
146
|
}
|
|
129
147
|
|