@sap/cds-dk 8.7.3 → 8.8.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/CHANGELOG.md +27 -1
- package/bin/bind.js +7 -8
- package/bin/cds.js +2 -4
- package/bin/compile/index.js +1 -0
- package/bin/deploy/index.js +10 -5
- package/bin/watch.js +48 -7
- package/lib/bind/{cfKeyProvider.js → cf.js} +43 -53
- package/lib/bind/k8s.js +138 -0
- package/lib/bind/plugin.js +48 -0
- package/lib/bind/shared.js +267 -0
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +0 -2
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -3
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +3 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +3 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +2 -1
- package/lib/build/util.js +1 -1
- package/lib/compile/to-mermaid.js +1 -2
- package/lib/deploy/to-hana/hana.js +1 -1
- package/lib/deploy/to-hana/mtaUtil.js +0 -13
- package/lib/import/common.js +6 -7
- package/lib/init/merge.js +41 -32
- package/lib/init/projectReader.js +15 -7
- package/lib/init/registries/mta.js +7 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/files/xs-app.json.hbs +0 -11
- package/lib/init/template/cloud-logging/files/package.json.hbs +12 -1
- package/lib/init/template/cloud-logging/files/values.yaml.hbs +0 -2
- package/lib/init/template/cloud-logging/index.js +8 -15
- package/lib/init/template/destination/files/values.yaml +4 -4
- package/lib/init/template/destination/index.js +1 -1
- package/lib/init/template/html5-repo/files/app-package.json.hbs +0 -2
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +25 -0
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +1 -2
- package/lib/init/template/html5-repo/files/package.json.hbs +1 -1
- package/lib/init/template/html5-repo/files/ui-resources/package.json.hbs +11 -0
- package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +4 -7
- package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -4
- package/lib/init/template/html5-repo/index.js +22 -10
- package/lib/init/template/http/index.js +1 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +0 -2
- package/lib/init/template/multitenancy/index.js +1 -1
- package/lib/init/template/portal/files/mta.yaml.hbs +9 -0
- package/lib/init/template/portal/index.js +3 -1
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +119 -119
- package/lib/init/template/sample/files/app/browse/webapp/manifest.json +1 -1
- package/lib/init/template/telemetry/index.js +0 -13
- package/lib/init/template/typer/files/package.json +3 -0
- package/lib/init/template/workzone-standard/files/cdm.json.hbs +97 -0
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +23 -0
- package/lib/init/template/workzone-standard/index.js +70 -39
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +2 -2
- package/lib/init/template/xsuaa/files/values.yaml.hbs +0 -2
- package/lib/init/template/xsuaa/index.js +22 -10
- package/lib/mtx/util/cf.js +1 -2
- package/lib/util/cf.js +104 -84
- package/lib/util/command.js +1 -2
- package/lib/util/git.js +1 -1
- package/npm-shrinkwrap.json +31 -31
- package/package.json +1 -1
- package/bin/deploy/to-cf.js +0 -0
- package/bin/deploy/via-mta.js +0 -0
- package/lib/bind/KubernetesApiAccessor.js +0 -81
- package/lib/bind/bindingManager.js +0 -89
- package/lib/bind/bindingResolver.js +0 -54
- package/lib/bind/bindingUtil.js +0 -129
- package/lib/bind/index.js +0 -127
- package/lib/bind/kubernetesCredentialsProvider.js +0 -84
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -92
- package/lib/init/template/destination/files/package.sidecar.json +0 -7
- package/lib/init/template/html5-repo/files/package.sidecar.json +0 -7
- package/lib/init/template/telemetry/files/package.json.hbs +0 -18
package/bin/deploy/via-mta.js
DELETED
|
File without changes
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
const { yaml } = require('../cds').utils
|
|
2
|
-
const { execSync } = require('child_process');
|
|
3
|
-
const { bold } = require('../util/term');
|
|
4
|
-
|
|
5
|
-
class KubernetesApiAccessor {
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
|
|
9
|
-
this.config = once( async() => {
|
|
10
|
-
const configString = execSync('kubectl config view --raw').toString();
|
|
11
|
-
const config = yaml.parse(configString);
|
|
12
|
-
|
|
13
|
-
const currentContextName = config['current-context'];
|
|
14
|
-
if (!currentContextName) throw new Error(`No current context in kubectl config.`);
|
|
15
|
-
const currentContextEntry = config.contexts && config.contexts.find( entry => entry.name === currentContextName && entry.context && entry.context.cluster && entry.context.user );
|
|
16
|
-
if (!currentContextEntry) throw new Error(`Current context not found in kubectl config.`);
|
|
17
|
-
const currentContext = currentContextEntry.context;
|
|
18
|
-
const clusterEntry = config.clusters && config.clusters.find( entry => entry.name === currentContext.cluster && entry.cluster && entry.cluster.server );
|
|
19
|
-
if (!currentContextEntry) throw new Error(`Cluster ${bold(currentContext.cluster)} not found in kubectl config.`);
|
|
20
|
-
const { cluster } = clusterEntry;
|
|
21
|
-
|
|
22
|
-
const serverToContext = {};
|
|
23
|
-
for (const clusterEntry of config.clusters) {
|
|
24
|
-
const contextEntries = config.contexts.filter( contextEntry => contextEntry.context.cluster === clusterEntry.name );
|
|
25
|
-
// Collect only cluster servers that are used in exactly one context, to clearly identify the context to be used
|
|
26
|
-
if (contextEntries.length === 1) {
|
|
27
|
-
serverToContext[clusterEntry.cluster.server] = contextEntries[0];
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
namespace: currentContext.namespace || 'default',
|
|
33
|
-
context: currentContextEntry,
|
|
34
|
-
cluster,
|
|
35
|
-
serverToContext
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async context({cluster, namespace}) {
|
|
41
|
-
const config = await this.config();
|
|
42
|
-
let contextEntry;
|
|
43
|
-
|
|
44
|
-
if (cluster && cluster !== config.cluster.server) {
|
|
45
|
-
contextEntry = config.serverToContext[cluster] || throwError(`Cluster ${bold(cluster)} not found in kubectl configuration`);
|
|
46
|
-
} else {
|
|
47
|
-
contextEntry = config.context;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
context: contextEntry.name,
|
|
52
|
-
cluster: cluster || config.cluster.server,
|
|
53
|
-
namespace: namespace || contextEntry.context.namespace || 'default'
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async get(options) {
|
|
58
|
-
const {context, namespace, kind, name} = options;
|
|
59
|
-
try {
|
|
60
|
-
return JSON.parse(execSync(`kubectl --output json --context ${context} --namespace ${namespace} get ${kind} ${name}`).toString());
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
if (error.status === 1) return null
|
|
64
|
-
else throw error
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function once(func) {
|
|
70
|
-
let result;
|
|
71
|
-
return function(...args) {
|
|
72
|
-
if (!result) result = func(...args);
|
|
73
|
-
return result;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function throwError(msg = 'Internal Error') {
|
|
78
|
-
throw new Error(msg);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
module.exports = KubernetesApiAccessor;
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
const bindingResolver = require('./bindingResolver')
|
|
2
|
-
const { logger } = require('./bindingUtil')
|
|
3
|
-
const cds = require('../cds')
|
|
4
|
-
const DEBUG = cds.log('cli')._debug // REVISIT: This loads cds.env too early!
|
|
5
|
-
|
|
6
|
-
class BindingManager {
|
|
7
|
-
constructor(options = {}) {
|
|
8
|
-
this.cache = {}
|
|
9
|
-
this.bindingsStr = ''
|
|
10
|
-
this.options = options
|
|
11
|
-
logger.log = DEBUG || !options.silent ? console.log : () => { }
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
getBindings() {
|
|
15
|
-
const bindings = bindingResolver.getUnresolvedBindings(this.options)
|
|
16
|
-
return bindings
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async resolve(bindings) {
|
|
20
|
-
if (!bindings) {
|
|
21
|
-
bindings = this.getBindings()
|
|
22
|
-
this.bindingsStr = JSON.stringify(bindings)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return await bindingResolver.resolveBindings({ ...this.options, bindings, cache: this.cache })
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async bindingEnv(bindings) {
|
|
29
|
-
if (!bindings) bindings = await this.resolve()
|
|
30
|
-
if (bindings) {
|
|
31
|
-
const VCAP_SERVICES = JSON.stringify(BindingManager.toVcapServices(bindings))
|
|
32
|
-
return { VCAP_SERVICES }
|
|
33
|
-
} else {
|
|
34
|
-
return undefined
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async update({ onBeforeUpdate, onAfterUpdate } = {}) {
|
|
39
|
-
const oldBindingsStr = this.bindingsStr
|
|
40
|
-
const bindings = this.getBindings()
|
|
41
|
-
const bindingsStr = JSON.stringify(bindings)
|
|
42
|
-
this.bindingsStr = bindingsStr
|
|
43
|
-
|
|
44
|
-
if (this.bindingsStr === oldBindingsStr) return // No change
|
|
45
|
-
if (onBeforeUpdate) await onBeforeUpdate()
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
if (this.pendingUpdate) await this.pendingUpdate
|
|
49
|
-
} catch (error) {
|
|
50
|
-
// ignore
|
|
51
|
-
}
|
|
52
|
-
this.pendingUpdate = undefined
|
|
53
|
-
|
|
54
|
-
if (this.bindingsStr !== bindingsStr) return // Next update in progress
|
|
55
|
-
|
|
56
|
-
let resolvedBindings
|
|
57
|
-
try {
|
|
58
|
-
this.pendingUpdate = this.resolve(bindings)
|
|
59
|
-
resolvedBindings = await this.pendingUpdate
|
|
60
|
-
} catch (error) {
|
|
61
|
-
console.error(error.message)
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (this.bindingsStr !== bindingsStr) return // Next update in progress
|
|
66
|
-
|
|
67
|
-
const env = Object.assign({ VCAP_SERVICES: '{}' }, await this.bindingEnv(resolvedBindings));
|
|
68
|
-
if (onAfterUpdate) onAfterUpdate({ bindings: resolvedBindings, env })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
static toVcapServices(bindings) {
|
|
72
|
-
const services = {}
|
|
73
|
-
|
|
74
|
-
for (const name in bindings) {
|
|
75
|
-
const binding = bindings[name]
|
|
76
|
-
const vcap = binding.binding.vcap || {}
|
|
77
|
-
const type = vcap.type || vcap.label
|
|
78
|
-
const serviceEntries = services[type] = services[type] || []
|
|
79
|
-
|
|
80
|
-
// setting the 'name' property is important to make CAP match the CDS service to the VCAP service
|
|
81
|
-
// it is also added to the 'vcap' property by 'cds bind'
|
|
82
|
-
serviceEntries.push({ ...vcap, name, credentials: binding.credentials })
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return services
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
module.exports = BindingManager
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
const cds = require('../cds');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { platformText, bindingText, logger } = require('./bindingUtil');
|
|
4
|
-
const { bold } = require('../util/term');
|
|
5
|
-
|
|
6
|
-
class BindingResolver {
|
|
7
|
-
getUnresolvedBindings({ cwd = process.cwd(), env } = {}) {
|
|
8
|
-
if (!env) {
|
|
9
|
-
env = cds.env.for('cds', path.resolve(cwd)) // REVISIT: This might load cds.env too early!
|
|
10
|
-
}
|
|
11
|
-
const bindings = {}
|
|
12
|
-
const requires = env.requires || {}
|
|
13
|
-
for (const name in requires) {
|
|
14
|
-
const service = requires[name]
|
|
15
|
-
if (service && service.binding && !service.binding.resolved) {
|
|
16
|
-
bindings[name] = { ...service.binding, kind: service.kind }
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return bindings
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async resolveBindings({ bindings, cwd = process.cwd(), env, cache = {} }) {
|
|
23
|
-
bindings = bindings || this.getUnresolvedBindings({ cwd, env })
|
|
24
|
-
if (Object.keys(bindings).length === 0) return undefined
|
|
25
|
-
logger.log(`resolving cloud service bindings...`)
|
|
26
|
-
|
|
27
|
-
const resolvedBindings = {}
|
|
28
|
-
|
|
29
|
-
// run in parallel
|
|
30
|
-
await Promise.all(Object.keys(bindings).map(async name => {
|
|
31
|
-
const binding = bindings[name]
|
|
32
|
-
const cacheKey = JSON.stringify(binding)
|
|
33
|
-
let resolvedBinding
|
|
34
|
-
if (!cache[cacheKey]) {
|
|
35
|
-
resolvedBinding = cache[cacheKey] = await this.resolveBinding(name, binding)
|
|
36
|
-
logger.log(`bound ${bold(name)} to ${platformText(binding)} ${bindingText(binding)}`)
|
|
37
|
-
} else {
|
|
38
|
-
resolvedBinding = cache[cacheKey]
|
|
39
|
-
}
|
|
40
|
-
resolvedBindings[name] = resolvedBinding
|
|
41
|
-
}));
|
|
42
|
-
return Object.keys(resolvedBindings).length > 0 ? resolvedBindings : undefined
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
resolveBinding(name, binding) {
|
|
46
|
-
switch (binding.type) {
|
|
47
|
-
case 'cf': return require('./cfKeyProvider').resolve(name, binding)
|
|
48
|
-
case 'k8s': return require('./kubernetesCredentialsProvider').resolve(name, binding)
|
|
49
|
-
default: throw new Error(`Unsupported binding type ${bold(binding.type)} for service ${bold(name)}.`)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
module.exports = new BindingResolver()
|
package/lib/bind/bindingUtil.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
const { bold } = require('../util/term');
|
|
2
|
-
|
|
3
|
-
const KindToRequiresNameMap = {
|
|
4
|
-
xsuaa: 'auth', // REVISIT: remove with ^8
|
|
5
|
-
'xsuaa-auth': 'auth',
|
|
6
|
-
'ias-auth': 'auth',
|
|
7
|
-
'hana-cloud': 'db',
|
|
8
|
-
hana: 'db',
|
|
9
|
-
'hana-mt': 'db', // For compatibility only
|
|
10
|
-
'sql-mt': 'db', // For compatibility only
|
|
11
|
-
destinations: 'destinations',
|
|
12
|
-
connectivity: 'connectivity',
|
|
13
|
-
'audit-log': 'auditlog',
|
|
14
|
-
'audit-log-to-restv2': 'auditlog',
|
|
15
|
-
'enterprise-messaging': 'messaging',
|
|
16
|
-
'enterprise-messaging-amqp': 'messaging',
|
|
17
|
-
'enterprise-messaging-http': 'messaging',
|
|
18
|
-
'enterprise-messaging-shared': 'messaging',
|
|
19
|
-
'redis-messaging': 'messaging',
|
|
20
|
-
'multitenancy': 'multitenancy'
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const PreferredKinds = {
|
|
24
|
-
multitenancy: 1, 'hana-cloud': 1, xsuaa: 1, 'xsuaa-auth': 1, 'ias-auth': 1, 'saas-registry': 1
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const PLATFORM_TYPES = {
|
|
28
|
-
'cf': 'Cloud Foundry',
|
|
29
|
-
'k8s': 'Kubernetes'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
class BindingUtil {
|
|
33
|
-
static getRequiresNameFromKind(service, kind) {
|
|
34
|
-
const requiresName = KindToRequiresNameMap[kind];
|
|
35
|
-
if (!requiresName) throw new Error(`Unknown CDS service name for service ${bold(service)}. Please specify as argument for ${bold('cds bind')}.`)
|
|
36
|
-
return requiresName;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Determine kind value for services based on VCAP binding
|
|
41
|
-
* information in the configuration.
|
|
42
|
-
*/
|
|
43
|
-
static determineKinds(services) {
|
|
44
|
-
const vcapServices = {}
|
|
45
|
-
|
|
46
|
-
// Build VCAP_SERVICES
|
|
47
|
-
for (const service of services) {
|
|
48
|
-
service.kindCandidates = []
|
|
49
|
-
|
|
50
|
-
const { vcap = {} } = service.binding || {};
|
|
51
|
-
let { label, type } = vcap;
|
|
52
|
-
type = type || label;
|
|
53
|
-
if (!type) continue;
|
|
54
|
-
|
|
55
|
-
if (!vcapServices[type]) vcapServices[type] = [];
|
|
56
|
-
vcapServices[type].push({ credentials: { dummy: 'dummy' }, ...vcap, service });
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Auto configure services based on VCAP_SERVICES
|
|
60
|
-
|
|
61
|
-
const cds = require('../cds');
|
|
62
|
-
const env = cds.env.for('cds', process.cwd()); // REVISIT: This might load cds.env too early!
|
|
63
|
-
|
|
64
|
-
if (!env._find_credentials_for_required_service) {
|
|
65
|
-
throw new Error(`Please provide a service kind or update @sap/cds version to use the kind detection.`);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const requires = env.requires.kinds || {};
|
|
69
|
-
for (const [kind, service] of Object.entries(requires)) {
|
|
70
|
-
const vcapService = env._find_credentials_for_required_service(kind, service, vcapServices);
|
|
71
|
-
if (!vcapService) continue;
|
|
72
|
-
const isUniqueKind = !Object.values(requires).some(otherService =>
|
|
73
|
-
otherService !== service && otherService.kind === kind
|
|
74
|
-
)
|
|
75
|
-
if (isUniqueKind) vcapService.service.kindCandidates.push(kind);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Check if one "kind" per service was found
|
|
79
|
-
for (const service of services) {
|
|
80
|
-
const preferredKind = service.kindCandidates.find(kind => kind in PreferredKinds);
|
|
81
|
-
if (preferredKind) {
|
|
82
|
-
service.kind = preferredKind;
|
|
83
|
-
} else if (service.kindCandidates.length === 1) {
|
|
84
|
-
service.kind = service.kindCandidates[0];
|
|
85
|
-
}
|
|
86
|
-
delete service.kindCandidates;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
static async checkIsJavaProject(projectPath) {
|
|
91
|
-
const { constants, promises: fsp } = require('fs');
|
|
92
|
-
const path = require('path');
|
|
93
|
-
const pomXmlPath = path.join(projectPath, 'pom.xml');
|
|
94
|
-
try {
|
|
95
|
-
await fsp.access(pomXmlPath, constants.F_OK);
|
|
96
|
-
return true;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
static platformText(binding) {
|
|
103
|
-
if (!PLATFORM_TYPES[binding.type]) throw new Error(`Unexpected binding type ${bold(binding.type)}.`);
|
|
104
|
-
return PLATFORM_TYPES[binding.type]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
static bindingText(binding) {
|
|
108
|
-
switch (binding.type) {
|
|
109
|
-
case 'cf':
|
|
110
|
-
if (binding.key) {
|
|
111
|
-
return `managed service ${bold(binding.instance + ':' + binding.key)}`
|
|
112
|
-
} else {
|
|
113
|
-
return `user provided service ${bold(binding.instance)}`
|
|
114
|
-
}
|
|
115
|
-
case 'k8s':
|
|
116
|
-
if (binding.name) {
|
|
117
|
-
return `service binding ${bold(binding.name)}`
|
|
118
|
-
} else {
|
|
119
|
-
return `secret ${bold(binding.secret)}`
|
|
120
|
-
}
|
|
121
|
-
default:
|
|
122
|
-
throw new Error('Internal Error');
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
static logger = { log: console.log }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
module.exports = BindingUtil
|
package/lib/bind/index.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
const cds = require('../cds');
|
|
2
|
-
|
|
3
|
-
const BindingResolver = require('./bindingResolver');
|
|
4
|
-
const { determineKinds, getRequiresNameFromKind, checkIsJavaProject, platformText, bindingText, logger } = require('./bindingUtil');
|
|
5
|
-
const { bold } = require('../util/term');
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
module.exports = { bind, storeBindings, parseProfile, mergeCredentials }
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Executes cds build in the directory defined by cds.root.
|
|
12
|
-
*
|
|
13
|
-
* @param {object} options - command options as defined by build command.
|
|
14
|
-
*/
|
|
15
|
-
async function bind(options) {
|
|
16
|
-
let on;
|
|
17
|
-
switch (options.on) {
|
|
18
|
-
case 'k8s':
|
|
19
|
-
case 'kubernetes':
|
|
20
|
-
on = 'k8s';
|
|
21
|
-
break;
|
|
22
|
-
case undefined:
|
|
23
|
-
case null:
|
|
24
|
-
case 'cf':
|
|
25
|
-
case 'cloudfoundry':
|
|
26
|
-
on = 'cf';
|
|
27
|
-
break;
|
|
28
|
-
default:
|
|
29
|
-
throw `Invalid value '${options.on}' for option --on.`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const onText = { 'cf': 'Cloud Foundry', 'k8s': 'Kubernetes' }[on];
|
|
33
|
-
if (!options['to-app-services']) logger.log(`Retrieving data from ${bold(onText)}...`);
|
|
34
|
-
|
|
35
|
-
// run in parallel
|
|
36
|
-
const targets = options.to.split(/,/g);
|
|
37
|
-
const 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');
|
|
46
|
-
}
|
|
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
|
-
}));
|
|
53
|
-
|
|
54
|
-
if (options.kind) {
|
|
55
|
-
resolvedServices.forEach(service => service.kind = options.kind);
|
|
56
|
-
} else {
|
|
57
|
-
determineKinds(resolvedServices);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (const resolvedService of resolvedServices) {
|
|
61
|
-
if (options.serviceArg) {
|
|
62
|
-
resolvedService.name = options.serviceArg;
|
|
63
|
-
} else if (resolvedService.kind) {
|
|
64
|
-
resolvedService.name = getRequiresNameFromKind(resolvedService.binding.instance, resolvedService.kind);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
let { kind, name, binding } = resolvedService;
|
|
68
|
-
name = name ?? `custom-service:${binding.instance || binding.secret}`
|
|
69
|
-
resolvedService.name = resolvedService.name ?? name
|
|
70
|
-
const kindText = kind ? ` with kind ${bold(kind)}` : ''
|
|
71
|
-
const nameText = bold(name)
|
|
72
|
-
|
|
73
|
-
logger.log(`Binding ${nameText} to ${platformText(binding)} ${bindingText(binding)}${kindText}`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return resolvedServices;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function storeBindings(resolvedServices, options) {
|
|
80
|
-
// prepare for different providers
|
|
81
|
-
const userCdsrcJsonStorageProvider = require('./userCdsrcJsonStorageProvider');
|
|
82
|
-
|
|
83
|
-
await userCdsrcJsonStorageProvider.storeServices(resolvedServices, {
|
|
84
|
-
...options
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const isJavaProject = await checkIsJavaProject(cds.root);
|
|
88
|
-
if (!cds.cli.options['to-app-services']) {
|
|
89
|
-
logger.log(`\nTo run with cloud bindings use ${bold(runCmd(options.for, isJavaProject))}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function runCmd(profile, isJavaProject) {
|
|
94
|
-
profile = parseProfile(profile);
|
|
95
|
-
if (isJavaProject) {
|
|
96
|
-
const p = profile === 'hybrid' ? '' : ` --profile ${profile}`;
|
|
97
|
-
return `cds bind --exec${p} mvn spring-boot:run`;
|
|
98
|
-
} else {
|
|
99
|
-
const p = profile === 'development' ? '' : ` --profile ${profile}`;
|
|
100
|
-
return `cds watch${p}`;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function parseProfile(profile) {
|
|
105
|
-
if (!profile) return 'hybrid';
|
|
106
|
-
else if (profile === '-') return 'development';
|
|
107
|
-
else return profile;
|
|
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] = mergeCredentials(credentials[key], customCredentials[key]);
|
|
122
|
-
} else {
|
|
123
|
-
credentials[key] = customCredentials[key];
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
return credentials;
|
|
127
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
const KubernetesApiAccessor = require('./KubernetesApiAccessor');
|
|
2
|
-
const { mergeCredentials } = require('./index');
|
|
3
|
-
|
|
4
|
-
class KubernetesCredentialsProvider {
|
|
5
|
-
async init() {
|
|
6
|
-
this.api = new KubernetesApiAccessor();
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async resolve(name, binding) {
|
|
10
|
-
if (!this.initPromise) {
|
|
11
|
-
this.initPromise = this.init();
|
|
12
|
-
}
|
|
13
|
-
await this.initPromise;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const { context, namespace, cluster } = await this.api.context({ cluster: binding.cluster, namespace: binding.namespace });
|
|
17
|
-
let { name: bindingName, secret, credentials: customCredentials } = binding;
|
|
18
|
-
|
|
19
|
-
let instance;
|
|
20
|
-
let secretOrBindingResource = 'Secret';
|
|
21
|
-
|
|
22
|
-
if (bindingName) {
|
|
23
|
-
const bindingResource = await this.api.get({ context, namespace, kind: 'servicebindings', name: bindingName });
|
|
24
|
-
if (bindingResource === null) {
|
|
25
|
-
// Fallback to secret instead of service binding;
|
|
26
|
-
secret = bindingName;
|
|
27
|
-
bindingName = undefined;
|
|
28
|
-
secretOrBindingResource = 'Service Binding or Secret';
|
|
29
|
-
} else {
|
|
30
|
-
const { spec } = bindingResource
|
|
31
|
-
if (!spec.secretName || !spec.serviceInstanceName) {
|
|
32
|
-
throw new Error('Internal error')
|
|
33
|
-
}
|
|
34
|
-
secret = spec.secretName;
|
|
35
|
-
instance = spec.serviceInstanceName;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const secretResource = await this.api.get({ context, namespace, kind: 'secrets', name: secret });
|
|
40
|
-
if (secretResource === null) throw new Error(`${secretOrBindingResource} '${secret}' not found.`);
|
|
41
|
-
|
|
42
|
-
const serviceBinding = this._readBinding(name, secretResource);
|
|
43
|
-
const { credentials } = serviceBinding;
|
|
44
|
-
const vcap = { ...serviceBinding, credentials: undefined }
|
|
45
|
-
|
|
46
|
-
const resolvedBinding = {
|
|
47
|
-
binding: {
|
|
48
|
-
type: 'k8s',
|
|
49
|
-
name: bindingName,
|
|
50
|
-
cluster,
|
|
51
|
-
instance,
|
|
52
|
-
namespace,
|
|
53
|
-
secret: secret,
|
|
54
|
-
resolved: true,
|
|
55
|
-
vcap
|
|
56
|
-
},
|
|
57
|
-
credentials: mergeCredentials(credentials, customCredentials)
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
return resolvedBinding;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
_readBinding(name, secret) {
|
|
64
|
-
const { data } = secret;
|
|
65
|
-
const properties = {};
|
|
66
|
-
for (const name in data) {
|
|
67
|
-
const value = Buffer.from(data[name], 'base64').toString()
|
|
68
|
-
properties[name] = value;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
let parseBinding
|
|
72
|
-
try {
|
|
73
|
-
// eslint-disable-next-line
|
|
74
|
-
parseBinding = require('@sap/cds/lib/env/serviceBindings').parseBinding;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
//
|
|
77
|
-
}
|
|
78
|
-
if (!parseBinding) throw new Error(`Please update your @sap/cds version for Kubernetes support`)
|
|
79
|
-
|
|
80
|
-
return parseBinding('/', name, properties);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
module.exports = new KubernetesCredentialsProvider();
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
const os = require('os');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
|
|
4
|
-
const fs = require('fs').promises;
|
|
5
|
-
const { bold } = require('../util/term');
|
|
6
|
-
const { logger } = require('./bindingUtil');
|
|
7
|
-
|
|
8
|
-
const gitUtil = require('../util/git');
|
|
9
|
-
|
|
10
|
-
const CDSRC_FILENAME = '.cdsrc-private.json';
|
|
11
|
-
|
|
12
|
-
class UserCdsrcJsonStorageProvider {
|
|
13
|
-
|
|
14
|
-
async storeServices(services, options = {}) {
|
|
15
|
-
let jsonPath = options.out || CDSRC_FILENAME;
|
|
16
|
-
|
|
17
|
-
if (jsonPath === CDSRC_FILENAME) {
|
|
18
|
-
await gitUtil.ensureFileIsGitignored(CDSRC_FILENAME);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
jsonPath = jsonPath.replace(/~/g, os.homedir());
|
|
22
|
-
if (path.extname(jsonPath) !== '.json') {
|
|
23
|
-
jsonPath = path.join(jsonPath, '.cdsrc.json');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const jsonObject = await this.readJsonSafely(jsonPath);
|
|
27
|
-
let cdsRoot;
|
|
28
|
-
if (path.basename(jsonPath) === 'package.json') {
|
|
29
|
-
jsonObject.cds = jsonObject.cds || {};
|
|
30
|
-
cdsRoot = jsonObject.cds;
|
|
31
|
-
} else {
|
|
32
|
-
cdsRoot = jsonObject;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const profileKey = `[${options.for}]`;
|
|
36
|
-
const features = cdsRoot.features && cdsRoot.features[profileKey];
|
|
37
|
-
if (features) delete features.emulate_vcap_services;
|
|
38
|
-
|
|
39
|
-
cdsRoot.requires = cdsRoot.requires || {};
|
|
40
|
-
let section;
|
|
41
|
-
section = cdsRoot.requires[profileKey] = cdsRoot.requires[profileKey] || {};
|
|
42
|
-
|
|
43
|
-
for (const service of services) {
|
|
44
|
-
const { name, kind, binding, credentials } = service;
|
|
45
|
-
const requireService = section[name] = section[name] || {};
|
|
46
|
-
// store optional custom credentials
|
|
47
|
-
binding.credentials = this._getServiceCredentials(binding, services, options);
|
|
48
|
-
|
|
49
|
-
Object.assign(requireService, { binding: { ...binding, vcap: undefined }, credentials, kind, vcap: { name } });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
await this.writeJsonFile(jsonPath, jsonObject);
|
|
53
|
-
logger.log(`Saving bindings to ${bold(jsonPath)} in profile ${bold(options.for)}`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async readJsonSafely(filePath) {
|
|
57
|
-
try {
|
|
58
|
-
return JSON.parse(await fs.readFile(filePath));
|
|
59
|
-
} catch (err) {
|
|
60
|
-
return {};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async writeJsonFile(filePath, content) {
|
|
65
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
66
|
-
await fs.writeFile(filePath, JSON.stringify(content, null, 2));
|
|
67
|
-
await fs.chmod(filePath, 0o600); // -rw-------
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_getServiceCredentials(binding, services, options) {
|
|
71
|
-
const { to, 'to-app-services': app, credentials } = options;
|
|
72
|
-
if (!credentials) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
// toService has format <service-name>[:<key>]
|
|
76
|
-
const toServices = to ? to.split(',') : services.map(s => s.binding.instance);
|
|
77
|
-
const toService = toServices.find(toService => binding.type === 'cf' ?
|
|
78
|
-
toService === binding.instance || toService === binding.instance + ':' + binding.key :
|
|
79
|
-
toService === binding.secret);
|
|
80
|
-
if (!toService) {
|
|
81
|
-
// must never happen
|
|
82
|
-
throw new Error(`Internal error: Service ${binding.instance}:${binding.key} not found`);
|
|
83
|
-
}
|
|
84
|
-
// app based binding always requires service name as key in credentials
|
|
85
|
-
if (!app && toServices.length === 1) {
|
|
86
|
-
return credentials[toService] || credentials;
|
|
87
|
-
}
|
|
88
|
-
return credentials[toService];
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = new UserCdsrcJsonStorageProvider();
|