sap-ai-dev-toolkit 0.9.9913 → 0.9.9915

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.
@@ -26,7 +26,7 @@ function cliError(error) {
26
26
  return failure;
27
27
  }
28
28
 
29
- async function runCf(args, { env = process.env, execFileImpl = nodeExecFile } = {}) {
29
+ export async function runCf(args, { env = process.env, execFileImpl = nodeExecFile } = {}) {
30
30
  try {
31
31
  const run = execFileImpl === nodeExecFile ? execFile : promisify(execFileImpl);
32
32
  const result = await run('cf', args, {
@@ -86,7 +86,7 @@ export async function getCloudFoundryTarget({ env = process.env, execFileImpl =
86
86
  if (!spaceGuid || /\s/.test(spaceGuid)) {
87
87
  return { available: false, reason: 'Cloud Foundry CLI is not authenticated to a targeted space; CF destinations were skipped.' };
88
88
  }
89
- return { available: true, spaceGuid };
89
+ return { available: true, spaceGuid, spaceName, orgName: /^\s*org:\s*(.*?)\s*$/im.exec(target)?.[1] || '' };
90
90
  }
91
91
 
92
92
  function missingServiceKey(error) {
@@ -159,7 +159,7 @@ export async function findOrphanedCloudFoundryServiceKeys({ env = process.env, s
159
159
  return orphans;
160
160
  }
161
161
 
162
- async function cfJson(path, options) {
162
+ export async function cfJson(path, options) {
163
163
  const value = await runCf(['curl', path], options);
164
164
  try {
165
165
  return JSON.parse(value);
@@ -177,7 +177,7 @@ function apiPath(href) {
177
177
  }
178
178
  }
179
179
 
180
- async function serviceInstances(spaceGuid, options) {
180
+ export async function serviceInstances(spaceGuid, options) {
181
181
  const instances = [];
182
182
  let path = `/v3/service_instances?space_guids=${encodeURIComponent(spaceGuid)}&per_page=500`;
183
183
  const visited = new Set();
@@ -398,7 +398,7 @@ async function readServiceKey(instance, kind, spaceGuid, managedKeys, createdKey
398
398
  return { keyName, credentials };
399
399
  }
400
400
 
401
- async function serviceKeyCredentials(instanceName, keyName, kind, options) {
401
+ export async function serviceKeyCredentials(instanceName, keyName, kind, options) {
402
402
  const raw = await runCf(['service-key', '--json', instanceName, keyName], options);
403
403
  let parsed;
404
404
  try {
@@ -0,0 +1,74 @@
1
+ // Integration Suite credentials from the logged-in Cloud Foundry space: find the Process Integration
2
+ // Runtime (plan api) and API Management (plan apiportal-apiaccess) instances, reuse or create one
3
+ // named service key per instance, and optionally create a dedicated instance with the roles the tools need.
4
+ import { execFile as nodeExecFile } from 'node:child_process';
5
+ import { cfJson, runCf, serviceInstances, serviceKeyCredentials } from './cf-destination.mjs';
6
+
7
+ // Not the toolkit's destination-key prefix: the destination orphan sweep must not offer to delete these keys.
8
+ export const INTEGRATION_KEY_NAME = 'sap-ai-dev-integration';
9
+
10
+ export const INTEGRATION_CF_SERVICES = Object.freeze({
11
+ CPI: Object.freeze({
12
+ offering: 'it-rt',
13
+ plan: 'api',
14
+ label: 'Process Integration Runtime',
15
+ dedicatedInstance: 'sap-ai-dev-integration-cpi',
16
+ // Role templates of the Cloud Integration API plan covering design, deployment, monitoring,
17
+ // security material and Partner Directory work done by the cpi_* tools.
18
+ parameters: Object.freeze({
19
+ 'grant-types': ['client_credentials'],
20
+ 'redirect-uris': [],
21
+ roles: ['AuthGroup_Administrator', 'AuthGroup_BusinessExpert', 'AuthGroup_IntegrationDeveloper', 'AuthGroup_TenantPartnerDirectoryConfigurator', 'AccessAllAccessPoliciesArtifacts']
22
+ })
23
+ }),
24
+ APIM: Object.freeze({
25
+ offering: 'apimanagement-apiportal',
26
+ plan: 'apiportal-apiaccess',
27
+ label: 'API Management, API portal',
28
+ dedicatedInstance: 'sap-ai-dev-integration-apim',
29
+ parameters: Object.freeze({ role: 'APIPortal.Administrator' })
30
+ })
31
+ });
32
+
33
+ /** Integration Suite instances in the space, by side: { CPI: [{ guid, name }], APIM: [...] }. */
34
+ export async function findIntegrationInstances({ env = process.env, spaceGuid, execFileImpl = nodeExecFile } = {}) {
35
+ const options = { env, execFileImpl };
36
+ const plans = new Map();
37
+ const found = { CPI: [], APIM: [] };
38
+ for (const instance of await serviceInstances(spaceGuid, options)) {
39
+ const planGuid = instance?.relationships?.service_plan?.data?.guid;
40
+ if (!planGuid || !instance?.name) continue;
41
+ if (!plans.has(planGuid)) {
42
+ try {
43
+ const plan = await cfJson(`/v3/service_plans/${encodeURIComponent(planGuid)}?include=service_offering`, options);
44
+ plans.set(planGuid, { plan: plan?.name, offering: plan?.included?.service_offerings?.[0]?.name });
45
+ } catch {
46
+ plans.set(planGuid, {});
47
+ }
48
+ }
49
+ const { plan, offering } = plans.get(planGuid);
50
+ for (const [side, service] of Object.entries(INTEGRATION_CF_SERVICES)) {
51
+ if (offering === service.offering && plan === service.plan) found[side].push({ guid: String(instance.guid), name: String(instance.name) });
52
+ }
53
+ }
54
+ return found;
55
+ }
56
+
57
+ /** Credentials of the toolkit's service key on an instance; the key is created when it does not exist yet. */
58
+ export async function integrationServiceKey(instanceName, { env = process.env, execFileImpl = nodeExecFile } = {}) {
59
+ const options = { env, execFileImpl };
60
+ try {
61
+ return { keyName: INTEGRATION_KEY_NAME, created: false, credentials: await serviceKeyCredentials(instanceName, INTEGRATION_KEY_NAME, 'Integration Suite', options) };
62
+ } catch {
63
+ // No readable key of that name yet.
64
+ }
65
+ await runCf(['create-service-key', instanceName, INTEGRATION_KEY_NAME, '--wait'], options);
66
+ return { keyName: INTEGRATION_KEY_NAME, created: true, credentials: await serviceKeyCredentials(instanceName, INTEGRATION_KEY_NAME, 'Integration Suite', options) };
67
+ }
68
+
69
+ /** Create the dedicated instance for a side with the roles the tools need; returns { guid?, name }. */
70
+ export async function createIntegrationInstance(side, { env = process.env, execFileImpl = nodeExecFile } = {}) {
71
+ const service = INTEGRATION_CF_SERVICES[side];
72
+ await runCf(['create-service', service.offering, service.plan, service.dedicatedInstance, '-c', JSON.stringify(service.parameters), '--wait'], { env, execFileImpl });
73
+ return { name: service.dedicatedInstance };
74
+ }