@sap/cds-dk 6.4.0 → 6.5.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 (93) hide show
  1. package/CHANGELOG.md +28 -1
  2. package/bin/bind.js +30 -34
  3. package/bin/cds.js +14 -3
  4. package/bin/deploy/index.js +3 -1
  5. package/bin/deploy/to-sqlite.js +2 -1
  6. package/bin/env.js +4 -4
  7. package/bin/migrate.js +25 -4
  8. package/bin/push.js +4 -0
  9. package/bin/watch.js +3 -3
  10. package/lib/bind/KubernetesApiAccessor.js +79 -0
  11. package/lib/bind/{BindingManager.js → bindingManager.js} +16 -15
  12. package/lib/bind/bindingResolver.js +41 -68
  13. package/lib/bind/bindingUtil.js +93 -104
  14. package/lib/bind/cfKeyProvider.js +85 -74
  15. package/lib/bind/kubernetesCredentialsProvider.js +34 -132
  16. package/lib/bind/userCdsrcJsonStorageProvider.js +12 -18
  17. package/lib/client/params.js +2 -2
  18. package/lib/client/push.js +11 -6
  19. package/lib/client/settings_manager.js +2 -1
  20. package/lib/client/subscription.js +4 -1
  21. package/lib/import/odata/v2parser.js +32 -8
  22. package/lib/import/odata/v4parser.js +11 -3
  23. package/lib/init/constants.js +1 -1
  24. package/lib/init/index.js +3 -11
  25. package/lib/init/template/approuter/files/approuter-configmap.yaml +4 -1
  26. package/lib/init/template/approuter/files/approuter-mtxs-configmap.yaml +5 -1
  27. package/lib/init/template/approuter/files/package.json +1 -1
  28. package/lib/init/template/approuter/files/values.yaml.hbs +1 -1
  29. package/lib/init/template/approuter/files/xs-app.json.hbs +2 -2
  30. package/lib/init/template/approuter/index.js +4 -5
  31. package/lib/init/template/auditlog/index.js +1 -1
  32. package/lib/init/template/data/index.js +1 -1
  33. package/lib/init/template/destinations/index.js +1 -1
  34. package/lib/init/template/enterprise-messaging/index.js +1 -1
  35. package/lib/init/template/extensibility/index.js +6 -1
  36. package/lib/init/template/hana/files/chart.yaml +4 -0
  37. package/lib/init/template/hana/files/hana.yaml +1 -0
  38. package/lib/init/template/hana/files/java/db/package.json +1 -1
  39. package/lib/init/template/hana/files/values.yaml.hbs +6 -3
  40. package/lib/init/template/hana/index.js +73 -21
  41. package/lib/init/template/helm/chart/templates/_deployment_helpers.tpl +14 -6
  42. package/lib/init/template/helm/chart/values.root.schema.json +10 -0
  43. package/lib/init/template/helm/chart/values.schema.json +11 -110
  44. package/lib/init/template/helm/index.js +2 -2
  45. package/lib/init/template/helm/subcharts/content-deployment/Chart.yaml +5 -0
  46. package/lib/init/template/helm/subcharts/content-deployment/templates/_helpers.tpl +207 -0
  47. package/lib/init/template/helm/subcharts/content-deployment/templates/job.yaml +66 -0
  48. package/lib/init/template/helm/subcharts/content-deployment/templates/secret.yaml +10 -0
  49. package/lib/init/template/helm/subcharts/content-deployment/templates/service-account.yaml +4 -0
  50. package/lib/init/template/helm/subcharts/content-deployment/templates/service-binding.yaml +28 -0
  51. package/lib/init/template/helm/subcharts/content-deployment/values-lint.yaml +5 -0
  52. package/lib/init/template/helm/subcharts/content-deployment/values.md +1014 -0
  53. package/lib/init/template/helm/subcharts/content-deployment/values.schema.json +589 -0
  54. package/lib/init/template/helm/subcharts/content-deployment/values.yaml +54 -0
  55. package/lib/init/template/helm/{subchart → subcharts/web-application}/Chart.yaml +0 -0
  56. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/_helpers.tpl +56 -50
  57. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/api-rule.yaml +1 -1
  58. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/deployment.yaml +15 -9
  59. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/network-policy.yaml +0 -0
  60. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/pod-disruption-budget.yaml +2 -2
  61. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/secret.yaml +0 -0
  62. package/lib/init/template/helm/subcharts/web-application/templates/service-account.yaml +6 -0
  63. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/service-binding.yaml +4 -3
  64. package/lib/init/template/helm/{subchart → subcharts/web-application}/templates/service.yaml +0 -0
  65. package/lib/init/template/helm/subcharts/web-application/templating.md +44 -0
  66. package/lib/init/template/helm/{subchart → subcharts/web-application}/values-lint.yaml +0 -0
  67. package/lib/init/template/helm/{subchart → subcharts/web-application}/values.md +254 -200
  68. package/lib/init/template/helm/{subchart → subcharts/web-application}/values.schema.json +82 -28
  69. package/lib/init/template/helm/{subchart → subcharts/web-application}/values.yaml +15 -5
  70. package/lib/init/template/html5-repo/files/chart.yaml +4 -0
  71. package/lib/init/template/html5-repo/files/html5-apps-deployer-configmap.yaml +14 -0
  72. package/lib/init/template/html5-repo/index.js +39 -7
  73. package/lib/init/template/kibana-logging/index.js +1 -1
  74. package/lib/init/template/lint/index.js +1 -1
  75. package/lib/init/template/multitenancy/files/saas-registry.yaml +2 -2
  76. package/lib/init/template/multitenancy/files/service-manager.yaml +1 -0
  77. package/lib/init/template/multitenancy/files/values.yaml.hbs +10 -6
  78. package/lib/init/template/multitenancy/index.js +21 -3
  79. package/lib/init/util/commandUtil.js +5 -5
  80. package/lib/init/util/merge.js +27 -1
  81. package/lib/init/util/mvnArchetypeUtil.js +1 -1
  82. package/lib/init/util/projectReader.js +2 -2
  83. package/lib/util/exec.js +3 -3
  84. package/lib/util/term.js +7 -7
  85. package/lib/watch/livereload-connect.js +187 -0
  86. package/lib/watch/livereload-server.js +12 -12
  87. package/lib/watch/node-watch.js +1 -1
  88. package/npm-shrinkwrap.json +191 -404
  89. package/package.json +2 -5
  90. package/lib/init/template/hana/files/hana-deployer-job.yaml +0 -49
  91. package/lib/init/template/html5-repo/files/_html5_deployment_helpers.tpl +0 -16
  92. package/lib/init/template/html5-repo/files/html5-apps-deployer-job.yaml +0 -61
  93. package/lib/init/util/spinner.js +0 -80
