UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

147 lines (129 loc) 6.48 kB
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; } } }