@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
147 lines (129 loc) • 6.48 kB
JavaScript
const cds = require('../cds');
const DEBUG = cds.log('cli')._debug
const cf = require('../util/cf');
const { bold } = require('../util/term');
const { mergeCredentials } = require('./shared');
module.exports = new class CFBind extends require('./plugin') {
/* Public API */
displayName() {
return 'Cloud Foundry'
}
binding4(service) {
const [instance, key] = service.split(/:/)
return { instance, key }
}
description4(binding) {
if (binding.key) {
return `managed service ${bold(binding.instance + ':' + binding.key)}`
} else {
return `user provided service ${bold(binding.instance)}`
}
}
service4({ instance, key }, services) {
const toServices = Array.isArray(services) ? services.map(s => s.binding.instance) : services.split(',')
const service = toServices.find(s => s === instance || s === instance + ':' + key)
if (!service) throw `service '${instance}:${key}' not found`
return service
}
async resolve(name, binding) {
this.target ??= await cf.target()
this.#checkApiEndpoint(name, binding)
let { instance, org, space } = binding;
let spaceGuid, orgGuid;
if (org && space) {
// org and space can be overwritten by currently logged in org and space
const orgObj = await cf.org(org);
if (!orgObj) throw `org ${bold(org)} not found when resolving service instance ${bold(instance)} in ${bold(this.target.apiEndpoint)}`;
const spaceObj = await cf.space(orgObj.guid, space);
if (!spaceObj) throw `space ${bold(space)} not found in org ${bold(org)} when resolving service instance ${bold(instance)} in ${bold(this.target.apiEndpoint)}`;
spaceGuid = spaceObj.guid;
orgGuid = orgObj.guid;
} else {
org = this.target.org;
space = this.target.space;
spaceGuid = this.target.spaceGuid;
orgGuid = this.target.orgGuid;
}
let instanceObj;
try {
instanceObj = await cf.instance(spaceGuid, instance);
const instanceSpaceGuid = instanceObj.relationships.space.data.guid;
if (instanceSpaceGuid !== spaceGuid) {
// service instance is shared from another space, service key must be created in that space
const sharedFromSpaceObj = await cf.curl('/v3/spaces/' + encodeURIComponent(instanceSpaceGuid));
space = sharedFromSpaceObj.name;
spaceGuid = sharedFromSpaceObj.guid;
const instanceOrgGuid = sharedFromSpaceObj.relationships.organization.data.guid;
if (instanceOrgGuid !== orgGuid) {
const sharedFromOrgObj = await cf.curl('/v3/organizations/' + encodeURIComponent(instanceOrgGuid));
org = sharedFromOrgObj.name;
orgGuid = sharedFromOrgObj.guid;
}
}
} catch (error) {
throw `service instance ${bold(instance)} not found.`;
}
switch (instanceObj.type) {
case 'managed':
return this.#resolveManagedService(instanceObj, instance, binding.key || `${instance}-key`, org, space, binding.credentials);
case 'user-provided':
return this.#resolveUserProvidedService(instanceObj, instance, org, space, binding.credentials);
default:
throw `Service type ${bold(instanceObj.type)} not supported`;
}
}
/* Private helpers */
async #resolveManagedService(instanceObj, instance, key, org, space, customCredentials) {
const create = (cds.cli.command === 'bind' || cds.cli.command === 'deploy') && !cds.cli.options['no-create-service-key']
const silent = (cds.cli.options.json || cds.cli.command === 'env') && !DEBUG
const planObj = await cf.plan(instanceObj.relationships.service_plan.data.guid);
const offeringObj = await cf.offering(planObj.relationships.service_offering.data.guid);
const parameters = offeringObj.name === "identity" ? { "credential-type": "X509_GENERATED", "app-identifier" : "cds_bind" } : {};
const credentials = await cf.getOrCreateServiceKey(instanceObj, key, parameters, { create, silent });
if (!credentials) {
let message = `No service key ${bold(key)} found for service instance ${bold(instance)}.\n\n`;
message += `Use ${bold(`cf create-service-key ${instanceObj.name} ${key} [-c ...]`)} to create the required service key.`;
throw message;
}
const resolvedBinding = {
binding: {
type: "cf",
apiEndpoint: this.target.apiEndpoint,
org, space, instance, key,
vcap: {
label: offeringObj.name,
plan: planObj.name,
tags: offeringObj.tags
}
},
credentials: mergeCredentials(credentials, customCredentials)
};
return resolvedBinding;
}
async #resolveUserProvidedService(instanceObj, instance, org, space, customCredentials) {
const credentials = await cf.curl(`/v3/service_instances/${encodeURIComponent(instanceObj.guid)}/credentials`);
const resolvedBinding = {
binding: {
type: "cf",
apiEndpoint: this.target.apiEndpoint,
org, space, instance,
vcap: {
label: instanceObj.type,
tags: instanceObj.tags
}
},
credentials: mergeCredentials(credentials, customCredentials)
};
return resolvedBinding;
}
#checkApiEndpoint(name, binding) {
const { apiEndpoint, key, instance } = binding
// if no api endpoint is configured the currently logged in endpoint will be used
if (apiEndpoint && apiEndpoint !== this.target.apiEndpoint) {
name = `${name || instance}:${key || instance + '-key'}`;
let message = `Current Cloud Foundry API endpoint ${bold(this.target.apiEndpoint)} differs from API endpoint ${bold(apiEndpoint)} for service binding ${bold(name)}.\n\n`;
message += `Use ${bold(`cf login -a ${apiEndpoint}`)} to log in to the target API endpoint.`;
throw message;
}
}
}