package/CHANGELOG.md CHANGED
@@ -7,12 +7,39 @@ This project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
  The format is based on [Keep a Changelog](http://keepachangelog.com/).
9
9
 
10
+ ## Version 6.5.0 - 2023-01-27
11
+
12
+ ### Added
13
+
14
+ - `cds run/serve/migrate --resolve-bindings` now pulls required service credentials if bound via `cds bind`. (beta)
15
+ - `cds add helm` now supports multitenancy.
16
+ - `cds bind` now supports binding of `user-provided service instances` from Cloud Foundry.
17
+
18
+ ### Changed
19
+
20
+ - `cds push` now runs a build of the extension project to update the pushed extension archive (unless custom archive given).
21
+ - `cds init` and `cds bind` no longer use a spinner when performing long running operations.
22
+
23
+ ### Fixed
24
+
25
+ - `cds import` now generates flattened value for `@Common.FieldControl` annotation in the CSN for OData V4 files.
26
+ - `cds import` now treates `CollectionKind` property attribute in OData V2 similar to `Collection(<data_type>)` in OData V4.
27
+
28
+ ## Version 6.4.1 - 2022-01-17
29
+
30
+ ### Fixed
31
+
32
+ - `cds deploy -2 sqlite` now preserves the `schema_evolution` setting in the `package.json`.
33
+ - `cds import` bug fix for OData V4 import crash.
34
+
35
+ ### Changed
36
+
10
37
  ## Version 6.4.0 - 2022-12-15
11
38
 
12
39
  ### Added
13
40
 
14
41
  - `cds add helm` now supports approuter.
15
- - `cds subscribe --local` starts @sap/cds-mtxs locally instead of contacting a running instance via URL.
42
+ - `cds subscribe --local` starts `@sap/cds-mtxs` locally instead of contacting a running instance via URL.
16
43
  - `cds unsubscribe` removes the subscription of a tenant from a SaaS app.
17
44
 
18
45
  ### Changed
package/bin/bind.js CHANGED
@@ -46,19 +46,14 @@ module.exports = Object.assign(cds_bind, {
46
46
  `
47
47
  });
48
48
 
49
- const cds = require('../lib/cds');
50
- const LOG = cds.log('bind');
51
- // required for spinner
52
- LOG.isDebug = LOG._debug;
53
- LOG.isTTY = process.stdout.isTTY;
54
- const resolveBinding = require('../lib/bind/bindingResolver').resolver(LOG);
49
+
50
+ const BindingResolver = require('../lib/bind/bindingResolver')
55
51
 
56
52
  const { highlight, bold, info } = require('../lib/util/term');
57
53
 
58
- const Spinner = require('../lib/init/util/spinner');
59
- const { determineKinds, getRequiresNameFromKind, execAndExit, checkIsJavaProject, throwError, platformText, bindingText } = require('../lib/bind/bindingUtil');
54
+ const { determineKinds, getRequiresNameFromKind, execAndExit, checkIsJavaProject, platformText, bindingText, logger } = require('../lib/bind/bindingUtil');
60
55
 
61
- async function cds_bind(args, options, logger = LOG) {
56
+ async function cds_bind(args, options) {
62
57
 
63
58
  options.profile = parseProfile(options.profile);
64
59
  setProfile(options.profile);
@@ -89,8 +84,6 @@ async function cds_bind(args, options, logger = LOG) {
89
84
  throw new Error(`The option '--kind' cannot be specified together with multiple targets ('--to') services. Use one service per call or omit the '--kind' option.`);
90
85
  }
91
86
 
92
- const spinner = new Spinner();
93
-
94
87
  let on;
95
88
  if (options.on === 'k8s' || options.on === 'kubernetes') {
96
89
  on = 'k8s';
@@ -101,9 +94,11 @@ async function cds_bind(args, options, logger = LOG) {
101
94
  }
102
95
 
103
96
  const onText = { 'cf': 'Cloud Foundry', 'k8s': 'Kubernetes' }[on];
104
- const resolvedServices = await spinner.run(`Retrieving data from ${onText}...`, async () => { // NOSONAR
105
- return await Promise.all(targets.map(async target => {
97
+ logger.log(`Retrieving data from ${onText}...`);
98
+ let resolvedServices
106
99
 
100
+ try {
101
+ resolvedServices = await Promise.all(targets.map(async target => {
107
102
  let binding;
108
103
  if (on === 'k8s') {
109
104
  binding = { type: on, name: target };
@@ -111,21 +106,28 @@ async function cds_bind(args, options, logger = LOG) {
111
106
  const [instance, key] = target.split(/:/);
112
107
  binding = { type: on, instance, key };
113
108
  } else {
114
- throwError();
109
+ throw new Error('Internal Error');
115
110
  }
116
-
117
- const resolvedService = await resolveBinding(undefined, binding);
111
+ const resolvedService = await BindingResolver.resolveBinding(undefined, binding);
118
112
  resolvedService._target = target;
119
113
  delete resolvedService.credentials;
120
114
  resolvedService.binding.resolved = false;
121
115
  return resolvedService;
122
116
  }));
123
- });
117
+ } catch (error) {
118
+ if (error.name === 'AxiosError') {
119
+ const errors = error.response?.data?.errors ?? []
120
+ const responseInfo = errors.map(e => `${e.title}: ${e.detail} (${e.code})`).join(' \n')
121
+ const requestInfo = error.request?.path
122
+ throw new Error(`${error.toString()}\nResponse: ${responseInfo}\nRequest:${requestInfo}}`)
123
+ }
124
+ throw error
125
+ }
124
126
 
125
127
  if (options.kind) {
126
128
  resolvedServices.forEach(service => service.kind = options.kind);
127
129
  } else {
128
- determineKinds(resolvedServices, LOG);
130
+ determineKinds(resolvedServices);
129
131
  }
130
132
 
131
133
  for (const resolvedService of resolvedServices) {
@@ -135,37 +137,31 @@ async function cds_bind(args, options, logger = LOG) {
135
137
  resolvedService.name = getRequiresNameFromKind(resolvedService.binding.instance, resolvedService.kind);
136
138
  }
137
139
 
138
- const { kind, name, binding } = resolvedService;
139
- const kindText = kind && `with kind ${highlight(kind)}`;
140
- const nameText = !isJavaProject && name && highlight(name);
140
+ let { kind, name, binding } = resolvedService;
141
+ name = name ?? `custom-service:${binding.instance || binding.secret}`
142
+ resolvedService.name = resolvedService.name ?? name
143
+ const kindText = kind ? ` with kind ${highlight(kind)}` : ''
144
+ const nameText = highlight(name)
141
145
 
142
- logger.info(['Binding', nameText, 'to', platformText(binding), bindingText(binding, highlight), kindText].filter(t => t).join(' '));
143
-
144
- if (!resolvedService.name) {
145
- const { binding } = resolvedService;
146
- // Generate a custom service entry for Java projects w/o the need to specify a name manually
147
- resolvedService.name = `custom-service:${binding.instance || binding.secret}`
148
- }
146
+ logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding, highlight)}${kindText}`);
149
147
  }
