@sap/cds-dk 4.6.4 → 4.7.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.
@@ -8,18 +8,6 @@ const { URL } = require('url');
8
8
  const execAsync = util.promisify(cp.exec);
9
9
  const IS_WIN = os.platform() === 'win32';
10
10
 
11
- const KindToRequiresNameMap = {
12
- xsuaa: 'uaa',
13
- hana: 'db',
14
- destinations: 'destinations',
15
- connectivity: 'connectivity',
16
- 'audit-log': 'audit-log',
17
- 'enterprise-messaging': 'messaging',
18
- 'enterprise-messaging-amqp': 'messaging',
19
- 'enterprise-messaging-http': 'messaging',
20
- 'enterprise-messaging-shared': 'messaging'
21
- }
22
-
23
11
  class CFKeyProvider {
24
12
 
25
13
  constructor() {
@@ -69,7 +57,6 @@ class CFKeyProvider {
69
57
 
70
58
  async getCfTargetFromCli() {
71
59
  const result = await this._cfRead('target');
72
-
73
60
  return {
74
61
  apiEndpoint: this.extract(result, /^api endpoint:\s+(.*)$/i),
75
62
  user: this.extract(result, /^user:\s+(.*)$/i),
@@ -142,7 +129,7 @@ class CFKeyProvider {
142
129
  name = name || `${instance}:${key}`;
143
130
 
144
131
  if (apiEndpoint && apiEndpoint !== target.apiEndpoint) {
145
- throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" doesn't match with requested api endpoint "${apiEndpoint}" for service binding "${name}".`);
132
+ throw new Error(`Current Cloud Foundry api endpoint "${target.apiEndpoint}" different than api endpoint "${apiEndpoint}" for service binding "${name}".`);
146
133
  }
147
134
 
148
135
  let spaceGuid;
@@ -182,6 +169,7 @@ class CFKeyProvider {
182
169
  vcap: {
183
170
  label: offeringObj.name,
184
171
  plan: planObj.name,
172
+ tags: offeringObj.tags
185
173
  },
186
174
  resolved: true,
187
175
  },
@@ -190,72 +178,6 @@ class CFKeyProvider {
190
178
 
191
179
  return resolvedBinding;
192
180
  }
193
-
194
- getRequiresNameFromKind(service, kind) {
195
- const requiresName = KindToRequiresNameMap[kind];
196
- if (!requiresName) throw new Error(`Unknown CDS service name for service "${service}". Please specify as argument for "cds bind".`)
197
- return requiresName;
198
- }
199
-
200
- /**
201
- * Determine kind value for services based on VCAP binding
202
- * information in the configuration.
203
- */
204
- determineKinds(services) {
205
- const vcapServices = {}
206
-
207
- const getIndex = service => {
208
- const { credentials } = service;
209
- return credentials && credentials.binding && credentials.binding.index;
210
- }
211
-
212
- // Build VCAP_SERVICES
213
- for (let index = 0; index<services.length; index++) {
214
- const service = services[index];
215
- const { label, name, tags } = service.binding.vcap;
216
- if (!vcapServices[label]) vcapServices[label] = [];
217
- vcapServices[label].push({ credentials: { binding: { index } }, label, name, tags });
218
-
219
- service.kindCandidates = [ ]
220
- service.nameCandidates = [ ]
221
- }
222
-
223
- // Auto configure services based on VCAP_SERVICES
224
- const cds = require('@sap/cds/lib');
225
- const VCAP_SERVICES = process.env.VCAP_SERVICES;
226
- try {
227
- process.env.VCAP_SERVICES = JSON.stringify(vcapServices);
228
- const env = cds.env.for('cds', '.');
229
-
230
- // Extract "kind" from auto configured services
231
- for (const kind of Object.keys(env.requires)) {
232
- const requireService = env.requires[kind];
233
- const superKind = requireService.kind;
234
- const index = getIndex(requireService);
235
- if (!Number.isInteger(index)) continue;
236
-
237
- const superRequireService = env.requires[superKind];
238
- if (superRequireService) {
239
- const superIndex = getIndex(superRequireService);
240
- if (index === superIndex) continue;
241
- }
242
-
243
- services[index].kindCandidates.push(kind);
244
- }
245
-
246
- // Check if one "kind" per service was found
247
- for (const service of services) {
248
- if (service.kindCandidates.length !== 1) {
249
- throw new Error(`Unknown kind for service "${service.binding.instance}". Please specify with "--kind" option.`);
250
- }
251
-
252
- service.kind = service.kindCandidates[0];
253
- delete service.kindCandidates;
254
- }
255
- } finally {
256
- process.env.VCAP_SERVICES = VCAP_SERVICES;
257
- }
258
- }
259
181
  }
260
182
 
261
183
  class BatchRequest {
@@ -0,0 +1,157 @@
1
+ const cp = require('child_process');
2
+ const os = require('os');
3
+ const util = require('util');
4
+ const yaml = require("@sap/cds-foss")("yaml");
5
+
6
+ const axios = require('axios');
7
+ const { URL } = require('url');
8
+
9
+ const execFile = util.promisify(cp.execFile);
10
+ const IS_WIN = os.platform() === 'win32';
11
+
12
+ class KubernetesCredentialsProvider {
13
+
14
+ constructor() {
15
+ this.logger = console;
16
+ }
17
+
18
+ setLogger(logger) {
19
+ this.logger = logger;
20
+ }
21
+
22
+ async _kubectlRead(...args) {
23
+ this.logger.debug(`_kubectlRead: kubectl ${args.join(" ")}`);
24
+
25
+ const result = await execFile("kubectl", args, {
26
+ shell: IS_WIN,
27
+ stdio: ['inherit', 'pipe', 'inherit'],
28
+ });
29
+
30
+ return result.stdout.toString().trim();
31
+ }
32
+
33
+
34
+ async _kubectlConfig() {
35
+ const configString = await this._kubectlRead('config', 'view', '--raw');
36
+ const config = yaml.parse(configString);
37
+
38
+ const currentContextName = config["current-context"];
39
+ if (!currentContextName) throw new Error(`No current context in kubectl config.`);
40
+ const currentContextEntry = config.contexts && config.contexts.find( entry => entry.name === currentContextName && entry.context && entry.context.cluster && entry.context.user );
41
+ if (!currentContextEntry) throw new Error(`Current context not found in kubectl config.`);
42
+ const currentContext = currentContextEntry.context;
43
+ const clusterEntry = config.clusters && config.clusters.find( entry => entry.name === currentContext.cluster && entry.cluster && entry.cluster.server );
44
+ if (!currentContextEntry) throw new Error(`Cluster ${currentContext.cluster} not found in kubectl config.`);
45
+ const cluster = clusterEntry.cluster;
46
+ const userEntry = config.users && config.users.find( entry => entry.name === currentContext.user && entry.user && entry.user.token );
47
+ if (!userEntry) throw new Error(`User ${currentContext.user} not found in kubectl config.`);
48
+ const user = userEntry.user;
49
+
50
+ return {
51
+ url: cluster.server,
52
+ namespace: currentContext.namespace || 'default',
53
+ token: user.token
54
+ }
55
+ }
56
+
57
+ async init() {
58
+ this.config = await this._kubectlConfig();
59
+ }
60
+
61
+ async _req(urlPath) {
62
+
63
+ const url = new URL(urlPath, this.config.url);
64
+ this.logger.debug(`_req: ${url.toString()}`);
65
+
66
+ const result = await axios.get(url.toString(), {
67
+ validateStatus: status => status === 200 || status === 404,
68
+ headers: {
69
+ authorization: `Bearer ${this.config.token}`
70
+ }
71
+ });
72
+
73
+ if (result.status === 200) {
74
+ return result.data;
75
+ } else {
76
+ return null
77
+ }
78
+ }
79
+
80
+
81
+ async resolve(name, binding) {
82
+ if (!this.initPromise) {
83
+ this.initPromise = this.init();
84
+ }
85
+ await this.initPromise;
86
+
87
+ let {cluster, namespace, name: bindingName, secret } = binding;
88
+ let instance;
89
+ let secretOrBindingResource = 'Secret';
90
+
91
+ if (!namespace) namespace = this.config.namespace;
92
+ if (cluster && cluster !== this.config.url) throw new Error(`Current Kubernetes cluster "${this.config.url}" different than Kubernetes cluster "${cluster}" for service binding "${name}".`);
93
+
94
+ if (bindingName) {
95
+ const bindingResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/servicebindings/${encodeURIComponent(bindingName)}`);
96
+ if (bindingResource === null) {
97
+ // Fallback to secret instead of service binding;
98
+ secret = bindingName;
99
+ bindingName = undefined;
100
+ secretOrBindingResource = 'Service Binding or Secret';
101
+ } else {
102
+ secret = bindingResource.spec.secretName || throwError();
103
+ instance = bindingResource.spec.instanceRef && bindingResource.spec.instanceRef.name || throwError();
104
+ }
105
+ }
106
+
107
+ const secretResource = await this._req(`/api/v1/namespaces/${encodeURIComponent(namespace)}/secrets/${encodeURIComponent(secret)}`);
108
+ if (secretResource === null) throwError(`${secretOrBindingResource} '${secret}' not found.`);
109
+
110
+ const credentials = {};
111
+ for (const name in secretResource.data) {
112
+ let value = secretResource.data[name];
113
+ value = Buffer.from(value, "base64").toString(); // secret values are base64 encoded
114
+ // TODO: TESTIT
115
+ if (value && value[0] === '{') try { value = JSON.parse(value); } catch(e) {/* ignored */} // secret values may contain JSON objects
116
+ credentials[name] = value;
117
+ }
118
+
119
+ const resolvedBinding = {
120
+ binding: {
121
+ type: "k8s",
122
+ name: bindingName,
123
+ cluster: this.config.url,
124
+ instance,
125
+ namespace,
126
+ secret: secret,
127
+ resolved: true,
128
+ },
129
+ credentials
130
+ };
131
+
132
+
133
+ if (instance) {
134
+ const serviceInstanceResource = await this._req(`/apis/servicecatalog.k8s.io/v1beta1/namespaces/${encodeURIComponent(namespace)}/serviceinstances/${encodeURIComponent(instance)}`);
135
+
136
+ resolvedBinding.binding.vcap = {
137
+ label: serviceInstanceResource.spec.clusterServiceClassExternalName,
138
+ plan: serviceInstanceResource.spec.clusterServicePlanExternalName
139
+ }
140
+ }
141
+
142
+ return resolvedBinding;
143
+ }
144
+
145
+ }
146
+
147
+
148
+ /**
149
+ *
150
+ * @param {string} msg
151
+ * @returns {never}
152
+ */
153
+ function throwError(msg = 'Internal Error') {
154
+ throw new Error(msg);
155
+ }
156
+
157
+ module.exports = new KubernetesCredentialsProvider();
@@ -3,7 +3,7 @@ const path = require('path');
3
3
  const readline = require('readline');
4
4
 
5
5
  const { fse } = require('@sap/cds-foss');
6
- const { highlight, warn } = require('../util/term');
6
+ const { highlight } = require('../util/term');
7
7
 
8
8
  const commandUtil = require('../init/util/commandUtil');
9
9
 
@@ -108,19 +108,19 @@ ${file}
108
108
  }
109
109
 
110
110
  for (const service of services) {
111
- const { name, kind, binding, credentials } = service;
111
+ const { name, kind, binding, credentials, vcap } = service;
112
112
  const requireService = section[name] = section[name] || {};
113
113
 
114
- Object.assign(requireService, { kind, binding, credentials });
115
-
116
- options.logger.log(`Binding ${highlight(name)} to Cloud Foundry service ${highlight(binding.instance)} key ${highlight(binding.key)} with kind ${highlight(kind)}.`);
114
+ Object.assign(requireService, { binding, credentials });
115
+ if (kind) requireService.kind = kind;
116
+ if (vcap) requireService.vcap = vcap;
117
117
  }
118
118
 
119
119
  await this.writeJsonFile(jsonPath, jsonObject, options.logger);
120
120
  if (options.profile) {
121
- options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)} .`);
121
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)} in profile ${highlight(options.profile)}`);
122
122
  } else {
123
- options.logger.log(`Saving bindings to ${highlight(jsonPath)}.`);
123
+ options.logger.log(`Saving bindings to ${highlight(jsonPath)}`);
124
124
  }
125
125
  }
126
126
 
@@ -166,15 +166,14 @@ module.exports = class Activate extends BaseCommand {
166
166
 
167
167
  activationUrl = Activate.getMtxApiUrl(params.get('appUrl'), COMMAND_URL_SUFFIX);
168
168
  const requestOptions = Object.assign({
169
- headers: {},
170
- data: {
171
- extension: [...allFiles],
172
- undeployExtension: params.get('undeploy')
173
- }
169
+ headers: {}
174
170
  }, params.get('reqAuth'));
175
171
  requestOptions.headers['Content-Type'] = 'application/json';
176
172
 
177
- return axios.post(activationUrl, requestOptions);
173
+ return axios.post(activationUrl, {
174
+ extension: [...allFiles],
175
+ undeployExtension: params.get('undeploy')
176
+ }, requestOptions);
178
177
  })
179
178
  .then(response => {
180
179
  if (response === null) {
@@ -223,13 +222,12 @@ module.exports = class Activate extends BaseCommand {
223
222
  const customFileUrl = Activate.getMtxApiUrl(params.get('appUrl'), Activate.UPDATE_CUSTOM_FILE_URL_SUFFIX);
224
223
  const requestOptions = Object.assign({
225
224
  headers: {},
226
- data: {
227
- content: customFilesSerialized
228
- }
229
225
  }, params.get('reqAuth'));
230
226
  requestOptions.headers['Content-Type'] = 'application/json';
231
227
 
232
- await axios.post(customFileUrl, requestOptions);
228
+ await axios.post(customFileUrl, {
229
+ content: customFilesSerialized
230
+ }, requestOptions);
233
231
 
234
232
  Logger.log('Custom file upload finished');
235
233
  }
@@ -0,0 +1,38 @@
1
+ # OData V2 or V4 EDM Datatype mapping to CAP CDS
2
+
3
+ oData V2/V4 | CAP CDS | Comments (if any) |
4
+ ------------|----------| ----------|
5
+ Edm.Guid |cds.UUID | |
6
+ Edm.Boolean |cds.Boolean| |
7
+ Edm.Int16 |cds.Integer| |
8
+ Edm.Int32 |cds.Integer| |
9
+ Edm.Int64 | cds.Integer64| |
10
+ Edm.Decimal | cds.DecimalFloat| |
11
+ Edm.Decimal Precision > 0 | cds.Decimal| |
12
+ Edm.Single | cds.Double @odata.type: “Edm.Single”| |
13
+ Edm.Double | cds.Double| |
14
+ Edm.Double Precision > 0 |cds.Double| |
15
+ Edm.String | cds.LargeString| |
16
+ Edm.String MaxLength > 0 |cds.String| |
17
+ Edm.Binary MaxLength > 0 | cds.Binary | |
18
+ Edm.Binary | cds.LargeBinary | |
19
+ Edm.Byte |cds.Integer| |
20
+ Edm.Byte MaxLength > 0 |cds.Binary| |
21
+ Edm.SByte |cds.Integer| |
22
+ Edm.Stream | cds.LargeBinary @odata.type: “Edm.Stream” | |
23
+ Edm.DateTimeOffset Precision : Microsecond | cds.Timestamp, @odata.type: “Edm.DateTimeOffset”, @odata.precision:<> | The value of precision from odata will be mapped to @odata.precision |
24
+ Edm.DateTimeOffset Precision : Second | cds.DateTime, @odata.type: “Edm.DateTimeOffset”, @odata.precision:0| |
25
+ | |
26
+ **Below attributes are applicable to V2 only** |
27
+ | |
28
+ Edm.DateTime Precision : Microsecond | cds.Timestamp, @odata.type: “Edm.DateTime”, @odata.precision:<> | The value of precision from odata will be mapped to @odata.precision | |
29
+ Edm.DateTime Precision : Second | cds.DateTime, @odata.type: “Edm.DateTime”, @odata.precision:0| |
30
+ Edm.Time |cds.Time| |
31
+ Edm.DateTime sap:display-format="Date" |cds.Date| |
32
+ Edm.DateTimeOffset sap:display-format="Date" |cds.Date||
33
+ **Below attributes are applicable to V4 only** |
34
+ | |
35
+ Edm.TimeOfDay |cds.Time||
36
+ Edm.Date |cds.Date||
37
+
38
+
@@ -49,6 +49,7 @@ var Multimap = (function () {
49
49
  * @param {Object} key
50
50
  * @param {Object} val...
51
51
  */
52
+ // eslint-disable-next-line no-unused-vars
52
53
  Multimap.prototype.set = function (key, val) {
53
54
  var args = Array.prototype.slice.call(arguments);
54
55