150
148
 
151
149
  // prepare for different providers
152
150
  const userCdsrcJsonStorageProvider = require('../lib/bind/userCdsrcJsonStorageProvider');
153
151
 
154
152
  await userCdsrcJsonStorageProvider.storeServices(resolvedServices, {
155
- ...options,
156
- logger
153
+ ...options
157
154
  });
158
155
 
159
- logger.info();
160
- logger.info(`${info('TIP:')} Run with cloud bindings: ${bold(runCmd(options, isJavaProject))}`);
156
+ logger.log(`${info('TIP:')} Run with cloud bindings: ${bold(runCmd(options, isJavaProject))}`);
161
157
  }
162
158
 
163
159
  async function cds_bind_exec(command) { // eslint-disable-lint
164
160
  const cds = require('../lib/cds');
165
161
  const env = cds.env.for('cds', process.cwd());
166
162
 
167
- const BindingManager = require('../lib/bind/BindingManager')
168
- const bindingManager = new BindingManager({ env, output: 'info' })
163
+ const BindingManager = require('../lib/bind/bindingManager')
164
+ const bindingManager = new BindingManager({ env })
169
165
  const bindingEnv = await bindingManager.bindingEnv()
170
166
  const processEnv = Object.assign(process.env, bindingEnv)
171
167
 
package/bin/cds.js CHANGED
@@ -37,7 +37,7 @@ const cli = { //NOSONAR
37
37
 
38
38
  const task = this.load (cmd, /*else*/ (f)=> argv.unshift(cmd) && _defaultCompile (argv,f))
39
39
  if (task) {
40
- // REVISIT: '--' handling for cds bind --exec. Should be moved to cds_cli
40
+ // REVISIT bind: '--' handling for cds bind --exec. Should be moved to cds_cli
41
41
  const endOfOptions = argv.indexOf('--')
42
42
  let appendArgs = []
43
43
  if (endOfOptions>=0) {
@@ -46,7 +46,9 @@ const cli = { //NOSONAR
46
46
  }
47
47
  const args = cds_cli.args(task,argv)
48
48
  args[0].push(...appendArgs)
49
- return task.apply (this, args)
49
+ const resolveBindings = cmd in { run: 1, serve: 1, migrate: 1 } && argv.includes('--resolve-bindings')
50
+ if (resolveBindings) return _resolveBindings().then(() => task.apply(this, args))
51
+ else return task.apply(this, args)
50
52
  }
51
53
  },
52
54
 
@@ -62,7 +64,16 @@ const cli = { //NOSONAR
62
64
  help (cmd) { return this.exec ('help', cmd) }
63
65
  }
64
66
 
65
- const DEBUG = /\b(cli)\b/.test (process.env.DEBUG) && console.error
67
+ const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) && console.debug
68
+
69
+ async function _resolveBindings() {
70
+ const cds = require('../lib/cds')
71
+ const BindingManager = require('../lib/bind/bindingManager')
72
+ const bindingManager = new BindingManager()
73
+ const bindingEnv = await bindingManager.bindingEnv()
74
+ Object.assign(process.env, bindingEnv)
75
+ cds.env = cds.env.for('cds') // reload env
76
+ }
66
77
 
67
78
  function _local (id, _else) {
68
79
  try { return require.resolve (id, {paths:[process.cwd(), __dirname]}) }
@@ -112,10 +112,12 @@ async function update_package_json (kind, model, database) {
112
112
  const { readFile, writeFile } = fs.promises
113
113
  const package_json = path.resolve ('package.json'), exists = isfile (package_json)
114
114
  const conf = exists ? JSON.parse (await readFile (package_json)) : {}
115
- const requires = ['cds','requires'] .reduce ((p,n)=>p[n] || (p[n]={}), conf)
115
+ const requires = conf.cds?.requires
116
+ const { schema_evolution } = requires?.db ?? {}
116
117
  requires.db = cds.env.requires.db = { kind, model }
117
118
  if (database) requires.db.credentials = { database }
118
119
  else if (kind === 'sqlite') requires.db.credentials = { database:'db.sqlite' }
120
+ if (schema_evolution) requires.db.schema_evolution = schema_evolution
119
121
  await writeFile (package_json, JSON.stringify(conf,null,' '))
120
122
  console.log (`/> ${ exists ? 'updated' : 'created' } ./package.json`)
121
123
  }
@@ -1,6 +1,7 @@
1
1
  exports.deploy = function deploy_to_sqlite (_model, _db, options) {
2
2
 
3
- if (options.dialect === 'postgres' && !options.dry) console.error('\nDeploying to PostgreSQL is not yet supported\n')
3
+ if (options.dialect === 'postgres' && !options.dry) console.error('\nDeploying into a PostgreSQL database is not yet supported\n')
4
+ if (options.dialect === 'h2' && !options.dry) console.error('\nDeploying into a H2 database is not yet supported\n')
4
5
 
5
6
  const cds = require('../../lib/cds')
6
7
  const conf = cds.env.requires.db ?? {}
package/bin/env.js CHANGED
@@ -53,8 +53,8 @@ async function env ([cmd, key, value], options={}) {
53
53
  let env = options.for ? cds.env.for(options.for) : cds.env
54
54
 
55
55
  if (options["resolve-bindings"]) {
56
- const BindingManager = require('../lib/bind/BindingManager')
57
- const bindingManager = new BindingManager({env, output: "debug"})
56
+ const BindingManager = require('../lib/bind/bindingManager')
57
+ const bindingManager = new BindingManager({ env, silent: true })
58
58
  const bindingEnv = await bindingManager.bindingEnv()
59
59
  Object.assign(process.env, bindingEnv)
60
60
  env = cds.env.for(options.for || 'cds') // reload env
@@ -97,14 +97,14 @@ const commands = {
97
97
  if (!process.stdout.isTTY) return commands.json (conf, key, val)
98
98
  // tty: display formatted object
99
99
  const obj = (key ? conf.get(key) : conf)
100
- console.log (inspect (obj, {depth:22,colors:true}).replace(/\s+use:.*\[Getter\].*/g,''))
100
+ console.log (inspect (obj, {depth:22,colors:true}).replace(/\s+use:.*\[Getter\][^,]+,?/g,''))
101
101
  },
102
102
 
103
103
  // eslint-disable-next-line no-unused-vars
104
104
  json (conf, key, val) {
105
105
  const obj = (key ? conf.get(key) : conf)
106
106
  const str = JSON.stringify(obj, null, 2)
107
- return console.log (str ? str.replace(/\s+use:.*\[Getter\].*/g,'') : str)
107
+ return console.log (str ? str.replace(/\s+use:.*\[Getter\][^,]+,?/g,'') : str)
108
108
  },
109
109
 
110
110
  set () {
package/bin/migrate.js CHANGED
@@ -1,11 +1,11 @@
1
1
  module.exports = Object.assign(migrate, {
2
- options: ['--directory'],
2
+ options: ['--directory', '--tag', '--tagRule'],
3
3
  shortcuts: ['-d'],
4
- flags: ['--dry'],
4
+ flags: ['--dry', '--force', '--cleanup', '--resolve-bindings'],
5
5
  help: `
6
6
  # SYNOPSIS
7
7
 
8
- *cds migrate* <tenant1|"*">[,<tenant2>] -d <directory> --dry
8
+ *cds migrate* <tenant1|"*">[,<tenant2>] [-d <directory>] [--tag <tag>] [--tagRule <tag rule>] [--dry] [--force]
9
9
 
10
10
  Migrate extensions created with @sap/cds-mtx to extensions for @sap/cds-mtxs
11
11
 
@@ -17,10 +17,30 @@ module.exports = Object.assign(migrate, {
17
17
 
18
18
  Specify the target directory of your migrated extension projects. By default, the migrated extension projects will not be stored.
19
19
 
20
+ *--tag* <tag>
21
+
22
+ Specify a default tag that is to be used to store the extension. If not specified "migrated" will be used.
23
+
24
+ *--tagRule* <tag rule>
25
+
26
+ Specify a regular expression that describes the mapping from file names to tags, e. g. "extension-(.*)\.cds" to map from "extension-123.cds" to tag "123". If the expression does not match, the default tag will be used.
27
+
28
+ *--force*
29
+
30
+ Force the re-migration of already migrated tenants.
31
+
20
32
  *--dry*
21
33
 
22
34
  Do a dry-run only. Only run a verification of the extension migration.
23
35
 
36
+ *--cleanup* (beta)
37
+
38
+ Removes all metadata containers created by @sap/cds-mtx.
39
+
40
+ *--resolve-bindings* (beta)
41
+
42
+ Resolve remote service bindings configured via *cds bind*.
43
+
24
44
  # SEE ALSO
25
45
  `})
26
46
 
@@ -30,4 +50,5 @@ async function migrate (param, options = {}) {
30
50
  await require('@sap/cds-mtxs/bin/cds-mtx-migrate').run(param[0], options)
31
51
  }
32
52
 
33
- /* eslint no-console: off */
53
+ /* eslint no-console: off */
54
+ /* eslint no-useless-escape: off */
package/bin/push.js CHANGED
@@ -14,6 +14,10 @@ module.exports = Object.assign(push, {
14
14
  A download URL may contain Basic-Auth credentials as in 'https://user:pass@host'.
15
15
 
16
16
  By default, the current working directory is used as project directory.
17
+
18
+ Unless a custom archive is given, the extension project will be automatically built
19
+ in order to update the generated extension archive. This ensures the most recent
20
+ extension model is pushed.
17
21
 
18
22
  Any authentication data will not be saved.
19
23
 
package/bin/watch.js CHANGED
@@ -43,7 +43,7 @@ module.exports = Object.assign ( watch, {
43
43
 
44
44
  `})
45
45
 
46
- const DEBUG = /\bwatch\b/.test(process.env.DEBUG) && console.debug
46
+ const DEBUG = /\b(y|all|cli|watch)\b/.test(process.env.DEBUG) && console.debug
47
47
  const {delimiter,dirname,relative,resolve,sep} = require ('path')
48
48
  const fs = require ('fs')
49
49
 
@@ -91,7 +91,7 @@ async function watch ([cwd], {
91
91
  ext += ',css,gif,html,jpg,png,svg'
92
92
  includes = fioriIncludes
93
93
  liveReload = new (require ('../lib/watch/livereload-server'))
94
- env.CDS_LIVERELOAD_PATH = require.resolve ('connect-livereload')
94
+ env.CDS_LIVERELOAD_PATH = require.resolve ('../lib/watch/livereload-connect')
95
95
  env.CDS_LIVERELOAD_URL = await liveReload.start (options['livereload'])
96
96
  .catch(err => { console.log(`Error starting live reload: ${err.message}`); liveReload = null })
97
97
  } else {
@@ -113,7 +113,7 @@ async function watch ([cwd], {
113
113
  DEBUG && log (`also watching: ${includes.source}`)
114
114
  if (liveReload) log (`live reload enabled for browsers`)
115
115
 
116
- const BindingManager = require('../lib/bind/BindingManager')
116
+ const BindingManager = require('../lib/bind/bindingManager')
117
117
  const bindingManager = new BindingManager({cwd})
118
118
  const bindingEnv = await bindingManager.bindingEnv()
119
119
  if (bindingEnv) env = {...env, ...bindingEnv}
@@ -0,0 +1,79 @@
1
+ const yaml = require("@sap/cds-foss")("yaml");
2
+ const { exec } = require('../util/exec');
3
+
4
+ class KubernetesApiAccessor {
5
+
6
+ constructor() {
7
+
8
+ this.config = once( async() => {
9
+ const configString = await exec({result: 'stdout'}) `kubectl config view --raw`;
10
+ const config = yaml.parse(configString);
11
+
12
+ const currentContextName = config["current-context"];
13
+ if (!currentContextName) throw new Error(`No current context in kubectl config.`);
14
+ const currentContextEntry = config.contexts && config.contexts.find( entry => entry.name === currentContextName && entry.context && entry.context.cluster && entry.context.user );
15
+ if (!currentContextEntry) throw new Error(`Current context not found in kubectl config.`);
16
+ const currentContext = currentContextEntry.context;
17
+ const clusterEntry = config.clusters && config.clusters.find( entry => entry.name === currentContext.cluster && entry.cluster && entry.cluster.server );
18
+ if (!currentContextEntry) throw new Error(`Cluster ${currentContext.cluster} not found in kubectl config.`);
19
+ const { cluster } = clusterEntry;
20
+
21
+ const serverToContext = {};
22
+ for (const clusterEntry of config.clusters) {
23
+ const contextEntries = config.contexts.filter( contextEntry => contextEntry.context.cluster === clusterEntry.name );
24
+ // Collect only cluster servers that are used in exactly one context, to clearly identify the context to be used
25
+ if (contextEntries.length === 1) {
26
+ serverToContext[clusterEntry.cluster.server] = contextEntries[0];
27
+ }
28
+ }
29
+
30
+ return {
31
+ namespace: currentContext.namespace || 'default',
32
+ context: currentContextEntry,
33
+ cluster,
34
+ serverToContext
35
+ }
36
+ });
37
+ }
38
+
39
+ async context({cluster, namespace}) {
40
+ const config = await this.config();
41
+ let contextEntry;
42
+
43
+ if (cluster && cluster !== config.cluster.server) {
44
+ contextEntry = config.serverToContext[cluster] || throwError(`Cluster ${cluster} not found in kubectl configuration`);
45
+ } else {
46
+ contextEntry = config.context;
47
+ }
48
+
49
+ return {
50
+ context: contextEntry.name,
51
+ cluster: cluster || config.cluster.server,
52
+ namespace: namespace || contextEntry.context.namespace || 'default'
53
+ };
54
+ }
55
+
56
+ async get(options) {
57
+ const {context, namespace, kind, name} = options;
58
+
59
+ const result = await exec({stdio: ['ignore', 'pipe', 'ignore'], acceptStatuses: [0, 1]}) `kubectl --output json --context ${context} --namespace ${namespace} get ${kind} ${name}`;
60
+ if (result.status === 0)
61
+ return JSON.parse(result.stdout);
62
+ else
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function once(func) {
68
+ let result;
69
+ return function(...args) {
70
+ if (!result) result = func(...args);
71
+ return result;
72
+ }
73
+ }
74
+
75
+ function throwError(msg = 'Internal Error') {
76
+ throw new Error(msg);
77
+ }
78
+
79
+ module.exports = KubernetesApiAccessor;
@@ -1,32 +1,33 @@
1
- const cds = require('../cds')
2
- const LOG = cds.log('bind');
3
- const { resolveBindings, getUnresolvedBindings } = require('./bindingResolver')
1
+ const bindingResolver = require('./bindingResolver')
2
+ const { logger } = require('./bindingUtil')
3
+ const DEBUG = /\b(all|cli)\b/.test(process.env.DEBUG)
4
4
 
5
5
  class BindingManager {
6
- constructor(options) {
6
+ constructor(options = {}) {
7
7
  this.cache = {}
8
8
  this.bindingsStr = ''
9
9
  this.options = options
10
+ logger.log = DEBUG || !options.silent ? console.log : () => { }
10
11
  }
11
12
 
12
13
  getBindings() {
13
- const bindings = getUnresolvedBindings(this.options)
14
+ const bindings = bindingResolver.getUnresolvedBindings(this.options)
14
15
  return bindings
15
16
  }
16
17
 
17
18
  async resolve(bindings) {
18
19
  if (!bindings) {
19
- bindings = this.getBindings(this.options)
20
+ bindings = this.getBindings()
20
21
  this.bindingsStr = JSON.stringify(bindings)
21
22
  }
22
23
 
23
- return await resolveBindings({...this.options, bindings, cache: this.cache})
24
+ return await bindingResolver.resolveBindings({ ...this.options, bindings, cache: this.cache })
24
25
  }
25
26
 
26
27
  async bindingEnv(bindings) {
27
28
  if (!bindings) bindings = await this.resolve()
28
29
  if (bindings) {
29
- const VCAP_SERVICES = JSON.stringify(this.toVcapServices(bindings))
30
+ const VCAP_SERVICES = JSON.stringify(BindingManager.toVcapServices(bindings))
30
31
  return { VCAP_SERVICES }
31
32
  } else {
32
33
  return undefined
@@ -44,7 +45,7 @@ class BindingManager {
44
45
 
45
46
  try {
46
47
  if (this.pendingUpdate) await this.pendingUpdate
47
- } catch(error) {
48
+ } catch (error) {
48
49
  // ignore
49
50
  }
50
51
  this.pendingUpdate = undefined
@@ -55,18 +56,18 @@ class BindingManager {
55
56
  try {
56
57
  this.pendingUpdate = this.resolve(bindings)
57
58
  resolvedBindings = await this.pendingUpdate
58
- } catch(error) {
59
- LOG.error(error.message)
59
+ } catch (error) {
60
+ console.error(error.message)
60
61
  return
61
62
  }
62
63
 
63
64
  if (this.bindingsStr !== bindingsStr) return // Next update in progress
64
65
 
65
66
  const env = Object.assign({ VCAP_SERVICES: '{}' }, await this.bindingEnv(resolvedBindings));
66
- if (onAfterUpdate) onAfterUpdate({bindings: resolvedBindings, env})
67
+ if (onAfterUpdate) onAfterUpdate({ bindings: resolvedBindings, env })
67
68
  }
68
69
 
69
- toVcapServices(bindings) {
70
+ static toVcapServices(bindings) {
70
71
  const services = {}
71
72
 
72
73
  for (const name in bindings) {
@@ -77,11 +78,11 @@ class BindingManager {
77
78
 
78
79
  // setting the 'name' property is important to make CAP match the CDS service to the VCAP service
79
80
  // it is also added to the 'vcap' property by 'cds bind'
80
- serviceEntries.push({ ...vcap, name, credentials: binding.credentials})
81
+ serviceEntries.push({ ...vcap, name, credentials: binding.credentials })
81
82
  }
82
83
 
83
84
  return services
84
- }
85
+ }
85
86
  }
86
87
 
87
88
  module.exports = BindingManager
@@ -1,78 +1,51 @@
1
- const cds = require('../cds')
2
- const LOG = cds.log('bind');
3
- const resolveBinding = resolver(LOG);
4
- const { platformText, bindingText } = require('./bindingUtil');
1
+ const cds = require('../cds');
2
+ const { platformText, bindingText, logger } = require('./bindingUtil');
5
3
 
6
- function getUnresolvedBindings({cwd = process.cwd(), env}) {
7
- if (!env) env = cds.env.for('cds', cwd)
8
- if (feature(env) === false || feature(env) === "false") return {}
9
-
10
- const bindings = {}
11
- const requires = env.requires || {}
12
- for (const name in requires) {
13
- const service = requires[name]
14
- if (service && service.binding && !service.binding.resolved) {
15
- bindings[name] = {...service.binding, kind: service.kind }
4
+ class BindingResolver {
5
+ getUnresolvedBindings({ cwd = process.cwd(), env } = {}) {
6
+ if (!env) {
7
+ env = cds.env.for('cds', cwd)
16
8
  }
17
- }
18
-
19
- return bindings
20
- }
21
-
22
- async function resolveBindings({bindings, cwd = process.cwd(), env, output = 'info', cache = {} }) {
23
- bindings = bindings || getUnresolvedBindings({cwd, env})
24
- if (Object.keys(bindings).length===0) return undefined
25
- LOG[output](`resolving cloud service bindings...`)
26
-
27
- const resolvedBindings = {}
28
-
29
- await Promise.all( Object.keys(bindings).map( async name => {
30
- const binding = bindings[name]
31
- const cacheKey = JSON.stringify(binding)
32
- let resolvedBinding
33
- if (!cache[cacheKey]) {
34
- resolvedBinding = cache[cacheKey] = await resolveBinding(name, binding)
35
- LOG[output](`bound ${name} to ${platformText(binding)} ${bindingText(binding)}`)
36
- } else {
37
- resolvedBinding = cache[cacheKey]
9
+ const bindings = {}
10
+ const requires = env.requires || {}
11
+ for (const name in requires) {
12
+ const service = requires[name]
13
+ if (service && service.binding && !service.binding.resolved) {
14
+ bindings[name] = { ...service.binding, kind: service.kind }
15
+ }
38
16
  }
17
+ return bindings
18
+ }
39
19
 
40
- resolvedBindings[name] = resolvedBinding
41
- } ))
42
-
43
- return Object.keys(resolvedBindings).length > 0 ? resolvedBindings : undefined
44
- }
45
-
46
- let cfKeyProvider
47
- let kubernetesCredentialsProvider
20
+ async resolveBindings({ bindings, cwd = process.cwd(), env, cache = {} }) {
21
+ bindings = bindings || this.getUnresolvedBindings({ cwd, env })
22
+ if (Object.keys(bindings).length === 0) return undefined
23
+ logger.log(`resolving cloud service bindings...`)
24
+
25
+ const resolvedBindings = {}
26
+
27
+ await Promise.all(Object.keys(bindings).map(async name => {
28
+ const binding = bindings[name]
29
+ const cacheKey = JSON.stringify(binding)
30
+ let resolvedBinding
31
+ if (!cache[cacheKey]) {
32
+ resolvedBinding = cache[cacheKey] = await this.resolveBinding(name, binding)
33
+ logger.log(`bound ${name} to ${platformText(binding)} ${bindingText(binding)}`)
34
+ } else {
35
+ resolvedBinding = cache[cacheKey]
36
+ }
37
+ resolvedBindings[name] = resolvedBinding
38
+ }))
39
+ return Object.keys(resolvedBindings).length > 0 ? resolvedBindings : undefined
40
+ }
48
41
 
49
- function resolver(log) {
50
- return function resolveBinding(name, binding) {
42
+ resolveBinding(name, binding) {
51
43
  switch (binding.type) {
52
- case 'cf':
53
- if (!cfKeyProvider) {
54
- cfKeyProvider = require('./cfKeyProvider');
55
- cfKeyProvider.setLogger(log);
56
- }
57
- return cfKeyProvider.resolve(name, binding)
58
- case 'k8s':
59
- if (!kubernetesCredentialsProvider) {
60
- kubernetesCredentialsProvider = require('./kubernetesCredentialsProvider')
61
- kubernetesCredentialsProvider.setLogger(log)
62
- }
63
- return kubernetesCredentialsProvider.resolve(name, binding)
64
- default:
65
- throw new Error(`Unsupported binding type "${binding.type}" for service "${name}".`)
44
+ case 'cf': return require('./cfKeyProvider').resolve(name, binding)
45
+ case 'k8s': return require('./kubernetesCredentialsProvider').resolve(name, binding)
46
+ default: throw new Error(`Unsupported binding type "${binding.type}" for service "${name}".`)
66
47
  }
67
48
  }
68
49
  }
69
50
 
70
- function feature(env) {
71
- return env.features && env.features.dk && env.features.dk.resolveBindingsForTesting
72
- }
73
-
74
- module.exports = {
75
- resolver,
76
- resolveBindings,
77
- getUnresolvedBindings
78
- }
51
+ module.exports = new BindingResolver()